diff --git a/.changeset/quiet-push-payments.md b/.changeset/quiet-push-payments.md
new file mode 100644
index 0000000..28485c9
--- /dev/null
+++ b/.changeset/quiet-push-payments.md
@@ -0,0 +1,6 @@
+---
+"@chiahq/sdk": minor
+"@chiahq/mcp": minor
+---
+
+Add OneKhusa direct mobile money push payments, hosted checkout sessions for inline modal or redirect use, webhook types, and matching MCP tools.
diff --git a/docs/docs/mcp/claude-desktop.md b/docs/docs/mcp/claude-desktop.md
index bea6960..ff5f89f 100644
--- a/docs/docs/mcp/claude-desktop.md
+++ b/docs/docs/mcp/claude-desktop.md
@@ -29,6 +29,8 @@ Add the following to your Claude Desktop config file:
"ONEKHUSA_API_KEY": "your-onekhusa-api-key",
"ONEKHUSA_API_SECRET": "your-onekhusa-api-secret",
"ONEKHUSA_ORGANISATION_ID": "your-organisation-id",
+ "ONEKHUSA_MERCHANT_ACCOUNT_NUMBER": "35253486",
+ "ONEKHUSA_DEFAULT_CAPTURED_BY": "payments@example.com",
"ENVIRONMENT": "DEVELOPMENT"
}
}
@@ -45,6 +47,8 @@ Add the following to your Claude Desktop config file:
| `ONEKHUSA_API_KEY` | OneKhusa | Your OneKhusa API key |
| `ONEKHUSA_API_SECRET` | OneKhusa | Your OneKhusa API secret |
| `ONEKHUSA_ORGANISATION_ID` | OneKhusa | Your OneKhusa organisation ID |
+| `ONEKHUSA_MERCHANT_ACCOUNT_NUMBER` | OneKhusa | Merchant account used for OneKhusa requests |
+| `ONEKHUSA_DEFAULT_CAPTURED_BY` | OneKhusa | Default operator email for collection requests |
| `ENVIRONMENT` | All | `DEVELOPMENT` or `PRODUCTION` |
:::tip
@@ -99,6 +103,8 @@ You only need to configure the providers you plan to use. Leave out the environm
"ONEKHUSA_API_KEY": "your-api-key",
"ONEKHUSA_API_SECRET": "your-api-secret",
"ONEKHUSA_ORGANISATION_ID": "your-org-id",
+ "ONEKHUSA_MERCHANT_ACCOUNT_NUMBER": "35253486",
+ "ONEKHUSA_DEFAULT_CAPTURED_BY": "payments@example.com",
"ENVIRONMENT": "DEVELOPMENT"
}
}
diff --git a/docs/docs/mcp/overview.md b/docs/docs/mcp/overview.md
index b83dc77..4f5066c 100644
--- a/docs/docs/mcp/overview.md
+++ b/docs/docs/mcp/overview.md
@@ -10,7 +10,7 @@ import Version from '@site/src/components/Version';
-Model Context Protocol server enabling AI assistants like Claude to handle payment operations through natural language. **48 comprehensive tools**: 4 provider-agnostic unified tools plus dedicated tools for PayChangu, PawaPay, and OneKhusa.
+Model Context Protocol server enabling AI assistants like Claude to handle payment operations through natural language. **50 comprehensive tools**: 4 provider-agnostic unified tools plus dedicated tools for PayChangu, PawaPay, and OneKhusa.
## What is MCP?
@@ -24,7 +24,7 @@ Instead of writing code, you can simply ask:
## Features
-- **48 Tools** - Comprehensive coverage of all payment operations
+- **50 Tools** - Comprehensive coverage of all payment operations
- **Unified by Default** - Provider-agnostic tools that route for you, so most tasks don't need a specific provider named
- **Natural Language** - Interact with payments using plain English
- **Multiple Providers** - PayChangu, PawaPay, and OneKhusa support
@@ -37,7 +37,7 @@ Instead of writing code, you can simply ask:
| Unified | 4 | Provider-agnostic collections and payouts, routed by country and currency |
| PayChangu | 12 | Payments, transfers, mobile money, bank payouts |
| PawaPay | 14 | Deposits, payouts, refunds, wallets, config |
-| OneKhusa | 18 | Collections, single/batch disbursements |
+| OneKhusa | 20 | Direct push, hosted checkout, collections, single/batch disbursements |
## Quick Example
diff --git a/docs/docs/mcp/tools/onekhusa.md b/docs/docs/mcp/tools/onekhusa.md
index 1fab4a1..5138732 100644
--- a/docs/docs/mcp/tools/onekhusa.md
+++ b/docs/docs/mcp/tools/onekhusa.md
@@ -4,19 +4,35 @@ title: OneKhusa Tools
description: OneKhusa MCP tools reference
---
-# OneKhusa Tools (18)
+# OneKhusa Tools (20)
MCP tools for OneKhusa payment operations.
-## Collections (3 tools)
+## Collections (5 tools)
### Initiate Request-to-Pay
-Request payment from a customer.
+Reserve a timed account number for the customer to pay into.
**Example prompts:**
-- *"Request a payment of 5000 MWK from 265991234567 using OneKhusa"*
-- *"Initiate a OneKhusa collection for 10000 MWK"*
+- *"Reserve a OneKhusa timed account number for 5000 MWK"*
+- *"Initiate a OneKhusa request-to-pay for order ORDER12345"*
+
+### Initiate Direct Push
+
+Start a USSD prompt on the customer's mobile money wallet. The final outcome arrives through webhooks.
+
+**Example prompts:**
+- *"Send a OneKhusa payment prompt for 5000 MWK to 265991234567"*
+- *"Start a direct OneKhusa push using connector 550044"*
+
+### Initiate Standard Checkout
+
+Create a hosted checkout session and return a URL that can be opened in a modal or redirect.
+
+**Example prompts:**
+- *"Create a OneKhusa checkout for 150 MWK and return to my success and failure pages"*
+- *"Start a OneKhusa inline checkout for order ORDER12345"*
### Get Collection Transactions
@@ -37,107 +53,107 @@ Get details for a specific collection.
### Add Single Disbursement
-Create a payout to one recipient.
+Create a payout to one beneficiary using their OneKhusa connector and account number.
**Example prompts:**
-- *"Create a OneKhusa disbursement of 10000 MWK to John Doe at 265991234567"*
+- *"Create a OneKhusa disbursement of 10000 to John Doe at 265991234567 using connector 550044"*
- *"Send a single payout via OneKhusa"*
### Approve Disbursement
-Approve a pending disbursement.
+Approve a pending disbursement using its transaction reference number.
**Example prompts:**
-- *"Approve OneKhusa disbursement DIS_12345"*
+- *"Approve OneKhusa transaction reference PAYOUT123"*
### Review Disbursement
-Mark a disbursement as reviewed.
+Mark a disbursement as reviewed using its transaction reference number.
**Example prompts:**
-- *"Review disbursement DIS_12345"*
+- *"Review OneKhusa transaction reference PAYOUT123"*
### Reject Disbursement
-Reject a disbursement.
+Reject a disbursement using its transaction reference number.
**Example prompts:**
-- *"Reject disbursement DIS_12345 because of incorrect details"*
+- *"Reject OneKhusa transaction reference PAYOUT123 because of incorrect details"*
### Get Disbursement Details
-Check disbursement status.
+Check disbursement details using its transaction reference number.
**Example prompts:**
-- *"Get status of disbursement DIS_12345"*
+- *"Get details for OneKhusa transaction reference PAYOUT123"*
## Batch Disbursements (9 tools)
### Add Batch Disbursement
-Create a batch with multiple recipients.
+Create a batch with beneficiary entries. Each entry includes a connector, beneficiary account, amount, description, and source reference.
**Example prompts:**
-- *"Create a batch disbursement for January salaries with 3 recipients totaling 150000 MWK"*
+- *"Create a OneKhusa batch for January salaries with 3 beneficiary entries totaling 150000"*
- *"Start a OneKhusa batch payout"*
### Approve Batch
-Approve a batch for processing.
+Approve a batch using its numeric batch number.
**Example prompts:**
-- *"Approve batch BATCH_12345"*
+- *"Approve OneKhusa batch number 12345"*
### Review Batch
-Mark a batch as reviewed.
+Mark a batch as reviewed using its numeric batch number.
**Example prompts:**
-- *"Review batch BATCH_12345"*
+- *"Review OneKhusa batch number 12345"*
### Reject Batch
-Reject a batch.
+Reject a batch, optionally identifying its header ID.
**Example prompts:**
-- *"Reject batch BATCH_12345 - budget not approved"*
+- *"Reject OneKhusa batch header 12345 because the budget was not approved"*
### Cancel Batch
-Cancel a batch.
+Cancel a pending batch using its numeric batch number.
**Example prompts:**
-- *"Cancel batch BATCH_12345"*
+- *"Cancel OneKhusa batch number 12345"*
### Transfer Batch Funds
-Process payment for an approved batch.
+Transfer funds for an approved batch using its numeric batch number.
**Example prompts:**
-- *"Transfer funds for batch BATCH_12345"*
-- *"Process batch BATCH_12345"*
+- *"Transfer funds for OneKhusa batch number 12345"*
+- *"Process OneKhusa batch number 12345"*
### Get All Batches
-List your batches.
+List batches for a transaction date and page.
**Example prompts:**
- *"Show my OneKhusa batches"*
-- *"Get all pending batches"*
+- *"List OneKhusa disbursement batches for 2026-10-31"*
### Get Batch Details
-Get details for a specific batch.
+Get details for a specific batch number.
**Example prompts:**
-- *"Get details for batch BATCH_12345"*
+- *"Get details for OneKhusa batch number 12345"*
### Get Batch Transactions
-List transactions within a batch.
+List transactions within a batch using its numeric batch number.
**Example prompts:**
-- *"Show transactions in batch BATCH_12345"*
+- *"Show transactions in OneKhusa batch number 12345"*
## Config (1 tool)
diff --git a/docs/docs/mcp/troubleshooting.md b/docs/docs/mcp/troubleshooting.md
index a51fc84..6ff134a 100644
--- a/docs/docs/mcp/troubleshooting.md
+++ b/docs/docs/mcp/troubleshooting.md
@@ -97,7 +97,8 @@ The provider isn't configured. Add the required environment variables:
"PAWAPAY_JWT": "...",
"ONEKHUSA_API_KEY": "...",
"ONEKHUSA_API_SECRET": "...",
- "ONEKHUSA_ORGANISATION_ID": "..."
+ "ONEKHUSA_ORGANISATION_ID": "...",
+ "ONEKHUSA_MERCHANT_ACCOUNT_NUMBER": "35253486"
}
}
```
diff --git a/docs/docs/sdk/configuration.md b/docs/docs/sdk/configuration.md
index 67d162b..99f6258 100644
--- a/docs/docs/sdk/configuration.md
+++ b/docs/docs/sdk/configuration.md
@@ -35,6 +35,7 @@ const sdk = ChiaSDK.initialize({
apiKey: "your-api-key",
apiSecret: "your-api-secret",
organisationId: "your-organisation-id",
+ merchantAccountNumber: 35253486,
environment: ENVIRONMENTS.DEVELOPMENT
}
});
@@ -62,6 +63,7 @@ PAWAPAY_ENVIRONMENT=DEVELOPMENT
ONEKHUSA_API_KEY=your-onekhusa-api-key
ONEKHUSA_API_SECRET=your-onekhusa-api-secret
ONEKHUSA_ORGANISATION_ID=your-organisation-id
+ONEKHUSA_MERCHANT_ACCOUNT_NUMBER=35253486
ONEKHUSA_ENVIRONMENT=DEVELOPMENT
# Chia platform - enables automatic provider routing and sdk.platform.*
@@ -110,6 +112,7 @@ const sdk = ChiaSDK.initialize({
apiKey: "your-api-key",
apiSecret: "your-api-secret",
organisationId: "your-organisation-id",
+ merchantAccountNumber: 35253486,
environment: ENVIRONMENTS.DEVELOPMENT,
sandboxUrl: "https://custom-sandbox.onekhusa.com/v1",
productionUrl: "https://custom-prod.onekhusa.com/v1"
@@ -121,8 +124,8 @@ const sdk = ChiaSDK.initialize({
Both `sandboxUrl` and `productionUrl` are optional. If not provided, the SDK uses the default provider URLs. The URL used depends on the `environment` setting.
:::
-:::note OneKhusa OAuth
-For OneKhusa, the SDK automatically appends `/oauth/token` to custom URLs for authentication endpoints.
+:::note OneKhusa token scope
+OneKhusa access tokens are scoped to a merchant account. Configure the merchant account number used for the token and collection requests.
:::
## Selective Provider Configuration
@@ -149,6 +152,7 @@ const multiProvider = ChiaSDK.initialize({
apiKey: process.env.ONEKHUSA_API_KEY || "",
apiSecret: process.env.ONEKHUSA_API_SECRET || "",
organisationId: process.env.ONEKHUSA_ORGANISATION_ID || "",
+ merchantAccountNumber: Number(process.env.ONEKHUSA_MERCHANT_ACCOUNT_NUMBER),
environment: ENVIRONMENTS.PRODUCTION
}
});
diff --git a/docs/docs/sdk/onekhusa/collections.md b/docs/docs/sdk/onekhusa/collections.md
index 6475964..776e099 100644
--- a/docs/docs/sdk/onekhusa/collections.md
+++ b/docs/docs/sdk/onekhusa/collections.md
@@ -7,101 +7,117 @@ description: OneKhusa collection operations
# OneKhusa Collections
:::info Direct provider access
-For ordinary collections routed automatically across your configured providers, use [`sdk.payments`](/docs/sdk/unified-payments) instead. This page covers direct OneKhusa access and OneKhusa-specific features.
+For ordinary collections routed automatically across your configured providers, use [`sdk.payments`](/docs/sdk/unified-payments). This page covers direct OneKhusa collections and checkout.
:::
-Request payments from customers via mobile money or bank transfer.
+## Direct mobile money push
-## Initiate Request-to-Pay
-
-Request payment from a customer:
+Start a USSD prompt for a customer. The response confirms initiation only; use the `pushpayment.success`, `pushpayment.failed`, and `pushpayment.reversed` webhooks to track the final outcome.
```typescript
-const collection = await sdk.onekhusa.collections.initiateRequestToPay({
- amount: 5000,
- currency: "MWK",
- phoneNumber: "265991234567",
- reference: "order-123",
- narration: "Payment for goods"
+import { isServiceError } from "@chiahq/sdk";
+
+const result = await sdk.onekhusa.collections.initiateDirectPushPayment({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ connectorId: 550044,
+ payerAccountNumber: "0999000000",
+ sourceReferenceNumber: "ORDER12345",
+ transactionDescription: "Payment for order ORDER12345",
+ transactionAmount: 1000,
+ capturedBy: "payments@example.com"
});
-// The TAN (Transaction Authentication Number) is used by the customer
-// to authorize the payment on their device
-console.log("TAN:", collection.tan);
-console.log("Transaction ID:", collection.transactionId);
-```
+if (isServiceError(result)) {
+ throw new Error(result.errorMessage);
+}
-### Parameters
+console.log(result.responseCode);
+```
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `amount` | number | Yes | Amount to collect |
-| `currency` | string | Yes | Currency code (MWK, USD, etc.) |
-| `phoneNumber` | string | Yes | Customer phone number |
-| `reference` | string | Yes | Your unique reference |
-| `narration` | string | No | Description for the transaction |
+Use a unique `sourceReferenceNumber` for each attempt. Find connector IDs with `sdk.onekhusa.getConnectors()`.
-## Get Collection Transactions
+## Standard Checkout
-Retrieve paginated collection transactions:
+Create a hosted checkout session for card or mobile money. For inline checkout, load the returned `checkoutUrl` in your app's modal overlay. The URL expires after 10 minutes and can also be used as a redirect. Subscribe to the push payment webhooks and rely on them for the final payment result.
```typescript
-const transactions = await sdk.onekhusa.collections.getTransactions({
- page: 0,
- size: 20,
- status: "COMPLETED"
+import { isServiceError } from "@chiahq/sdk";
+
+const result = await sdk.onekhusa.collections.initiateStandardCheckout({
+ payment: {
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ amount: 150,
+ description: "Groceries purchase",
+ sourceReferenceNumber: "ORDER12346",
+ capturedBy: "payments@example.com"
+ },
+ route: {
+ successRedirectionUrl: "https://example.com/payment/success",
+ failureRedirectionUrl: "https://example.com/payment/failure"
+ }
});
-transactions.content.forEach(tx => {
- console.log(`${tx.reference}: ${tx.status} - ${tx.amount} ${tx.currency}`);
-});
+if (isServiceError(result)) {
+ throw new Error(result.errorMessage);
+}
+
+console.log(result.checkoutUrl);
```
-### Filter Parameters
+See [OneKhusa Standard Checkout](https://docs.onekhusa.com/api-reference/collections/standard-checkout) for payment method and webhook setup requirements.
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `page` | number | Page number (0-indexed) |
-| `size` | number | Items per page |
-| `status` | string | Filter by status |
-| `startDate` | string | Filter from date |
-| `endDate` | string | Filter to date |
+## Request-to-Pay
-## Get Transaction Details
+Reserve a timed account number for the customer to pay into. Settlement is reported through the collection webhook.
```typescript
-const details = await sdk.onekhusa.collections.getTransaction(transactionId);
+import { isServiceError } from "@chiahq/sdk";
+
+const result = await sdk.onekhusa.collections.initiateRequestToPay({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionAmount: 5000,
+ transactionDescription: "Payment for goods",
+ referenceNumber: "ORDER12347",
+ capturedBy: "payments@example.com"
+});
+
+if (isServiceError(result)) {
+ throw new Error(result.errorMessage);
+}
-console.log("Status:", details.status);
-console.log("Amount:", details.amount);
-console.log("Completed at:", details.completedAt);
+console.log(result.timedAccountNumber);
```
-## Transaction Statuses
+## List collection transactions
-| Status | Description |
-|--------|-------------|
-| `PENDING` | Awaiting customer authorization |
-| `COMPLETED` | Payment received |
-| `FAILED` | Transaction failed |
-| `CANCELLED` | Cancelled by customer or system |
-| `EXPIRED` | TAN expired without authorization |
+```typescript
+const transactions = await sdk.onekhusa.collections.getTransactions({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionDate: "2026-10-31",
+ pageNumber: 1,
+ numberOfReturnedRows: 20,
+ isIncremental: false,
+ searchBy: "TransactionReferenceNumber"
+});
+```
-## Supported Currencies
+## Get a collection transaction
-- MWK (Malawian Kwacha)
-- USD (US Dollar)
-- ZAR (South African Rand)
-- ZMW (Zambian Kwacha)
-- TZS (Tanzanian Shilling)
-- KES (Kenyan Shilling)
-- UGX (Ugandan Shilling)
+```typescript
+const result = await sdk.onekhusa.collections.getTransaction({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: "260921VK4NEG9V"
+});
+```
-## Response Types
+## Types
```typescript
import type { OneKhusaTypes } from "@chiahq/sdk";
-type CollectionResponse = OneKhusaTypes.CollectionResponse;
-type TransactionsResponse = OneKhusaTypes.CollectionTransactionsResponse;
+type DirectPushRequest = OneKhusaTypes.InitiateDirectPushPaymentRequest;
+type CheckoutRequest = OneKhusaTypes.StandardCheckoutRequest;
+type CheckoutResponse = OneKhusaTypes.StandardCheckoutResponse;
+type PushWebhook = OneKhusaTypes.PushPaymentWebhookPayload;
+type WebhookEvent = OneKhusaTypes.CollectionWebhookEvent;
```
diff --git a/docs/docs/sdk/onekhusa/disbursements.md b/docs/docs/sdk/onekhusa/disbursements.md
index 594d694..eac7ca9 100644
--- a/docs/docs/sdk/onekhusa/disbursements.md
+++ b/docs/docs/sdk/onekhusa/disbursements.md
@@ -7,183 +7,183 @@ description: OneKhusa disbursement operations
# OneKhusa Disbursements
:::info Direct provider access
-For ordinary payouts routed automatically across your configured providers, use [`sdk.payouts`](/docs/sdk/unified-payments) instead - note that OneKhusa payouts always require approval. This page covers direct OneKhusa access, including batch disbursements, which are a OneKhusa-specific feature.
+For ordinary payouts routed across providers, use [`sdk.payouts`](/docs/sdk/unified-payments). OneKhusa payouts require approval. This page covers direct OneKhusa operations, including batch disbursements.
:::
-Send payments to recipients with single or batch disbursements.
+Every request includes the configured OneKhusa merchant account number. Write operations that require an operator use `capturedBy`, `actionedBy`, `rejectedBy`, `cancelledBy`, or `transferredBy` as shown in the request types.
## Single Disbursements
-### Create a Disbursement
+### Create and approve a disbursement
```typescript
+import { isServiceError } from "@chiahq/sdk";
+
const disbursement = await sdk.onekhusa.disbursements.addSingle({
- amount: 10000,
- currency: "MWK",
- paymentMethod: "MOBILE_MONEY",
- recipient: {
- name: "John Doe",
- phone: "265991234567"
- },
- reference: "payout-001",
- narration: "Salary payment"
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ beneficiaryName: "John Doe",
+ connectorId: 550044,
+ beneficiaryAccountNumber: "265991234567",
+ transactionAmount: 10000,
+ transactionDescription: "Salary payment",
+ sourceReferenceNumber: "payout-001",
+ capturedBy: "payments@example.com"
});
-console.log("Disbursement ID:", disbursement.id);
-console.log("Status:", disbursement.status);
-```
+if (isServiceError(disbursement)) {
+ throw new Error(disbursement.errorMessage);
+}
-### Approve a Disbursement
+const approval = await sdk.onekhusa.disbursements.approveSingle({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: disbursement.transactionReferenceNumber,
+ actionedBy: "payments@example.com"
+});
-```typescript
-await sdk.onekhusa.disbursements.approveSingle(disbursementId);
+if (isServiceError(approval)) {
+ throw new Error(approval.errorMessage);
+}
```
-### Review a Disbursement
+### Review or reject a disbursement
```typescript
-await sdk.onekhusa.disbursements.reviewSingle(disbursementId);
-```
-
-### Reject a Disbursement
+await sdk.onekhusa.disbursements.reviewSingle({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: "transaction-reference",
+ actionedBy: "payments@example.com"
+});
-```typescript
-await sdk.onekhusa.disbursements.rejectSingle(disbursementId, {
- reason: "Incorrect recipient details"
+await sdk.onekhusa.disbursements.rejectSingle({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: "transaction-reference",
+ reasonForRejection: "Incorrect beneficiary details",
+ rejectedBy: "payments@example.com"
});
```
-### Get Disbursement Details
+### Get disbursements
```typescript
-const details = await sdk.onekhusa.disbursements.getSingle(disbursementId);
+const transactions = await sdk.onekhusa.disbursements.getSingleTransactions({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionDate: "2026-10-31",
+ pageNumber: 1,
+ numberOfReturnedRows: 20,
+ isIncremental: false,
+ searchBy: "TransactionReferenceNumber"
+});
-console.log("Status:", details.status);
-console.log("Amount:", details.amount);
+const transaction = await sdk.onekhusa.disbursements.getSingleTransaction({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: "transaction-reference"
+});
```
## Batch Disbursements
-Process multiple payouts efficiently.
-
-### Create a Batch
+### Create a batch
```typescript
-const batch = await sdk.onekhusa.disbursements.addBatch({
- name: "January Salaries",
- currency: "MWK",
- paymentMethod: "MOBILE_MONEY",
- recipients: [
- { name: "John Doe", phone: "265991234567", amount: 50000 },
- { name: "Jane Smith", phone: "265999876543", amount: 45000 },
- { name: "Bob Wilson", phone: "265888765432", amount: 55000 }
+const batch = await sdk.onekhusa.disbursements.addBatchJson({
+ header: {
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ isBatchScheduled: false,
+ capturedBy: "payments@example.com"
+ },
+ transactions: [
+ {
+ beneficiaryName: "John Doe",
+ connectorId: 550044,
+ beneficiaryAccountNumber: "265991234567",
+ transactionDescription: "January salary",
+ transactionAmount: 50000,
+ sourceReferenceNumber: "salary-john-001"
+ }
]
});
-
-console.log("Batch ID:", batch.id);
-console.log("Total:", batch.totalAmount);
-```
-
-### Approve Batch
-
-```typescript
-await sdk.onekhusa.disbursements.approveBatch(batchId);
-```
-
-### Review Batch
-
-```typescript
-await sdk.onekhusa.disbursements.reviewBatch(batchId);
```
-### Transfer Funds
-
-After approval, transfer funds to process the batch:
+### Review, approve, reject, cancel, or transfer
```typescript
-await sdk.onekhusa.disbursements.transferBatchFunds(batchId);
-```
+await sdk.onekhusa.disbursements.reviewBatch({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ batchNumber: 12345,
+ actionedBy: "payments@example.com"
+});
-### Cancel Batch
+await sdk.onekhusa.disbursements.approveBatch({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ batchNumber: 12345,
+ actionedBy: "payments@example.com"
+});
-```typescript
-await sdk.onekhusa.disbursements.cancelBatch(batchId);
-```
+await sdk.onekhusa.disbursements.rejectBatch({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ reasonForRejection: "Budget not approved",
+ rejectedBy: "payments@example.com",
+ headerId: 12345
+});
-### Reject Batch
+await sdk.onekhusa.disbursements.cancelBatch({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ batchNumber: 12345,
+ cancelledBy: "payments@example.com"
+});
-```typescript
-await sdk.onekhusa.disbursements.rejectBatch(batchId, {
- reason: "Budget not approved"
+await sdk.onekhusa.disbursements.transferBatchFunds({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ batchNumber: 12345,
+ transferredBy: "payments@example.com"
});
```
-### Get All Batches
+### Get batches and transactions
```typescript
const batches = await sdk.onekhusa.disbursements.getBatches({
- page: 0,
- size: 10
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionDate: "2026-10-31",
+ pageNumber: 1,
+ numberOfReturnedRows: 20,
+ isIncremental: false
});
-batches.content.forEach(batch => {
- console.log(`${batch.name}: ${batch.status} - ${batch.totalAmount}`);
+const batchDetails = await sdk.onekhusa.disbursements.getBatch({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ batchNumber: 12345
});
-```
-### Get Batch Details
-
-```typescript
-const batchDetails = await sdk.onekhusa.disbursements.getBatch(batchId);
+const transactions = await sdk.onekhusa.disbursements.getBatchTransactions({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ batchNumber: 12345,
+ pageNumber: 1,
+ numberOfReturnedRows: 20,
+ isIncremental: false
+});
```
-### Get Batch Transactions
+## File batches
+
+`addBatchFile` accepts a base64-encoded CSV or XLSX payload:
```typescript
-const transactions = await sdk.onekhusa.disbursements.getBatchTransactions(batchId, {
- page: 0,
- size: 20
+await sdk.onekhusa.disbursements.addBatchFile({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ isBatchScheduled: false,
+ contentType: "text/csv",
+ capturedBy: "payments@example.com",
+ fileContent: encodedFile
});
```
-## Disbursement Statuses
-
-| Status | Description |
-|--------|-------------|
-| `PENDING` | Awaiting review/approval |
-| `APPROVED` | Approved, awaiting fund transfer |
-| `REVIEWED` | Reviewed, awaiting approval |
-| `REJECTED` | Rejected |
-| `CANCELLED` | Cancelled |
-| `PROCESSING` | Funds being transferred |
-| `COMPLETED` | Successfully disbursed |
-| `FAILED` | Disbursement failed |
-
-## Payment Methods
-
-| Method | Description |
-|--------|-------------|
-| `MOBILE_MONEY` | Mobile money transfer |
-| `BANK_TRANSFER` | Bank account transfer |
-
-## Recipient Parameters
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `name` | string | Yes | Recipient name |
-| `phone` | string | Yes | Phone number |
-| `email` | string | No | Email address |
-| `accountNumber` | string | No | Bank account (for bank transfers) |
-| `bankCode` | string | No | Bank code (for bank transfers) |
-| `amount` | number | Yes* | Amount (required for batch recipients) |
-
-## Response Types
+## Response types
```typescript
import type { OneKhusaTypes } from "@chiahq/sdk";
-type DisbursementResponse = OneKhusaTypes.SingleDisbursementResponse;
-type BatchResponse = OneKhusaTypes.BatchDisbursementResponse;
-type BatchesResponse = OneKhusaTypes.BatchesResponse;
-type BatchTransactionsResponse = OneKhusaTypes.BatchTransactionsResponse;
+type DisbursementAck = OneKhusaTypes.SingleDisbursementAck;
+type BatchAck = OneKhusaTypes.BatchAck;
+type BatchSummary = OneKhusaTypes.BatchSummary;
```
diff --git a/docs/docs/sdk/overview.md b/docs/docs/sdk/overview.md
index c287c6a..6ffd781 100644
--- a/docs/docs/sdk/overview.md
+++ b/docs/docs/sdk/overview.md
@@ -37,6 +37,7 @@ const sdk = ChiaSDK.initialize({
apiKey: "your-api-key",
apiSecret: "your-api-secret",
organisationId: "your-org-id",
+ merchantAccountNumber: 35253486,
environment: ENVIRONMENTS.DEVELOPMENT
}
});
@@ -63,13 +64,13 @@ the full error hierarchy.
Each provider is also available on its own namespace - `sdk.pawapay`, `sdk.paychangu`,
`sdk.onekhusa` - for provider-specific features the unified API doesn't cover: PawaPay
refunds, remittances and wallet balances, PayChangu hosted checkout and bank transfers,
-OneKhusa batch disbursements, and more.
+OneKhusa direct push, hosted checkout, and batch disbursements, and more.
| Provider | Region | Direct-access capabilities |
|----------|--------|--------------|
| PayChangu | Malawi | Hosted checkout, mobile money, bank transfers |
| PawaPay | Sub-Saharan Africa | Deposits, payouts, refunds, remittances, wallets |
-| OneKhusa | Malawi | Collections, single and batch disbursements |
+| OneKhusa | Malawi | Direct push, hosted checkout, request-to-pay, single and batch disbursements |
This is the escape hatch, not a gap: reach for it when you need something specific to one
provider. See the provider pages under Direct Provider Access for the full reference.
diff --git a/docs/docs/sdk/quick-start.md b/docs/docs/sdk/quick-start.md
index 0db652e..49ff3a3 100644
--- a/docs/docs/sdk/quick-start.md
+++ b/docs/docs/sdk/quick-start.md
@@ -28,6 +28,7 @@ const sdk = ChiaSDK.initialize({
apiKey: "your-onekhusa-api-key",
apiSecret: "your-onekhusa-api-secret",
organisationId: "your-organisation-id",
+ merchantAccountNumber: 35253486,
environment: ENVIRONMENTS.DEVELOPMENT
}
});
@@ -79,18 +80,30 @@ error handling.
Each provider is also available on its own namespace, for provider-specific features the
unified API doesn't cover - PawaPay refunds and remittances, PayChangu hosted checkout and
-bank transfers, OneKhusa batch disbursements, and so on:
+bank transfers, OneKhusa direct push and hosted checkout, and so on:
```typescript
-const collection = await sdk.onekhusa.collections.initiateRequestToPay({
- amount: 5000,
- currency: "MWK",
- phoneNumber: "265991234567",
- reference: "order-789",
- narration: "Payment for goods"
+import { isServiceError } from "@chiahq/sdk";
+
+const checkout = await sdk.onekhusa.collections.initiateStandardCheckout({
+ payment: {
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ amount: 5000,
+ description: "Payment for goods",
+ sourceReferenceNumber: "ORDER789",
+ capturedBy: "payments@example.com"
+ },
+ route: {
+ successRedirectionUrl: "https://example.com/payment/success",
+ failureRedirectionUrl: "https://example.com/payment/failure"
+ }
});
-console.log("Collection TAN:", collection.tan);
+if (isServiceError(checkout)) {
+ throw new Error(checkout.errorMessage);
+}
+
+console.log("Checkout URL:", checkout.checkoutUrl);
```
- [PawaPay](/docs/sdk/pawapay/deposits) - direct PawaPay API reference
diff --git a/docs/docs/sdk/types.md b/docs/docs/sdk/types.md
index c9ab3ee..4b420fb 100644
--- a/docs/docs/sdk/types.md
+++ b/docs/docs/sdk/types.md
@@ -54,11 +54,13 @@ import type {
```typescript
import type { OneKhusaTypes } from "@chiahq/sdk";
-type CollectionRequest = OneKhusaTypes.InitiateCollectionRequest;
-type CollectionResponse = OneKhusaTypes.CollectionResponse;
-type SingleDisbursementRequest = OneKhusaTypes.SingleDisbursementRequest;
+type RequestToPayRequest = OneKhusaTypes.InitiateRequestToPayRequest;
+type DirectPushRequest = OneKhusaTypes.InitiateDirectPushPaymentRequest;
+type StandardCheckoutRequest = OneKhusaTypes.StandardCheckoutRequest;
+type StandardCheckoutResponse = OneKhusaTypes.StandardCheckoutResponse;
+type SingleDisbursementRequest = OneKhusaTypes.AddSingleDisbursementRequest;
type BatchDisbursementRequest = OneKhusaTypes.AddBatchDisbursementRequest;
-type Recipient = OneKhusaTypes.Recipient;
+type PushWebhookPayload = OneKhusaTypes.PushPaymentWebhookPayload;
```
## Common Types
@@ -84,6 +86,7 @@ const config: SDKConfig = {
apiKey: "...",
apiSecret: "...",
organisationId: "...",
+ merchantAccountNumber: 35253486,
environment: "DEVELOPMENT"
},
providers: {
diff --git a/docs/docs/sdk/unified-payments.md b/docs/docs/sdk/unified-payments.md
index 821f994..1782b69 100644
--- a/docs/docs/sdk/unified-payments.md
+++ b/docs/docs/sdk/unified-payments.md
@@ -191,7 +191,11 @@ const payout = await sdk.payouts.send({
});
if (payout.requiresApproval) {
- await sdk.onekhusa.disbursements.approveSingle(payout.id);
+ await sdk.onekhusa.disbursements.approveSingle({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: payout.id,
+ actionedBy: "payments@example.com"
+ });
}
```
diff --git a/examples/README.md b/examples/README.md
index bc7ec04..769f349 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -8,7 +8,7 @@ Working examples showing how to use Chia for subscription billing over mobile mo
|---------|-------------|---------------|
| **[platform-subscribe](./platform-subscribe/)** | Most users | The full subscribe flow via Chia's managed platform. Sign up, create plans, collect payments - Chia handles billing, renewals, and payouts. |
| **[widget-basic](./widget-basic/)** | Frontend developers | Embed a subscribe widget on your website with a single script tag. No backend needed. |
-| **[sdk-direct](./sdk-direct/)** | Developers with their own provider accounts | Direct access to PayChangu, PawaPay, and OneKhusa APIs via the SDK. Collections, payouts, status checks. |
+| **[sdk-direct](./sdk-direct/)** | Developers with their own provider accounts | Direct access to PayChangu, PawaPay, and OneKhusa APIs via the SDK, including OneKhusa direct push and hosted checkout. |
| **[basic-usage](./basic-usage/)** | SDK reference | Hono server with endpoints for each provider operation. Good for understanding the raw SDK API. |
## Getting started
diff --git a/examples/basic-usage/.env.example b/examples/basic-usage/.env.example
index 23a2ded..476a0fd 100644
--- a/examples/basic-usage/.env.example
+++ b/examples/basic-usage/.env.example
@@ -9,4 +9,9 @@ PAYCHANGU_RETURN_URL=http://localhost:3000/api/paychangu/callback
# PawaPay Configuration
PAWAPAY_JWT=your_pawapay_jwt
-PAWAPAY_ENVIRONMENT=DEVELOPMENT
\ No newline at end of file
+PAWAPAY_ENVIRONMENT=DEVELOPMENT
+
+ONEKHUSA_API_KEY=your_onekhusa_api_key
+ONEKHUSA_API_SECRET=your_onekhusa_api_secret
+ONEKHUSA_ORGANISATION_ID=your_organisation_id
+ONEKHUSA_MERCHANT_ACCOUNT_NUMBER=35253486
diff --git a/examples/basic-usage/chia-sdk.postman_collection.json b/examples/basic-usage/chia-sdk.postman_collection.json
index 1582bd6..d6308d9 100644
--- a/examples/basic-usage/chia-sdk.postman_collection.json
+++ b/examples/basic-usage/chia-sdk.postman_collection.json
@@ -184,19 +184,6 @@
"item": [
{
"name": "Request to Pay",
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- "const res = pm.response.json();",
- "if (res.id) {",
- " pm.collectionVariables.set('transactionId', res.id);",
- "}"
- ]
- }
- }
- ],
"request": {
"method": "POST",
"url": "{{baseUrl}}/onekhusa/collections",
@@ -205,7 +192,35 @@
],
"body": {
"mode": "raw",
- "raw": "{\n\t\"amount\": 1000,\n\t\"currency\": \"MWK\",\n\t\"phone\": \"265888123456\",\n\t\"paymentMethod\": \"MOBILE_MONEY\",\n\t\"reference\": \"TEST_001\",\n\t\"description\": \"Test collection\"\n}"
+ "raw": "{\n\t\"amount\": 1000,\n\t\"referenceNumber\": \"ORDER12345\",\n\t\"description\": \"Test collection\",\n\t\"capturedBy\": \"payments@example.com\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Direct Push",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/onekhusa/collections/direct-push",
+ "header": [
+ { "key": "Content-Type", "value": "application/json" }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"connectorId\": 550044,\n\t\"payerAccountNumber\": \"0999000000\",\n\t\"sourceReferenceNumber\": \"PUSH12345\",\n\t\"description\": \"Test direct push\",\n\t\"amount\": 1000,\n\t\"capturedBy\": \"payments@example.com\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Standard Checkout",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/onekhusa/checkout",
+ "header": [
+ { "key": "Content-Type", "value": "application/json" }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"amount\": 150,\n\t\"description\": \"Groceries purchase\",\n\t\"sourceReferenceNumber\": \"ORDER12346\",\n\t\"capturedBy\": \"payments@example.com\",\n\t\"successRedirectionUrl\": \"https://example.com/payment/success\",\n\t\"failureRedirectionUrl\": \"https://example.com/payment/failure\"\n}"
}
}
},
diff --git a/examples/basic-usage/src/index.ts b/examples/basic-usage/src/index.ts
index d91a73a..df7323f 100644
--- a/examples/basic-usage/src/index.ts
+++ b/examples/basic-usage/src/index.ts
@@ -85,7 +85,9 @@ pawapay.post("/deposits", async (c) => {
});
pawapay.get("/deposits/:depositId", async (c) => {
- const result = await sdk.pawapay.deposits.getDeposit(c.req.param("depositId"));
+ const result = await sdk.pawapay.deposits.getDeposit(
+ c.req.param("depositId"),
+ );
if (isServiceError(result)) {
return c.json(result, 400);
}
@@ -96,7 +98,10 @@ pawapay.post("/payments", async (c) => {
const body = await c.req.json();
const result = await sdk.pawapay.payments.initiatePayment({
depositId: body.depositId || crypto.randomUUID(),
- returnUrl: body.returnUrl || process.env.PAWAPAY_RETURN_URL || "https://example.com/return",
+ returnUrl:
+ body.returnUrl ||
+ process.env.PAWAPAY_RETURN_URL ||
+ "https://example.com/return",
amount: body.amount,
country: body.country,
reason: body.reason,
@@ -121,13 +126,49 @@ const onekhusa = new Hono();
onekhusa.post("/collections", async (c) => {
const body = await c.req.json();
const result = await sdk.onekhusa.collections.initiateRequestToPay({
- amount: body.amount,
- currency: body.currency || "MWK",
- phone: body.phone,
- paymentMethod: body.paymentMethod || "MOBILE_MONEY",
- reference: body.reference,
- description: body.description,
- callbackUrl: body.callbackUrl,
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionAmount: body.amount,
+ transactionDescription: body.description,
+ referenceNumber: body.referenceNumber,
+ capturedBy: body.capturedBy,
+ });
+ if (isServiceError(result)) {
+ return c.json(result, 400);
+ }
+ return c.json(result);
+});
+
+onekhusa.post("/collections/direct-push", async (c) => {
+ const body = await c.req.json();
+ const result = await sdk.onekhusa.collections.initiateDirectPushPayment({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ connectorId: body.connectorId,
+ payerAccountNumber: body.payerAccountNumber,
+ sourceReferenceNumber: body.sourceReferenceNumber,
+ transactionDescription: body.description,
+ transactionAmount: body.amount,
+ capturedBy: body.capturedBy,
+ });
+ if (isServiceError(result)) {
+ return c.json(result, 400);
+ }
+ return c.json(result);
+});
+
+onekhusa.post("/checkout", async (c) => {
+ const body = await c.req.json();
+ const result = await sdk.onekhusa.collections.initiateStandardCheckout({
+ payment: {
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ amount: body.amount,
+ description: body.description,
+ sourceReferenceNumber: body.sourceReferenceNumber,
+ capturedBy: body.capturedBy,
+ },
+ route: {
+ successRedirectionUrl: body.successRedirectionUrl,
+ failureRedirectionUrl: body.failureRedirectionUrl,
+ },
});
if (isServiceError(result)) {
return c.json(result, 400);
@@ -136,7 +177,10 @@ onekhusa.post("/collections", async (c) => {
});
onekhusa.get("/collections/:transactionId", async (c) => {
- const result = await sdk.onekhusa.collections.getTransaction(c.req.param("transactionId"));
+ const result = await sdk.onekhusa.collections.getTransaction({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: c.req.param("transactionId"),
+ });
if (isServiceError(result)) {
return c.json(result, 400);
}
@@ -149,5 +193,7 @@ const port = Number(process.env.PORT) || 9999;
serve({ fetch: app.fetch, port }, () => {
console.log(`Server running on http://localhost:${port}`);
- console.log(`Configured services: ${sdk.getConfiguredServices().join(", ") || "none"}`);
+ console.log(
+ `Configured services: ${sdk.getConfiguredServices().join(", ") || "none"}`,
+ );
});
diff --git a/examples/sdk-direct/.env.example b/examples/sdk-direct/.env.example
index 8cd551e..cd8d4df 100644
--- a/examples/sdk-direct/.env.example
+++ b/examples/sdk-direct/.env.example
@@ -8,3 +8,10 @@ PAWAPAY_JWT=
ONEKHUSA_API_KEY=
ONEKHUSA_API_SECRET=
ONEKHUSA_ORGANISATION_ID=
+ONEKHUSA_MERCHANT_ACCOUNT_NUMBER=
+ONEKHUSA_CAPTURED_BY=
+ONEKHUSA_CONNECTOR_ID=
+ONEKHUSA_PAYER_ACCOUNT_NUMBER=
+ONEKHUSA_SUCCESS_REDIRECTION_URL=
+ONEKHUSA_FAILURE_REDIRECTION_URL=
+ONEKHUSA_PAYOUT_ACCOUNT_NUMBER=
diff --git a/examples/sdk-direct/README.md b/examples/sdk-direct/README.md
index 58a9d71..e61cba6 100644
--- a/examples/sdk-direct/README.md
+++ b/examples/sdk-direct/README.md
@@ -10,7 +10,7 @@ You need API credentials from at least one provider:
- **PayChangu**: A secret key from [paychangu.com](https://paychangu.com)
- **PawaPay**: A JWT token from [pawapay.io](https://pawapay.io)
-- **OneKhusa**: An API key, API secret, and organisation ID from [onekhusa.com](https://onekhusa.com)
+- **OneKhusa**: API credentials, merchant account number, and captured-by email from [onekhusa.com](https://onekhusa.com)
## Setup
@@ -32,7 +32,8 @@ Each script targets a specific provider and operation:
# Collections (receiving money from customers)
pnpm collect:paychangu # PayChangu mobile money collection
pnpm collect:pawapay # PawaPay deposit
-pnpm collect:onekhusa # OneKhusa request-to-pay
+pnpm collect:onekhusa
+pnpm checkout:onekhusa
# Payouts (sending money to recipients)
pnpm payout:paychangu # PayChangu mobile money payout
@@ -48,7 +49,8 @@ pnpm all-providers # Side-by-side demo of all three providers
- **PayChangu**: Uses `initializeMobileMoneyCollection` to request payment from a mobile money number, then `verifyMobileMoneyPayment` to check the result.
- **PawaPay**: Uses `deposits.sendDeposit` to initiate a mobile money deposit, then `deposits.getDeposit` to poll for status.
-- **OneKhusa**: Uses `collections.initiateRequestToPay` to send a payment request, then `collections.getTransaction` to check status.
+- **OneKhusa direct push**: Uses `collections.initiateDirectPushPayment`; final status arrives through push payment webhooks.
+- **OneKhusa checkout**: Uses `collections.initiateStandardCheckout` to create a hosted checkout URL for inline modal display or redirect.
### Payouts (money out)
diff --git a/examples/sdk-direct/package.json b/examples/sdk-direct/package.json
index 9e0d183..1f9ad90 100644
--- a/examples/sdk-direct/package.json
+++ b/examples/sdk-direct/package.json
@@ -6,6 +6,7 @@
"collect:paychangu": "tsx src/collect-paychangu.ts",
"collect:pawapay": "tsx src/collect-pawapay.ts",
"collect:onekhusa": "tsx src/collect-onekhusa.ts",
+ "checkout:onekhusa": "tsx src/checkout-onekhusa.ts",
"payout:paychangu": "tsx src/payout-paychangu.ts",
"payout:pawapay": "tsx src/payout-pawapay.ts",
"all-providers": "tsx src/all-providers.ts"
diff --git a/examples/sdk-direct/src/all-providers.ts b/examples/sdk-direct/src/all-providers.ts
index db26541..c89c255 100644
--- a/examples/sdk-direct/src/all-providers.ts
+++ b/examples/sdk-direct/src/all-providers.ts
@@ -58,15 +58,15 @@ async function demoPayChangu() {
const collectChargeId = `pc-collect-${Date.now()}`;
const collectResult = await sdk.paychangu.initializeMobileMoneyCollection(
- "0888800000", // Customer's Airtel Malawi number
- "airtel-malawi-ref", // Mobile money operator reference ID
- 500, // MWK 500
+ "0888800000", // Customer's Airtel Malawi number
+ "airtel-malawi-ref", // Mobile money operator reference ID
+ 500, // MWK 500
collectChargeId,
{
firstName: "Chimwemwe",
lastName: "Mwale",
email: "chimwemwe@example.com",
- transactionStatus: "successful", // Sandbox: simulate success
+ transactionStatus: "successful", // Sandbox: simulate success
},
);
@@ -74,8 +74,12 @@ async function demoPayChangu() {
console.log(` Error: ${collectResult.payload.ErrorMessage}`);
} else {
console.log(` Charge ID: ${collectChargeId}`);
- console.log(` Status: ${collectResult.payload.PaymentDetails?.status || "initiated"}`);
- console.log(` Details: ${JSON.stringify(collectResult.payload.PaymentDetails, null, 2)}`);
+ console.log(
+ ` Status: ${collectResult.payload.PaymentDetails?.status || "initiated"}`,
+ );
+ console.log(
+ ` Details: ${JSON.stringify(collectResult.payload.PaymentDetails, null, 2)}`,
+ );
}
console.log();
@@ -83,12 +87,15 @@ async function demoPayChangu() {
step(2, "Verify: verifyMobileMoneyPayment");
- const verifyResult = await sdk.paychangu.verifyMobileMoneyPayment(collectChargeId);
+ const verifyResult =
+ await sdk.paychangu.verifyMobileMoneyPayment(collectChargeId);
if (verifyResult.payload.HasError) {
console.log(` Error: ${verifyResult.payload.ErrorMessage}`);
} else {
- console.log(` Verified status: ${verifyResult.payload.PaymentDetails?.status || "unknown"}`);
+ console.log(
+ ` Verified status: ${verifyResult.payload.PaymentDetails?.status || "unknown"}`,
+ );
}
console.log();
@@ -101,9 +108,9 @@ async function demoPayChangu() {
const payoutChargeId = `pc-payout-${Date.now()}`;
const payoutResult = await sdk.paychangu.initializeMobileMoneyPayout(
- "0999100200", // Recipient's TNM Mpamba number
- "tnm-malawi-ref", // Mobile money operator reference ID
- 1000, // MWK 1000
+ "0999100200", // Recipient's TNM Mpamba number
+ "tnm-malawi-ref", // Mobile money operator reference ID
+ 1000, // MWK 1000
payoutChargeId,
{
firstName: "Grace",
@@ -116,7 +123,9 @@ async function demoPayChangu() {
console.log(` Error: ${payoutResult.payload.ErrorMessage}`);
} else {
console.log(` Charge ID: ${payoutChargeId}`);
- console.log(` Details: ${JSON.stringify(payoutResult.payload.PayoutDetails, null, 2)}`);
+ console.log(
+ ` Details: ${JSON.stringify(payoutResult.payload.PayoutDetails, null, 2)}`,
+ );
}
}
@@ -149,7 +158,9 @@ async function demoPawaPay() {
const availability = await sdk.pawapay.getAvailability();
if ("errorMessage" in availability) {
- console.log(` Error: ${(availability as { errorMessage: string }).errorMessage}`);
+ console.log(
+ ` Error: ${(availability as { errorMessage: string }).errorMessage}`,
+ );
} else {
const countries = availability as PawaPayTypes.CountryAvailability[];
console.log(` ${countries.length} countries available`);
@@ -174,7 +185,9 @@ async function demoPawaPay() {
const prediction = await sdk.pawapay.predictProvider(phoneNumber);
if ("errorMessage" in prediction) {
- console.log(` Error: ${(prediction as { errorMessage: string }).errorMessage}`);
+ console.log(
+ ` Error: ${(prediction as { errorMessage: string }).errorMessage}`,
+ );
return;
}
@@ -207,7 +220,9 @@ async function demoPawaPay() {
const depositResult = await sdk.pawapay.deposits.sendDeposit(depositRequest);
if (isServiceError(depositResult)) {
- console.log(` Error: ${depositResult.errorMessage} (${depositResult.statusCode})`);
+ console.log(
+ ` Error: ${depositResult.errorMessage} (${depositResult.statusCode})`,
+ );
} else {
console.log(` Deposit ID: ${depositResult.depositId}`);
console.log(` Status: ${depositResult.status}`);
@@ -229,7 +244,9 @@ async function demoPawaPay() {
console.log(` Deposit ID: ${depositStatus.depositId}`);
console.log(` Status: ${depositStatus.status}`);
if (depositStatus.amount) {
- console.log(` Amount: ${depositStatus.amount} ${depositStatus.currency}`);
+ console.log(
+ ` Amount: ${depositStatus.amount} ${depositStatus.currency}`,
+ );
}
}
console.log();
@@ -258,7 +275,9 @@ async function demoPawaPay() {
const payoutResult = await sdk.pawapay.payouts.sendPayout(payoutRequest);
if (isServiceError(payoutResult)) {
- console.log(` Error: ${payoutResult.errorMessage} (${payoutResult.statusCode})`);
+ console.log(
+ ` Error: ${payoutResult.errorMessage} (${payoutResult.statusCode})`,
+ );
} else {
console.log(` Payout ID: ${payoutResult.payoutId}`);
console.log(` Status: ${payoutResult.status}`);
@@ -272,13 +291,24 @@ async function demoPawaPay() {
async function demoOneKhusa() {
separator("OneKhusa (Malawi - MWK)");
+ const merchantAccountNumber = Number(
+ process.env.ONEKHUSA_MERCHANT_ACCOUNT_NUMBER,
+ );
+ const connectorId = Number(process.env.ONEKHUSA_CONNECTOR_ID);
+ const capturedBy = process.env.ONEKHUSA_CAPTURED_BY;
+
if (
!process.env.ONEKHUSA_API_KEY ||
!process.env.ONEKHUSA_API_SECRET ||
- !process.env.ONEKHUSA_ORGANISATION_ID
+ !process.env.ONEKHUSA_ORGANISATION_ID ||
+ !Number.isInteger(merchantAccountNumber) ||
+ !Number.isInteger(connectorId) ||
+ !capturedBy
) {
console.log(" Skipped - OneKhusa credentials not set\n");
- console.log(" Required: ONEKHUSA_API_KEY, ONEKHUSA_API_SECRET, ONEKHUSA_ORGANISATION_ID");
+ console.log(
+ " Required: OneKhusa API credentials, merchant account number, connector ID, and captured-by email",
+ );
return;
}
@@ -287,130 +317,88 @@ async function demoOneKhusa() {
apiKey: process.env.ONEKHUSA_API_KEY,
apiSecret: process.env.ONEKHUSA_API_SECRET,
organisationId: process.env.ONEKHUSA_ORGANISATION_ID,
+ merchantAccountNumber,
+ defaultCapturedBy: capturedBy,
environment: "DEVELOPMENT",
},
});
- // --- Health check ---
-
step(1, "Check status");
const status = await sdk.onekhusa.checkStatus();
- if ("errorMessage" in status) {
- console.log(` Error: ${(status as { errorMessage: string }).errorMessage}`);
+ if (!status.available) {
+ console.log(` Error: ${status.error || "OneKhusa is unavailable"}`);
return;
}
- const healthStatus = status as { available: boolean; environment: string };
- console.log(` Available: ${healthStatus.available}`);
- console.log(` Environment: ${healthStatus.environment}`);
+ console.log(` Available: ${status.available}`);
+ console.log(` Environment: ${status.environment}`);
console.log();
- // --- Collection ---
-
- step(2, "Collect: collections.initiateRequestToPay");
-
- // OneKhusa sends a request-to-pay to the customer's phone.
- // The customer sees a USSD prompt and approves with their PIN.
- const collectionResult = await sdk.onekhusa.collections.initiateRequestToPay({
- amount: 1000,
- currency: "MWK",
- phone: "+265888123456",
- paymentMethod: "MOBILE_MONEY",
- reference: `order-${Date.now()}`,
- description: "Monthly subscription payment",
- callbackUrl: "https://your-server.com/webhooks/onekhusa",
- metadata: {
- orderId: "12345",
- planId: "premium",
- },
- });
+ step(2, "Collect: collections.initiateDirectPushPayment");
+
+ const collectionResult =
+ await sdk.onekhusa.collections.initiateDirectPushPayment({
+ merchantAccountNumber,
+ connectorId,
+ payerAccountNumber:
+ process.env.ONEKHUSA_PAYER_ACCOUNT_NUMBER || "+265888123456",
+ sourceReferenceNumber: `ORDER${Date.now()}`,
+ transactionDescription: "Payment for order 12345",
+ transactionAmount: 1000,
+ capturedBy,
+ });
if (isServiceError(collectionResult)) {
- console.log(` Error: ${collectionResult.errorMessage} (${collectionResult.statusCode})`);
+ console.log(
+ ` Error: ${collectionResult.errorMessage} (${collectionResult.statusCode})`,
+ );
} else {
- console.log(` Transaction ID: ${collectionResult.id}`);
- console.log(` TAN: ${collectionResult.tan}`);
- console.log(` Status: ${collectionResult.status}`);
- console.log(` Amount: ${collectionResult.amount} ${collectionResult.currency}`);
- }
- console.log();
-
- // --- Check collection status ---
-
- step(3, "Check: collections.getTransaction");
-
- if (!isServiceError(collectionResult)) {
- const txResult = await sdk.onekhusa.collections.getTransaction(collectionResult.id);
-
- if (isServiceError(txResult)) {
- console.log(` Error: ${txResult.errorMessage}`);
- } else {
- console.log(` Transaction ID: ${txResult.id}`);
- console.log(` Status: ${txResult.status}`);
- if (txResult.completedAt) {
- console.log(` Completed: ${txResult.completedAt}`);
- }
- }
+ console.log(` Response code: ${collectionResult.responseCode}`);
+ console.log(` Amount to pay: ${collectionResult.amountToPay}`);
}
console.log();
- // --- Disbursement (payout) ---
-
- step(4, "Disburse: disbursements.addSingle + approveSingle");
-
- // OneKhusa disbursements use a two-step process:
- // 1. addSingle - creates the disbursement in PENDING state
- // 2. approveSingle - approves it for processing
- //
- // This allows for review workflows where a different user approves payments.
- // For automated payouts, call both steps back-to-back.
+ step(3, "Disburse: disbursements.addSingle + approveSingle");
const disbursementResult = await sdk.onekhusa.disbursements.addSingle({
- amount: 2000,
- currency: "MWK",
- recipient: {
- name: "James Kamanga",
- phone: "+265999876543", // TNM Mpamba number
- email: "james.k@example.com",
- },
- paymentMethod: "MOBILE_MONEY",
- reference: `payout-${Date.now()}`,
- description: "Freelancer payment - March invoice",
- callbackUrl: "https://your-server.com/webhooks/onekhusa",
- metadata: {
- invoiceId: "INV-2024-0042",
- },
+ merchantAccountNumber,
+ beneficiaryName: "James Kamanga",
+ connectorId,
+ beneficiaryAccountNumber:
+ process.env.ONEKHUSA_PAYOUT_ACCOUNT_NUMBER || "+265999876543",
+ transactionAmount: 2000,
+ transactionDescription: "Freelancer payment",
+ sourceReferenceNumber: `PAYOUT${Date.now()}`,
+ capturedBy,
});
if (isServiceError(disbursementResult)) {
- console.log(` Error creating disbursement: ${disbursementResult.errorMessage}`);
+ console.log(
+ ` Error creating disbursement: ${disbursementResult.errorMessage}`,
+ );
return;
}
- console.log(` Disbursement ID: ${disbursementResult.id}`);
- console.log(` Status: ${disbursementResult.status}`);
- console.log(` Amount: ${disbursementResult.amount} ${disbursementResult.currency}`);
- console.log(` Recipient: ${disbursementResult.recipient.name} (${disbursementResult.recipient.phone})`);
+ console.log(
+ ` Transaction reference: ${disbursementResult.transactionReferenceNumber}`,
+ );
+ console.log(` Response code: ${disbursementResult.responseCode}`);
console.log();
- // Now approve it to trigger the actual payout.
console.log(" Approving disbursement...\n");
- const approvalResult = await sdk.onekhusa.disbursements.approveSingle(
- disbursementResult.id,
- { comment: "Approved - verified invoice" },
- );
+ const approvalResult = await sdk.onekhusa.disbursements.approveSingle({
+ merchantAccountNumber,
+ transactionReferenceNumber: disbursementResult.transactionReferenceNumber,
+ actionedBy: capturedBy,
+ });
if (isServiceError(approvalResult)) {
console.log(` Error approving: ${approvalResult.errorMessage}`);
} else {
- console.log(` Disbursement ID: ${approvalResult.id}`);
- console.log(` Status: ${approvalResult.status}`);
- if (approvalResult.approvedAt) {
- console.log(` Approved at: ${approvalResult.approvedAt}`);
- }
+ console.log(` Response code: ${approvalResult.responseCode}`);
}
}
@@ -421,13 +409,23 @@ async function demoOneKhusa() {
async function main() {
console.log("=== Chia SDK - Direct Provider Access ===");
console.log();
- console.log("This example shows how to use each provider directly via the SDK.");
- console.log("For managed billing (recommended), see the platform-subscribe example.");
+ console.log(
+ "This example shows how to use each provider directly via the SDK.",
+ );
+ console.log(
+ "For managed billing (recommended), see the platform-subscribe example.",
+ );
console.log();
console.log("Providers configured:");
- console.log(` PayChangu: ${process.env.PAYCHANGU_SECRET_KEY ? "yes" : "no (set PAYCHANGU_SECRET_KEY)"}`);
- console.log(` PawaPay: ${process.env.PAWAPAY_JWT ? "yes" : "no (set PAWAPAY_JWT)"}`);
- console.log(` OneKhusa: ${process.env.ONEKHUSA_API_KEY ? "yes" : "no (set ONEKHUSA_API_KEY + API_SECRET + ORGANISATION_ID)"}`);
+ console.log(
+ ` PayChangu: ${process.env.PAYCHANGU_SECRET_KEY ? "yes" : "no (set PAYCHANGU_SECRET_KEY)"}`,
+ );
+ console.log(
+ ` PawaPay: ${process.env.PAWAPAY_JWT ? "yes" : "no (set PAWAPAY_JWT)"}`,
+ );
+ console.log(
+ ` OneKhusa: ${process.env.ONEKHUSA_API_KEY ? "yes" : "no (configure all required ONEKHUSA variables)"}`,
+ );
await demoPayChangu();
await demoPawaPay();
@@ -438,7 +436,7 @@ async function main() {
console.log("See the individual scripts for more detailed examples:");
console.log(" pnpm collect:paychangu pnpm payout:paychangu");
console.log(" pnpm collect:pawapay pnpm payout:pawapay");
- console.log(" pnpm collect:onekhusa");
+ console.log(" pnpm collect:onekhusa pnpm checkout:onekhusa");
console.log();
}
diff --git a/examples/sdk-direct/src/checkout-onekhusa.ts b/examples/sdk-direct/src/checkout-onekhusa.ts
new file mode 100644
index 0000000..183d276
--- /dev/null
+++ b/examples/sdk-direct/src/checkout-onekhusa.ts
@@ -0,0 +1,55 @@
+import { ChiaSDK, isServiceError } from "@chiahq/sdk";
+
+function requiredEnvironmentVariable(name: string): string {
+ const value = process.env[name];
+ if (!value) throw new Error(`${name} is required`);
+ return value;
+}
+
+async function main() {
+ const merchantAccountNumber = Number(
+ requiredEnvironmentVariable("ONEKHUSA_MERCHANT_ACCOUNT_NUMBER"),
+ );
+ const capturedBy = requiredEnvironmentVariable("ONEKHUSA_CAPTURED_BY");
+ const sdk = ChiaSDK.create({
+ onekhusa: {
+ apiKey: requiredEnvironmentVariable("ONEKHUSA_API_KEY"),
+ apiSecret: requiredEnvironmentVariable("ONEKHUSA_API_SECRET"),
+ organisationId: requiredEnvironmentVariable("ONEKHUSA_ORGANISATION_ID"),
+ merchantAccountNumber,
+ defaultCapturedBy: capturedBy,
+ environment: "DEVELOPMENT",
+ },
+ });
+
+ const result = await sdk.onekhusa.collections.initiateStandardCheckout({
+ payment: {
+ merchantAccountNumber,
+ amount: 150,
+ description: "Groceries purchase",
+ sourceReferenceNumber: `ORDER${Date.now()}`,
+ capturedBy,
+ },
+ route: {
+ successRedirectionUrl: requiredEnvironmentVariable(
+ "ONEKHUSA_SUCCESS_REDIRECTION_URL",
+ ),
+ failureRedirectionUrl: requiredEnvironmentVariable(
+ "ONEKHUSA_FAILURE_REDIRECTION_URL",
+ ),
+ },
+ });
+
+ if (isServiceError(result)) {
+ throw new Error(`${result.errorMessage} (${result.statusCode})`);
+ }
+
+ console.log(result.checkoutUrl);
+}
+
+main().catch((error: unknown) => {
+ console.error(
+ error instanceof Error ? error.message : "OneKhusa checkout failed",
+ );
+ process.exitCode = 1;
+});
diff --git a/examples/sdk-direct/src/collect-onekhusa.ts b/examples/sdk-direct/src/collect-onekhusa.ts
index 218b45b..3e01dc1 100644
--- a/examples/sdk-direct/src/collect-onekhusa.ts
+++ b/examples/sdk-direct/src/collect-onekhusa.ts
@@ -1,127 +1,56 @@
-/**
- * OneKhusa Collection Example
- *
- * Demonstrates how to collect money from a customer using OneKhusa.
- * OneKhusa uses a "request to pay" model - you send a payment request
- * to the customer's mobile money account and they approve it.
- *
- * OneKhusa operates primarily in Malawi (MWK) and supports Airtel Money
- * and TNM Mpamba via the MOBILE_MONEY payment method.
- */
-
import { ChiaSDK, isServiceError } from "@chiahq/sdk";
-import type { InitiateCollectionRequest } from "@chiahq/sdk/dist/services/onekhusa/types/collection";
-async function main() {
- console.log("=== OneKhusa Collection (Request to Pay) ===\n");
+function requiredEnvironmentVariable(name: string): string {
+ const value = process.env[name];
+ if (!value) throw new Error(`${name} is required`);
+ return value;
+}
- // Step 1: Initialize the SDK with OneKhusa credentials.
- // OneKhusa requires three credentials: API key, API secret, and organisation ID.
- // The SDK handles OAuth2 token management (client_credentials flow) automatically.
+async function main() {
+ const merchantAccountNumber = Number(
+ requiredEnvironmentVariable("ONEKHUSA_MERCHANT_ACCOUNT_NUMBER"),
+ );
+ const connectorId = Number(
+ requiredEnvironmentVariable("ONEKHUSA_CONNECTOR_ID"),
+ );
+ const capturedBy = requiredEnvironmentVariable("ONEKHUSA_CAPTURED_BY");
const sdk = ChiaSDK.create({
onekhusa: {
- apiKey: process.env.ONEKHUSA_API_KEY || "",
- apiSecret: process.env.ONEKHUSA_API_SECRET || "",
- organisationId: process.env.ONEKHUSA_ORGANISATION_ID || "",
+ apiKey: requiredEnvironmentVariable("ONEKHUSA_API_KEY"),
+ apiSecret: requiredEnvironmentVariable("ONEKHUSA_API_SECRET"),
+ organisationId: requiredEnvironmentVariable("ONEKHUSA_ORGANISATION_ID"),
+ merchantAccountNumber,
+ defaultCapturedBy: capturedBy,
environment: "DEVELOPMENT",
},
});
- // Step 2: Check if the OneKhusa service is available.
- // This hits the /health endpoint and confirms connectivity.
- console.log("Checking service status...\n");
-
const status = await sdk.onekhusa.checkStatus();
-
- if ("errorMessage" in status) {
- console.error("Status check failed:", status.errorMessage);
- return;
- }
-
- console.log(` Available: ${status.available}`);
- console.log(` Environment: ${status.environment}`);
- console.log();
-
- // Step 3: Initiate a request-to-pay.
- // This sends a USSD prompt to the customer's phone. They will see
- // a message asking them to confirm the payment.
- //
- // The request fields:
- // amount - numeric amount (not a string, unlike PayChangu/PawaPay)
- // currency - "MWK", "ZMW", "KES", etc.
- // phone - customer's phone number
- // paymentMethod - "MOBILE_MONEY" or "BANK_TRANSFER"
- // reference - your internal reference (optional but recommended)
- // description - human-readable description shown to customer
- // callbackUrl - webhook URL for status updates
- // metadata - key-value pairs for your own tracking
- console.log("Initiating request to pay...\n");
-
- const collectionResult = await sdk.onekhusa.collections.initiateRequestToPay({
- amount: 1000,
- currency: "MWK",
- phone: "+265888123456", // Airtel Malawi number
- paymentMethod: "MOBILE_MONEY",
- reference: `order-${Date.now()}`,
- description: "Payment for order #12345",
- callbackUrl: "https://your-server.com/webhooks/onekhusa",
- metadata: {
- orderId: "12345",
- customerId: "cust-001",
- },
+ if (!status.available)
+ throw new Error(status.error ?? "OneKhusa is unavailable");
+
+ const result = await sdk.onekhusa.collections.initiateDirectPushPayment({
+ merchantAccountNumber,
+ connectorId,
+ payerAccountNumber: requiredEnvironmentVariable(
+ "ONEKHUSA_PAYER_ACCOUNT_NUMBER",
+ ),
+ sourceReferenceNumber: `ORDER${Date.now()}`,
+ transactionDescription: "Payment for order 12345",
+ transactionAmount: 1000,
+ capturedBy,
});
- if (isServiceError(collectionResult)) {
- console.error("Collection failed:", collectionResult.errorMessage);
- console.error("Status code:", collectionResult.statusCode);
- return;
+ if (isServiceError(result)) {
+ throw new Error(`${result.errorMessage} (${result.statusCode})`);
}
- // The response includes:
- // id - OneKhusa's transaction ID (use this to check status later)
- // tan - Transaction Authorization Number (customer may need this)
- // status - initial status, typically "PENDING"
- console.log(` Transaction ID: ${collectionResult.id}`);
- console.log(` TAN: ${collectionResult.tan}`);
- console.log(` Status: ${collectionResult.status}`);
- console.log(` Amount: ${collectionResult.amount} ${collectionResult.currency}`);
- console.log(` Phone: ${collectionResult.phone}`);
- console.log(` Created: ${collectionResult.createdAt}`);
- if (collectionResult.expiresAt) {
- console.log(` Expires: ${collectionResult.expiresAt}`);
- }
- console.log();
-
- // Step 4: Check the transaction status.
- // Collection statuses: PENDING -> COMPLETED | FAILED | CANCELLED | EXPIRED
- //
- // In production, rely on the callbackUrl webhook for real-time updates.
- // Use getTransaction as a fallback or for manual verification.
- console.log("Checking transaction status...\n");
-
- const txResult = await sdk.onekhusa.collections.getTransaction(collectionResult.id);
-
- if (isServiceError(txResult)) {
- console.error("Status check failed:", txResult.errorMessage);
- return;
- }
-
- console.log(` Transaction ID: ${txResult.id}`);
- console.log(` Collection ID: ${txResult.collectionId}`);
- console.log(` Status: ${txResult.status}`);
- console.log(` Amount: ${txResult.amount} ${txResult.currency}`);
- if (txResult.fee !== undefined) {
- console.log(` Fee: ${txResult.fee} ${txResult.currency}`);
- }
- if (txResult.providerReference) {
- console.log(` Provider ref: ${txResult.providerReference}`);
- }
- if (txResult.failureReason) {
- console.log(` Failure reason: ${txResult.failureReason}`);
- }
- if (txResult.completedAt) {
- console.log(` Completed: ${txResult.completedAt}`);
- }
+ console.log(result);
}
-main().catch(console.error);
+main().catch((error: unknown) => {
+ console.error(
+ error instanceof Error ? error.message : "OneKhusa collection failed",
+ );
+ process.exitCode = 1;
+});
diff --git a/packages/mcp/.env.example b/packages/mcp/.env.example
index 524e5d4..e0beeb2 100644
--- a/packages/mcp/.env.example
+++ b/packages/mcp/.env.example
@@ -4,5 +4,11 @@ PAYCHANGU_SECRET_KEY=your-paychangu-secret-key
# PawaPay Configuration
PAWAPAY_JWT=your-pawapay-jwt-token
+ONEKHUSA_API_KEY=your-onekhusa-api-key
+ONEKHUSA_API_SECRET=your-onekhusa-api-secret
+ONEKHUSA_ORGANISATION_ID=your-organisation-id
+ONEKHUSA_MERCHANT_ACCOUNT_NUMBER=35253486
+ONEKHUSA_DEFAULT_CAPTURED_BY=payments@example.com
+
# Environment (DEVELOPMENT or PRODUCTION)
ENVIRONMENT=DEVELOPMENT
diff --git a/packages/mcp/README.md b/packages/mcp/README.md
index 5ad735a..bf840cd 100644
--- a/packages/mcp/README.md
+++ b/packages/mcp/README.md
@@ -21,11 +21,11 @@ separate [Chia platform](https://usechia.com) is for.
- **Unified** - Provider-agnostic payment and payout tools that route across whatever is configured (4 tools)
- **PayChangu** - Payment services in Malawi (12 tools)
- **PawaPay** - Mobile money payments across Sub-Saharan Africa (14 tools)
-- **OneKhusa** - Enterprise payments in Malawi & Southern Africa (18 tools)
+- **OneKhusa** - Enterprise payments in Malawi & Southern Africa (20 tools)
## Features
-- 48 comprehensive tools for payment operations
+- 50 comprehensive tools for payment operations
- Provider-agnostic payment and payout tools that pick a provider for you
- Support for deposits, payouts, refunds, and transfers
- Collections and disbursements with approval workflows
@@ -71,6 +71,8 @@ Add to your Claude Desktop config file:
"ONEKHUSA_API_KEY": "your-onekhusa-api-key",
"ONEKHUSA_API_SECRET": "your-onekhusa-api-secret",
"ONEKHUSA_ORGANISATION_ID": "your-organisation-id",
+ "ONEKHUSA_MERCHANT_ACCOUNT_NUMBER": "35253486",
+ "ONEKHUSA_DEFAULT_CAPTURED_BY": "payments@example.com",
"ENVIRONMENT": "DEVELOPMENT"
}
}
@@ -91,7 +93,7 @@ Try commands like:
- "What's my PawaPay wallet balance?"
- "Create a OneKhusa batch disbursement for January salaries"
-## Available Tools (48 Total)
+## Available Tools (50 Total)
### Unified Tools (4)
@@ -144,10 +146,12 @@ Provider-agnostic tools that route across whatever providers are configured, ins
- `pawapay_get_active_config` - Get active merchant configuration
- `pawapay_get_availability` - Get correspondent availability status
-### OneKhusa Tools (18)
+### OneKhusa Tools (20)
#### Collection Tools
- `onekhusa_initiate_request_to_pay` - Initiate a request-to-pay collection
+- `onekhusa_initiate_direct_push` - Start a direct mobile money USSD prompt
+- `onekhusa_initiate_standard_checkout` - Create a hosted checkout session and return its URL
- `onekhusa_get_collection_transactions` - Get paginated collection transactions
- `onekhusa_get_collection_transaction` - Get specific transaction details
@@ -217,7 +221,11 @@ Send a PawaPay payout of 50 UGX to phone number 256701234567
### OneKhusa Examples
```
-Request a payment of 5000 MWK from 265991234567 using OneKhusa
+Start a OneKhusa mobile money push for 5000 MWK to 265991234567
+```
+
+```
+Create a OneKhusa inline checkout for order ORDER12345
```
```
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index 6ef77fe..085491b 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -1,7 +1,7 @@
{
"name": "@chiahq/mcp",
"version": "0.0.4",
- "description": "Model Context Protocol server for Chia SDK - enabling AI assistants to interact with African payment providers (PayChangu & PawaPay)",
+ "description": "Model Context Protocol server for Chia SDK - enabling AI assistants to interact with African payment providers (PayChangu, PawaPay, and OneKhusa)",
"main": "dist/index.js",
"type": "module",
"bin": {
diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts
index 3ceb231..ea1deda 100644
--- a/packages/mcp/src/index.ts
+++ b/packages/mcp/src/index.ts
@@ -30,7 +30,10 @@ import { registerOneKhusaDisbursementTools } from "./tools/onekhusa/disbursement
import { registerOneKhusaConfigTools } from "./tools/onekhusa/config.js";
import { registerUnifiedTools } from "./tools/unified.js";
-import { validateEnvironment, sanitizeErrorMessage } from "./utils/validation.js";
+import {
+ validateEnvironment,
+ sanitizeErrorMessage,
+} from "./utils/validation.js";
// Environment configuration
const PAYCHANGU_SECRET_KEY = process.env.PAYCHANGU_SECRET_KEY;
@@ -38,12 +41,18 @@ const PAWAPAY_JWT = process.env.PAWAPAY_JWT;
const ONEKHUSA_API_KEY = process.env.ONEKHUSA_API_KEY;
const ONEKHUSA_API_SECRET = process.env.ONEKHUSA_API_SECRET;
const ONEKHUSA_ORGANISATION_ID = process.env.ONEKHUSA_ORGANISATION_ID;
+const ONEKHUSA_MERCHANT_ACCOUNT_NUMBER = Number(
+ process.env.ONEKHUSA_MERCHANT_ACCOUNT_NUMBER,
+);
+const ONEKHUSA_DEFAULT_CAPTURED_BY = process.env.ONEKHUSA_DEFAULT_CAPTURED_BY;
let ENVIRONMENT: "PRODUCTION" | "DEVELOPMENT";
try {
ENVIRONMENT = validateEnvironment(process.env.ENVIRONMENT || "DEVELOPMENT");
} catch {
- console.error("Invalid ENVIRONMENT value. Must be PRODUCTION or DEVELOPMENT.");
+ console.error(
+ "Invalid ENVIRONMENT value. Must be PRODUCTION or DEVELOPMENT.",
+ );
process.exit(1);
}
@@ -56,22 +65,30 @@ try {
? {
secretKey: PAYCHANGU_SECRET_KEY,
environment: ENVIRONMENT,
- }
+ }
: undefined,
pawapay: PAWAPAY_JWT
? {
jwt: PAWAPAY_JWT,
environment: ENVIRONMENT,
- }
- : undefined,
- onekhusa: ONEKHUSA_API_KEY && ONEKHUSA_API_SECRET && ONEKHUSA_ORGANISATION_ID
- ? {
- apiKey: ONEKHUSA_API_KEY,
- apiSecret: ONEKHUSA_API_SECRET,
- organisationId: ONEKHUSA_ORGANISATION_ID,
- environment: ENVIRONMENT,
- }
+ }
: undefined,
+ onekhusa:
+ ONEKHUSA_API_KEY &&
+ ONEKHUSA_API_SECRET &&
+ ONEKHUSA_ORGANISATION_ID &&
+ Number.isInteger(ONEKHUSA_MERCHANT_ACCOUNT_NUMBER) &&
+ ONEKHUSA_MERCHANT_ACCOUNT_NUMBER >= 10000000 &&
+ ONEKHUSA_MERCHANT_ACCOUNT_NUMBER <= 99999999
+ ? {
+ apiKey: ONEKHUSA_API_KEY,
+ apiSecret: ONEKHUSA_API_SECRET,
+ organisationId: ONEKHUSA_ORGANISATION_ID,
+ merchantAccountNumber: ONEKHUSA_MERCHANT_ACCOUNT_NUMBER,
+ defaultCapturedBy: ONEKHUSA_DEFAULT_CAPTURED_BY,
+ environment: ENVIRONMENT,
+ }
+ : undefined,
});
} catch (error) {
console.error("Failed to initialize Chia SDK:", sanitizeErrorMessage(error));
diff --git a/packages/mcp/src/tools/onekhusa/collections.ts b/packages/mcp/src/tools/onekhusa/collections.ts
index 305c5c7..d0f759e 100644
--- a/packages/mcp/src/tools/onekhusa/collections.ts
+++ b/packages/mcp/src/tools/onekhusa/collections.ts
@@ -1,124 +1,240 @@
import type { OneKhusa } from "@chiahq/sdk";
import type { ToolRegistrationFunction } from "../../types/index.js";
-import { validateEnum, validateEnumOptional, validatePhone, validateUrlOptional } from "../../utils/validation.js";
+import { validateUrlOptional } from "../../utils/validation.js";
-const CURRENCIES = ["MWK", "USD", "ZAR", "ZMW", "TZS", "KES", "UGX"] as const;
-const PAYMENT_METHODS = ["MOBILE_MONEY", "BANK_TRANSFER"] as const;
-const COLLECTION_STATUSES = ["PENDING", "COMPLETED", "FAILED", "CANCELLED", "EXPIRED"] as const;
+const COLLECTION_SEARCH_FIELDS = [
+ "TransactionReferenceNumber",
+ "SourceCustomerName",
+ "Connector.ConnectorName",
+ "SourceAccountNumber",
+] as const;
+
+function getCapturedBy(
+ args: Record,
+ onekhusa: OneKhusa,
+): string {
+ const capturedBy =
+ (typeof args.capturedBy === "string" && args.capturedBy) ||
+ onekhusa.defaultCapturedBy;
+ if (!capturedBy) {
+ throw new Error(
+ "capturedBy is required unless ONEKHUSA_DEFAULT_CAPTURED_BY is configured",
+ );
+ }
+ return capturedBy;
+}
+
+function getRequiredUrl(args: Record, key: string): string {
+ const url = validateUrlOptional(args[key], key);
+ if (!url) throw new Error(`${key} is required`);
+ return url;
+}
export function registerOneKhusaCollectionTools(
registerTool: ToolRegistrationFunction,
- onekhusa: OneKhusa
+ onekhusa: OneKhusa,
) {
registerTool(
"onekhusa_initiate_request_to_pay",
- "Initiate a request-to-pay (collection) to receive money from a customer. Returns a TAN that the customer uses to authorize payment.",
+ "Reserve a timed account number for the customer to pay into. Final settlement is reported by webhook.",
+ {
+ type: "object",
+ properties: {
+ amount: { type: "number", description: "Amount to collect" },
+ description: {
+ type: "string",
+ description: "Payment description",
+ },
+ referenceNumber: {
+ type: "string",
+ description: "Unique 5 to 25 character merchant reference",
+ },
+ capturedBy: {
+ type: "string",
+ description:
+ "Operator email, or configure ONEKHUSA_DEFAULT_CAPTURED_BY",
+ },
+ },
+ required: ["amount", "description", "referenceNumber"],
+ },
+ async (args) =>
+ onekhusa.collections.initiateRequestToPay({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionAmount: args.amount as number,
+ transactionDescription: args.description as string,
+ referenceNumber: args.referenceNumber as string,
+ capturedBy: getCapturedBy(args, onekhusa),
+ }),
+ );
+
+ registerTool(
+ "onekhusa_initiate_direct_push",
+ "Start a OneKhusa USSD prompt on a customer's mobile money wallet. The response acknowledges initiation; use webhooks for the final outcome.",
{
type: "object",
properties: {
- amount: {
+ connectorId: {
type: "number",
- description: "Amount to collect",
+ description: "OneKhusa MNO connector ID",
},
- currency: {
+ payerAccountNumber: {
type: "string",
- description: "Currency code (MWK, USD, ZAR, ZMW, TZS, KES, UGX)",
- enum: ["MWK", "USD", "ZAR", "ZMW", "TZS", "KES", "UGX"],
+ description: "Customer mobile money account number",
},
- phone: {
+ sourceReferenceNumber: {
type: "string",
- description: "Customer phone number in international format",
+ description: "Unique 5 to 25 character merchant reference",
},
- paymentMethod: {
+ transactionDescription: {
type: "string",
- description: "Payment method",
- enum: ["MOBILE_MONEY", "BANK_TRANSFER"],
+ description: "Description shown for the payment",
+ },
+ transactionAmount: {
+ type: "number",
+ description: "Amount to collect",
},
- reference: {
+ capturedBy: {
type: "string",
- description: "Optional reference for the collection",
+ description:
+ "Operator email, or configure ONEKHUSA_DEFAULT_CAPTURED_BY",
},
+ },
+ required: [
+ "connectorId",
+ "payerAccountNumber",
+ "sourceReferenceNumber",
+ "transactionDescription",
+ "transactionAmount",
+ ],
+ },
+ async (args) =>
+ onekhusa.collections.initiateDirectPushPayment({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ connectorId: args.connectorId as number,
+ payerAccountNumber: args.payerAccountNumber as string,
+ sourceReferenceNumber: args.sourceReferenceNumber as string,
+ transactionDescription: args.transactionDescription as string,
+ transactionAmount: args.transactionAmount as number,
+ capturedBy: getCapturedBy(args, onekhusa),
+ }),
+ );
+
+ registerTool(
+ "onekhusa_initiate_standard_checkout",
+ "Create a OneKhusa hosted checkout session for card or mobile money and return its checkout URL for modal or redirect use.",
+ {
+ type: "object",
+ properties: {
+ amount: { type: "number", description: "Amount to collect" },
description: {
type: "string",
- description: "Optional description",
+ description: "Payment description",
+ },
+ sourceReferenceNumber: {
+ type: "string",
+ description: "Unique merchant reference",
},
- callbackUrl: {
+ successRedirectionUrl: {
type: "string",
- description: "Optional callback URL for status updates",
+ description: "URL to return to after successful checkout",
+ },
+ failureRedirectionUrl: {
+ type: "string",
+ description: "URL to return to after failed checkout",
+ },
+ capturedBy: {
+ type: "string",
+ description:
+ "Operator email, or configure ONEKHUSA_DEFAULT_CAPTURED_BY",
},
},
- required: ["amount", "currency", "phone", "paymentMethod"],
+ required: [
+ "amount",
+ "description",
+ "sourceReferenceNumber",
+ "successRedirectionUrl",
+ "failureRedirectionUrl",
+ ],
},
- async (args) => {
- validatePhone(args.phone, "phone");
- const callbackUrl = validateUrlOptional(args.callbackUrl, "callbackUrl");
- return await onekhusa.collections.initiateRequestToPay({
- amount: args.amount as number,
- currency: validateEnum(args.currency, CURRENCIES, "currency"),
- phone: args.phone as string,
- paymentMethod: validateEnum(args.paymentMethod, PAYMENT_METHODS, "paymentMethod"),
- reference: args.reference as string | undefined,
- description: args.description as string | undefined,
- callbackUrl,
- });
- }
+ async (args) =>
+ onekhusa.collections.initiateStandardCheckout({
+ payment: {
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ amount: args.amount as number,
+ description: args.description as string,
+ sourceReferenceNumber: args.sourceReferenceNumber as string,
+ capturedBy: getCapturedBy(args, onekhusa),
+ },
+ route: {
+ successRedirectionUrl: getRequiredUrl(args, "successRedirectionUrl"),
+ failureRedirectionUrl: getRequiredUrl(args, "failureRedirectionUrl"),
+ },
+ }),
);
registerTool(
"onekhusa_get_collection_transactions",
- "Get a paginated list of collection transactions with optional filters.",
+ "Get a page of OneKhusa collection transactions for a date, with an optional search filter.",
{
type: "object",
properties: {
- page: {
- type: "number",
- description: "Page number (0-indexed)",
+ transactionDate: {
+ type: "string",
+ description: "Transaction date in YYYY-MM-DD format",
},
- size: {
+ pageNumber: { type: "number", description: "Page number" },
+ numberOfReturnedRows: {
type: "number",
- description: "Number of items per page",
+ description: "Maximum rows to return",
},
- status: {
- type: "string",
- description: "Filter by status",
- enum: ["PENDING", "COMPLETED", "FAILED", "CANCELLED", "EXPIRED"],
- },
- startDate: {
- type: "string",
- description: "Filter by start date (ISO format)",
+ isIncremental: {
+ type: "boolean",
+ description: "Return incremental results",
},
- endDate: {
+ searchBy: {
type: "string",
- description: "Filter by end date (ISO format)",
+ enum: [...COLLECTION_SEARCH_FIELDS],
+ description: "Field to search",
},
+ searchText: { type: "string", description: "Search value" },
},
+ required: [
+ "transactionDate",
+ "pageNumber",
+ "numberOfReturnedRows",
+ "isIncremental",
+ "searchBy",
+ ],
},
- async (args) => {
- return await onekhusa.collections.getTransactions({
- page: args.page as number | undefined,
- size: args.size as number | undefined,
- status: validateEnumOptional(args.status, COLLECTION_STATUSES, "status"),
- startDate: args.startDate as string | undefined,
- endDate: args.endDate as string | undefined,
- });
- }
+ async (args) =>
+ onekhusa.collections.getTransactions({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionDate: args.transactionDate as string,
+ pageNumber: args.pageNumber as number,
+ numberOfReturnedRows: args.numberOfReturnedRows as number,
+ isIncremental: args.isIncremental as boolean,
+ searchBy: args.searchBy as (typeof COLLECTION_SEARCH_FIELDS)[number],
+ searchText: args.searchText as string | undefined,
+ }),
);
registerTool(
"onekhusa_get_collection_transaction",
- "Get details of a specific collection transaction by ID.",
+ "Get a specific collection transaction by its OneKhusa transaction reference.",
{
type: "object",
properties: {
transactionId: {
type: "string",
- description: "The collection transaction ID",
+ description: "The OneKhusa transaction reference number",
},
},
required: ["transactionId"],
},
- async (args) => {
- return await onekhusa.collections.getTransaction(args.transactionId as string);
- }
+ async (args) =>
+ onekhusa.collections.getTransaction({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: args.transactionId as string,
+ }),
);
}
diff --git a/packages/mcp/src/tools/onekhusa/disbursements.ts b/packages/mcp/src/tools/onekhusa/disbursements.ts
index 35e51c1..1307a48 100644
--- a/packages/mcp/src/tools/onekhusa/disbursements.ts
+++ b/packages/mcp/src/tools/onekhusa/disbursements.ts
@@ -1,452 +1,381 @@
import type { OneKhusa } from "@chiahq/sdk";
import type { ToolRegistrationFunction } from "../../types/index.js";
-import { validateEnum, validateEnumOptional, validatePhone, validateUrlOptional, validateArrayLength } from "../../utils/validation.js";
+import { validateArrayLength } from "../../utils/validation.js";
-const CURRENCIES = ["MWK", "USD", "ZAR", "ZMW", "TZS", "KES", "UGX"] as const;
-const PAYMENT_METHODS = ["MOBILE_MONEY", "BANK_TRANSFER"] as const;
-const BATCH_STATUSES = ["PENDING", "APPROVED", "REVIEWED", "REJECTED", "CANCELLED", "PROCESSING", "COMPLETED", "FAILED"] as const;
+function getCapturedBy(
+ args: Record,
+ onekhusa: OneKhusa,
+): string {
+ const capturedBy =
+ (typeof args.capturedBy === "string" && args.capturedBy) ||
+ onekhusa.defaultCapturedBy;
+ if (!capturedBy) {
+ throw new Error(
+ "capturedBy is required unless ONEKHUSA_DEFAULT_CAPTURED_BY is configured",
+ );
+ }
+ return capturedBy;
+}
+
+function getActor(
+ args: Record,
+ key: string,
+ onekhusa: OneKhusa,
+): string {
+ const actor =
+ (typeof args[key] === "string" && args[key]) || onekhusa.defaultCapturedBy;
+ if (!actor) {
+ throw new Error(
+ `${key} is required unless ONEKHUSA_DEFAULT_CAPTURED_BY is configured`,
+ );
+ }
+ return actor;
+}
export function registerOneKhusaDisbursementTools(
registerTool: ToolRegistrationFunction,
- onekhusa: OneKhusa
+ onekhusa: OneKhusa,
) {
registerTool(
"onekhusa_add_single_disbursement",
- "Create a single disbursement (payout) to send money to a recipient.",
+ "Create a single OneKhusa disbursement for a beneficiary account.",
{
type: "object",
properties: {
- amount: {
- type: "number",
- description: "Amount to disburse",
- },
- currency: {
- type: "string",
- description: "Currency code",
- enum: ["MWK", "USD", "ZAR", "ZMW", "TZS", "KES", "UGX"],
- },
- recipientName: {
- type: "string",
- description: "Recipient's name",
- },
- recipientPhone: {
- type: "string",
- description: "Recipient's phone number in international format",
- },
- recipientEmail: {
- type: "string",
- description: "Recipient's email address (optional)",
- },
- paymentMethod: {
- type: "string",
- description: "Payment method",
- enum: ["MOBILE_MONEY", "BANK_TRANSFER"],
- },
- reference: {
- type: "string",
- description: "Optional reference",
- },
- description: {
- type: "string",
- description: "Optional description",
- },
- callbackUrl: {
- type: "string",
- description: "Optional callback URL for status updates",
- },
+ beneficiaryName: { type: "string" },
+ connectorId: { type: "number" },
+ beneficiaryAccountNumber: { type: "string" },
+ transactionDescription: { type: "string" },
+ transactionAmount: { type: "number" },
+ sourceReferenceNumber: { type: "string" },
+ capturedBy: { type: "string" },
},
- required: ["amount", "currency", "recipientName", "recipientPhone", "paymentMethod"],
+ required: ["beneficiaryName", "connectorId", "beneficiaryAccountNumber"],
},
- async (args) => {
- validatePhone(args.recipientPhone, "recipientPhone");
- const callbackUrl = validateUrlOptional(args.callbackUrl, "callbackUrl");
- return await onekhusa.disbursements.addSingle({
- amount: args.amount as number,
- currency: validateEnum(args.currency, CURRENCIES, "currency"),
- recipient: {
- name: args.recipientName as string,
- phone: args.recipientPhone as string,
- email: args.recipientEmail as string | undefined,
- },
- paymentMethod: validateEnum(args.paymentMethod, PAYMENT_METHODS, "paymentMethod"),
- reference: args.reference as string | undefined,
- description: args.description as string | undefined,
- callbackUrl,
- });
- }
+ async (args) =>
+ onekhusa.disbursements.addSingle({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ beneficiaryName: args.beneficiaryName as string,
+ connectorId: args.connectorId as number,
+ beneficiaryAccountNumber: args.beneficiaryAccountNumber as string,
+ transactionDescription: args.transactionDescription as
+ | string
+ | undefined,
+ transactionAmount: args.transactionAmount as number | undefined,
+ sourceReferenceNumber: args.sourceReferenceNumber as string | undefined,
+ capturedBy: getCapturedBy(args, onekhusa),
+ }),
);
registerTool(
"onekhusa_approve_single_disbursement",
- "Approve a single disbursement that is pending approval.",
+ "Approve a single OneKhusa disbursement by transaction reference number.",
{
type: "object",
properties: {
- disbursementId: {
- type: "string",
- description: "The disbursement ID to approve",
- },
- comment: {
- type: "string",
- description: "Optional approval comment",
- },
+ transactionReferenceNumber: { type: "string" },
+ actionedBy: { type: "string" },
},
- required: ["disbursementId"],
+ required: ["transactionReferenceNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.approveSingle(
- args.disbursementId as string,
- args.comment ? { comment: args.comment as string } : undefined
- );
- }
+ async (args) =>
+ onekhusa.disbursements.approveSingle({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: args.transactionReferenceNumber as string,
+ actionedBy: getActor(args, "actionedBy", onekhusa),
+ }),
);
registerTool(
"onekhusa_review_single_disbursement",
- "Mark a single disbursement as reviewed.",
+ "Mark a single OneKhusa disbursement as reviewed.",
{
type: "object",
properties: {
- disbursementId: {
- type: "string",
- description: "The disbursement ID to review",
- },
- comment: {
- type: "string",
- description: "Optional review comment",
- },
+ transactionReferenceNumber: { type: "string" },
+ actionedBy: { type: "string" },
},
- required: ["disbursementId"],
+ required: ["transactionReferenceNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.reviewSingle(
- args.disbursementId as string,
- args.comment ? { comment: args.comment as string } : undefined
- );
- }
+ async (args) =>
+ onekhusa.disbursements.reviewSingle({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: args.transactionReferenceNumber as string,
+ actionedBy: getActor(args, "actionedBy", onekhusa),
+ }),
);
registerTool(
"onekhusa_reject_single_disbursement",
- "Reject a single disbursement.",
+ "Reject a single OneKhusa disbursement.",
{
type: "object",
properties: {
- disbursementId: {
- type: "string",
- description: "The disbursement ID to reject",
- },
- reason: {
- type: "string",
- description: "Reason for rejection",
- },
+ transactionReferenceNumber: { type: "string" },
+ reasonForRejection: { type: "string" },
+ rejectedBy: { type: "string" },
},
- required: ["disbursementId", "reason"],
+ required: ["transactionReferenceNumber", "reasonForRejection"],
},
- async (args) => {
- return await onekhusa.disbursements.rejectSingle(args.disbursementId as string, {
- reason: args.reason as string,
- });
- }
+ async (args) =>
+ onekhusa.disbursements.rejectSingle({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: args.transactionReferenceNumber as string,
+ reasonForRejection: args.reasonForRejection as string,
+ rejectedBy: getActor(args, "rejectedBy", onekhusa),
+ }),
);
registerTool(
"onekhusa_get_single_disbursement",
- "Get details of a specific single disbursement by ID.",
+ "Get a single OneKhusa disbursement by transaction reference number.",
{
type: "object",
properties: {
- disbursementId: {
- type: "string",
- description: "The disbursement ID",
- },
+ transactionReferenceNumber: { type: "string" },
},
- required: ["disbursementId"],
+ required: ["transactionReferenceNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.getSingle(args.disbursementId as string);
- }
+ async (args) =>
+ onekhusa.disbursements.getSingleTransaction({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: args.transactionReferenceNumber as string,
+ }),
);
registerTool(
"onekhusa_add_batch_disbursement",
- "Create a batch disbursement with multiple recipients.",
+ "Create a OneKhusa batch disbursement from transaction entries.",
{
type: "object",
properties: {
- name: {
- type: "string",
- description: "Batch name",
- },
- description: {
- type: "string",
- description: "Batch description (optional)",
- },
- items: {
+ isBatchScheduled: { type: "boolean" },
+ capturedBy: { type: "string" },
+ scheduledDate: { type: "string" },
+ transactions: {
type: "array",
- description: "Array of disbursement items",
items: {
type: "object",
properties: {
- amount: { type: "number" },
- currency: { type: "string" },
- recipientName: { type: "string" },
- recipientPhone: { type: "string" },
- paymentMethod: { type: "string" },
- reference: { type: "string" },
- description: { type: "string" },
+ beneficiaryName: { type: "string" },
+ connectorId: { type: "number" },
+ beneficiaryAccountNumber: { type: "string" },
+ transactionDescription: { type: "string" },
+ transactionAmount: { type: "number" },
+ sourceReferenceNumber: { type: "string" },
},
- required: ["amount", "currency", "recipientName", "recipientPhone", "paymentMethod"],
+ required: [
+ "beneficiaryName",
+ "connectorId",
+ "beneficiaryAccountNumber",
+ "transactionDescription",
+ "transactionAmount",
+ "sourceReferenceNumber",
+ ],
},
},
- callbackUrl: {
- type: "string",
- description: "Optional callback URL for status updates",
- },
},
- required: ["name", "items"],
+ required: ["isBatchScheduled", "transactions"],
},
async (args) => {
- const rawItems = validateArrayLength(args.items, "items", 100) as Record[];
- const callbackUrl = validateUrlOptional(args.callbackUrl, "callbackUrl");
- const items = rawItems.map((item) => {
- validatePhone(item.recipientPhone, "recipientPhone");
- return {
- amount: item.amount as number,
- currency: validateEnum(item.currency, CURRENCIES, "currency"),
- recipient: {
- name: item.recipientName as string,
- phone: item.recipientPhone as string,
- },
- paymentMethod: validateEnum(item.paymentMethod, PAYMENT_METHODS, "paymentMethod"),
- reference: item.reference as string | undefined,
- description: item.description as string | undefined,
- };
- });
-
- return await onekhusa.disbursements.addBatch({
- name: args.name as string,
- description: args.description as string | undefined,
- items,
- callbackUrl,
+ const transactions = validateArrayLength(
+ args.transactions,
+ "transactions",
+ 100,
+ ) as Record[];
+ return onekhusa.disbursements.addBatchJson({
+ header: {
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ isBatchScheduled: args.isBatchScheduled as boolean,
+ capturedBy: getCapturedBy(args, onekhusa),
+ scheduledDate: args.scheduledDate as string | undefined,
+ },
+ transactions: transactions.map((transaction) => ({
+ beneficiaryName: transaction.beneficiaryName as string,
+ connectorId: transaction.connectorId as number,
+ beneficiaryAccountNumber:
+ transaction.beneficiaryAccountNumber as string,
+ transactionDescription: transaction.transactionDescription as string,
+ transactionAmount: transaction.transactionAmount as number,
+ sourceReferenceNumber: transaction.sourceReferenceNumber as string,
+ })),
});
- }
+ },
);
registerTool(
"onekhusa_approve_batch",
- "Approve a batch disbursement.",
+ "Approve a OneKhusa disbursement batch.",
{
type: "object",
properties: {
- batchId: {
- type: "string",
- description: "The batch ID to approve",
- },
- comment: {
- type: "string",
- description: "Optional approval comment",
- },
+ batchNumber: { type: "number" },
+ actionedBy: { type: "string" },
},
- required: ["batchId"],
+ required: ["batchNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.approveBatch(
- args.batchId as string,
- args.comment ? { comment: args.comment as string } : undefined
- );
- }
+ async (args) =>
+ onekhusa.disbursements.approveBatch({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ batchNumber: args.batchNumber as number,
+ actionedBy: getActor(args, "actionedBy", onekhusa),
+ }),
);
registerTool(
"onekhusa_review_batch",
- "Mark a batch disbursement as reviewed.",
+ "Mark a OneKhusa disbursement batch as reviewed.",
{
type: "object",
properties: {
- batchId: {
- type: "string",
- description: "The batch ID to review",
- },
- comment: {
- type: "string",
- description: "Optional review comment",
- },
+ batchNumber: { type: "number" },
+ actionedBy: { type: "string" },
},
- required: ["batchId"],
+ required: ["batchNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.reviewBatch(
- args.batchId as string,
- args.comment ? { comment: args.comment as string } : undefined
- );
- }
+ async (args) =>
+ onekhusa.disbursements.reviewBatch({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ batchNumber: args.batchNumber as number,
+ actionedBy: getActor(args, "actionedBy", onekhusa),
+ }),
);
registerTool(
"onekhusa_reject_batch",
- "Reject a batch disbursement.",
+ "Reject a OneKhusa disbursement batch.",
{
type: "object",
properties: {
- batchId: {
- type: "string",
- description: "The batch ID to reject",
- },
- reason: {
- type: "string",
- description: "Reason for rejection",
- },
+ reasonForRejection: { type: "string" },
+ rejectedBy: { type: "string" },
+ headerId: { type: "number" },
},
- required: ["batchId", "reason"],
+ required: ["reasonForRejection"],
},
- async (args) => {
- return await onekhusa.disbursements.rejectBatch(args.batchId as string, {
- reason: args.reason as string,
- });
- }
+ async (args) =>
+ onekhusa.disbursements.rejectBatch({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ reasonForRejection: args.reasonForRejection as string,
+ rejectedBy: getActor(args, "rejectedBy", onekhusa),
+ headerId: args.headerId as number | undefined,
+ }),
);
registerTool(
"onekhusa_cancel_batch",
- "Cancel a batch disbursement.",
+ "Cancel a pending OneKhusa disbursement batch.",
{
type: "object",
properties: {
- batchId: {
- type: "string",
- description: "The batch ID to cancel",
- },
- reason: {
- type: "string",
- description: "Optional cancellation reason",
- },
+ cancelledBy: { type: "string" },
+ batchNumber: { type: "number" },
},
- required: ["batchId"],
+ required: [],
},
- async (args) => {
- return await onekhusa.disbursements.cancelBatch(
- args.batchId as string,
- args.reason ? { reason: args.reason as string } : undefined
- );
- }
+ async (args) =>
+ onekhusa.disbursements.cancelBatch({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ cancelledBy: getActor(args, "cancelledBy", onekhusa),
+ batchNumber: args.batchNumber as number | undefined,
+ }),
);
registerTool(
"onekhusa_transfer_batch_funds",
- "Transfer funds for an approved batch disbursement.",
+ "Transfer funds for an approved OneKhusa disbursement batch.",
{
type: "object",
properties: {
- batchId: {
- type: "string",
- description: "The batch ID to transfer funds for",
- },
- sourceAccount: {
- type: "string",
- description: "Optional source account identifier",
- },
+ batchNumber: { type: "number" },
+ transferredBy: { type: "string" },
},
- required: ["batchId"],
+ required: ["batchNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.transferBatchFunds(
- args.batchId as string,
- args.sourceAccount ? { sourceAccount: args.sourceAccount as string } : undefined
- );
- }
+ async (args) =>
+ onekhusa.disbursements.transferBatchFunds({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ batchNumber: args.batchNumber as number,
+ transferredBy: getActor(args, "transferredBy", onekhusa),
+ }),
);
registerTool(
"onekhusa_get_batches",
- "Get a paginated list of batch disbursements with optional filters.",
+ "Get OneKhusa disbursement batches for a date and page.",
{
type: "object",
properties: {
- page: {
- type: "number",
- description: "Page number (0-indexed)",
- },
- size: {
- type: "number",
- description: "Number of items per page",
- },
- status: {
- type: "string",
- description: "Filter by status",
- enum: ["PENDING", "APPROVED", "REVIEWED", "REJECTED", "CANCELLED", "PROCESSING", "COMPLETED", "FAILED"],
- },
- startDate: {
- type: "string",
- description: "Filter by start date (ISO format)",
- },
- endDate: {
- type: "string",
- description: "Filter by end date (ISO format)",
- },
+ transactionDate: { type: "string" },
+ pageNumber: { type: "number" },
+ numberOfReturnedRows: { type: "number" },
+ isIncremental: { type: "boolean" },
+ searchBy: { type: "string" },
+ searchText: { type: "string" },
},
+ required: [
+ "transactionDate",
+ "pageNumber",
+ "numberOfReturnedRows",
+ "isIncremental",
+ ],
},
- async (args) => {
- return await onekhusa.disbursements.getBatches({
- page: args.page as number | undefined,
- size: args.size as number | undefined,
- status: validateEnumOptional(args.status, BATCH_STATUSES, "status"),
- startDate: args.startDate as string | undefined,
- endDate: args.endDate as string | undefined,
- });
- }
+ async (args) =>
+ onekhusa.disbursements.getBatches({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ transactionDate: args.transactionDate as string,
+ pageNumber: args.pageNumber as number,
+ numberOfReturnedRows: args.numberOfReturnedRows as number,
+ isIncremental: args.isIncremental as boolean,
+ searchBy: args.searchBy as string | undefined,
+ searchText: args.searchText as string | undefined,
+ }),
);
registerTool(
"onekhusa_get_batch",
- "Get details of a specific batch disbursement by ID.",
+ "Get a OneKhusa disbursement batch by batch number.",
{
type: "object",
- properties: {
- batchId: {
- type: "string",
- description: "The batch ID",
- },
- },
- required: ["batchId"],
+ properties: { batchNumber: { type: "number" } },
+ required: ["batchNumber"],
},
- async (args) => {
- return await onekhusa.disbursements.getBatch(args.batchId as string);
- }
+ async (args) =>
+ onekhusa.disbursements.getBatch({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ batchNumber: args.batchNumber as number,
+ }),
);
registerTool(
"onekhusa_get_batch_transactions",
- "Get transactions within a specific batch.",
+ "List transactions in a OneKhusa disbursement batch.",
{
type: "object",
properties: {
- batchId: {
- type: "string",
- description: "The batch ID",
- },
- page: {
- type: "number",
- description: "Page number (0-indexed)",
- },
- size: {
- type: "number",
- description: "Number of items per page",
- },
- status: {
- type: "string",
- description: "Filter by status",
- enum: ["PENDING", "APPROVED", "REVIEWED", "REJECTED", "CANCELLED", "PROCESSING", "COMPLETED", "FAILED"],
- },
+ batchNumber: { type: "number" },
+ pageNumber: { type: "number" },
+ numberOfReturnedRows: { type: "number" },
+ isIncremental: { type: "boolean" },
+ searchBy: { type: "string" },
+ searchText: { type: "string" },
},
- required: ["batchId"],
+ required: [
+ "batchNumber",
+ "pageNumber",
+ "numberOfReturnedRows",
+ "isIncremental",
+ ],
},
- async (args) => {
- return await onekhusa.disbursements.getBatchTransactions(args.batchId as string, {
- page: args.page as number | undefined,
- size: args.size as number | undefined,
- status: validateEnumOptional(args.status, BATCH_STATUSES, "status"),
- });
- }
+ async (args) =>
+ onekhusa.disbursements.getBatchTransactions({
+ merchantAccountNumber: onekhusa.merchantAccountNumber,
+ batchNumber: args.batchNumber as number,
+ pageNumber: args.pageNumber as number,
+ numberOfReturnedRows: args.numberOfReturnedRows as number,
+ isIncremental: args.isIncremental as boolean,
+ searchBy: args.searchBy as string | undefined,
+ searchText: args.searchText as string | undefined,
+ }),
);
}
diff --git a/packages/mcp/src/types/index.ts b/packages/mcp/src/types/index.ts
index b0aaa0d..76244b7 100644
--- a/packages/mcp/src/types/index.ts
+++ b/packages/mcp/src/types/index.ts
@@ -2,7 +2,11 @@
* Type definitions for Chia MCP Server
*/
-import type { PayChangu as PayChanguSDK, PawaPayTypes, OneKhusaTypes } from "@chiahq/sdk";
+import type {
+ PayChangu as PayChanguSDK,
+ PawaPayTypes,
+ OneKhusaTypes,
+} from "@chiahq/sdk";
/**
* JSON Schema type for tool input validation
@@ -32,7 +36,7 @@ export type ToolRegistrationFunction = (
name: string,
description: string,
inputSchema: JSONSchema,
- handler: ToolHandler
+ handler: ToolHandler,
) => void;
/**
@@ -214,20 +218,36 @@ export namespace PawapayToolArgs {
export namespace OneKhusaToolArgs {
export interface InitiateRequestToPay {
amount: number;
- currency: string;
- phone: string;
- paymentMethod: string;
- reference?: string;
- description?: string;
- callbackUrl?: string;
+ description: string;
+ referenceNumber: string;
+ capturedBy?: string;
+ }
+
+ export interface InitiateDirectPush {
+ connectorId: number;
+ payerAccountNumber: string;
+ sourceReferenceNumber: string;
+ transactionDescription: string;
+ transactionAmount: number;
+ capturedBy?: string;
+ }
+
+ export interface InitiateStandardCheckout {
+ amount: number;
+ description: string;
+ sourceReferenceNumber: string;
+ successRedirectionUrl: string;
+ failureRedirectionUrl: string;
+ capturedBy?: string;
}
export interface GetCollectionTransactions {
- page?: number;
- size?: number;
- status?: string;
- startDate?: string;
- endDate?: string;
+ transactionDate: string;
+ pageNumber: number;
+ numberOfReturnedRows: number;
+ isIncremental: boolean;
+ searchBy: OneKhusaTypes.CollectionSearchBy;
+ searchText?: string;
}
export interface GetCollectionTransaction {
@@ -235,95 +255,97 @@ export namespace OneKhusaToolArgs {
}
export interface AddSingleDisbursement {
- amount: number;
- currency: string;
- recipientName: string;
- recipientPhone: string;
- recipientEmail?: string;
- paymentMethod: string;
- reference?: string;
- description?: string;
- callbackUrl?: string;
+ beneficiaryName: string;
+ connectorId: number;
+ beneficiaryAccountNumber: string;
+ transactionDescription?: string;
+ transactionAmount?: number;
+ sourceReferenceNumber?: string;
+ capturedBy?: string;
}
export interface ApproveSingleDisbursement {
- disbursementId: string;
- comment?: string;
+ transactionReferenceNumber: string;
+ actionedBy?: string;
}
export interface ReviewSingleDisbursement {
- disbursementId: string;
- comment?: string;
+ transactionReferenceNumber: string;
+ actionedBy?: string;
}
export interface RejectSingleDisbursement {
- disbursementId: string;
- reason: string;
+ transactionReferenceNumber: string;
+ reasonForRejection: string;
+ rejectedBy?: string;
}
export interface GetSingleDisbursement {
- disbursementId: string;
+ transactionReferenceNumber: string;
}
export interface BatchDisbursementItem {
- amount: number;
- currency: string;
- recipientName: string;
- recipientPhone: string;
- paymentMethod: string;
- reference?: string;
- description?: string;
+ beneficiaryName: string;
+ connectorId: number;
+ beneficiaryAccountNumber: string;
+ transactionDescription: string;
+ transactionAmount: number;
+ sourceReferenceNumber: string;
}
export interface AddBatchDisbursement {
- name: string;
- description?: string;
- items: BatchDisbursementItem[];
- callbackUrl?: string;
+ isBatchScheduled: boolean;
+ capturedBy?: string;
+ scheduledDate?: string;
+ transactions: BatchDisbursementItem[];
}
export interface ApproveBatch {
- batchId: string;
- comment?: string;
+ batchNumber: number;
+ actionedBy?: string;
}
export interface ReviewBatch {
- batchId: string;
- comment?: string;
+ batchNumber: number;
+ actionedBy?: string;
}
export interface RejectBatch {
- batchId: string;
- reason: string;
+ reasonForRejection: string;
+ rejectedBy?: string;
+ headerId?: number;
}
export interface CancelBatch {
- batchId: string;
- reason?: string;
+ cancelledBy?: string;
+ batchNumber?: number;
}
export interface TransferBatchFunds {
- batchId: string;
- sourceAccount?: string;
+ batchNumber: number;
+ transferredBy?: string;
}
export interface GetBatches {
- page?: number;
- size?: number;
- status?: string;
- startDate?: string;
- endDate?: string;
+ transactionDate: string;
+ pageNumber: number;
+ numberOfReturnedRows: number;
+ isIncremental: boolean;
+ searchBy?: string;
+ searchText?: string;
}
export interface GetBatch {
- batchId: string;
+ batchNumber: number;
}
export interface GetBatchTransactions {
- batchId: string;
- page?: number;
- size?: number;
- status?: string;
+ batchNumber: number;
+ pageNumber: number;
+ numberOfReturnedRows: number;
+ isIncremental: boolean;
+ searchBy?: string;
+ searchText?: string;
}
}
diff --git a/packages/sdk/README.md b/packages/sdk/README.md
index 901abac..050c5aa 100644
--- a/packages/sdk/README.md
+++ b/packages/sdk/README.md
@@ -69,7 +69,8 @@ const sdk = ChiaSDK.initialize({
onekhusa: {
apiKey: "your-onekhusa-api-key",
apiSecret: "your-onekhusa-api-secret",
- organisationId: "your-organisation-id"
+ organisationId: "your-organisation-id",
+ merchantAccountNumber: 35253486
}
});
```
@@ -144,8 +145,17 @@ so on.
`sdk.payouts.send()` on OneKhusa returns `status: "pending_approval"` and
`requiresApproval: true`, because its API opens a maker-checker flow rather than sending.
-Advance it with `sdk.onekhusa.disbursements.approveSingle(id)`. Check
-`sdk.capabilities("onekhusa").payouts.requiresApproval` before calling.
+Advance it with the transaction reference and configured merchant account:
+
+```typescript
+await sdk.onekhusa.disbursements.approveSingle({
+ merchantAccountNumber: sdk.onekhusa.merchantAccountNumber,
+ transactionReferenceNumber: payout.id,
+ actionedBy: "payments@example.com"
+});
+```
+
+Check `sdk.capabilities("onekhusa").payouts.requiresApproval` before calling.
## PawaPay
@@ -237,73 +247,149 @@ const payout = await sdk.paychangu.initializeMobileMoneyPayout(
Enterprise payment platform with collections and disbursements.
+### Direct mobile money push
+
+`initiateDirectPushPayment` starts a USSD prompt. Its response acknowledges
+initiation only; wait for a `pushpayment.success` webhook before fulfilling an
+order. Use a unique `sourceReferenceNumber` for each payment attempt. See
+[OneKhusa's direct push API](https://docs.onekhusa.com/api-reference/collections/direct-push-payment)
+and [push payment webhooks](https://docs.onekhusa.com/api-reference/webhooks/pushpayment-webhook)
+for the full lifecycle.
+
+```typescript
+import { isServiceError } from "@chiahq/sdk";
+
+const push = await sdk.onekhusa.collections.initiateDirectPushPayment({
+ merchantAccountNumber: 35253486,
+ connectorId: 550044,
+ payerAccountNumber: "0999000000",
+ sourceReferenceNumber: "ORDER12345",
+ transactionDescription: "Payment for order ORDER12345",
+ transactionAmount: 1000,
+ capturedBy: "payments@example.com"
+});
+
+if (isServiceError(push)) {
+ throw new Error(push.errorMessage);
+}
+
+console.log("Amount to pay:", push.amountToPay);
+```
+
+### Inline or hosted checkout
+
+`initiateStandardCheckout` creates a OneKhusa checkout session for card and
+mobile money. For inline checkout, load the returned `checkoutUrl` in your app's
+modal overlay. The URL expires after 10 minutes and can also be used as a redirect.
+Confirm the final result through the push
+payment webhooks. See [OneKhusa Standard Checkout](https://docs.onekhusa.com/api-reference/collections/standard-checkout)
+for the supported payment methods and setup requirements.
+
+```typescript
+import { isServiceError } from "@chiahq/sdk";
+
+const checkout = await sdk.onekhusa.collections.initiateStandardCheckout({
+ payment: {
+ merchantAccountNumber: 35253486,
+ amount: 150,
+ description: "Groceries purchase",
+ sourceReferenceNumber: "ORDER12346",
+ capturedBy: "payments@example.com"
+ },
+ route: {
+ successRedirectionUrl: "https://example.com/payment/success",
+ failureRedirectionUrl: "https://example.com/payment/failure"
+ }
+});
+
+if (isServiceError(checkout)) {
+ throw new Error(checkout.errorMessage);
+}
+
+console.log("Checkout URL:", checkout.checkoutUrl);
+```
+
### Request-to-Pay Collection
-`initiateRequestToPay` returns `CollectionResponse | ServiceError` - narrow with
-`isServiceError` before reading the success fields:
+`initiateRequestToPay` returns a timed account number for the customer to pay.
+Settlement arrives through the collection webhook.
```typescript
import { isServiceError } from "@chiahq/sdk";
const collection = await sdk.onekhusa.collections.initiateRequestToPay({
- amount: 5000,
- currency: "MWK",
- phone: "265991234567",
- paymentMethod: "MOBILE_MONEY",
- reference: "order-789",
- description: "Payment for goods"
+ merchantAccountNumber: 35253486,
+ transactionAmount: 5000,
+ transactionDescription: "Payment for goods",
+ referenceNumber: "ORDER789",
+ capturedBy: "payments@example.com"
});
if (isServiceError(collection)) {
throw new Error(collection.errorMessage);
}
-console.log("TAN:", collection.tan);
+console.log("TAN:", collection.timedAccountNumber);
```
### Single Disbursement
```typescript
+import { isServiceError } from "@chiahq/sdk";
+
const disbursement = await sdk.onekhusa.disbursements.addSingle({
- amount: 10000,
- currency: "MWK",
- paymentMethod: "MOBILE_MONEY",
- recipient: {
- name: "John Doe",
- phone: "265991234567"
- },
- reference: "payout-001",
- description: "Salary payment"
+ merchantAccountNumber: 35253486,
+ beneficiaryName: "John Doe",
+ connectorId: 550044,
+ beneficiaryAccountNumber: "265991234567",
+ transactionAmount: 10000,
+ transactionDescription: "Salary payment",
+ sourceReferenceNumber: "payout-001",
+ capturedBy: "payments@example.com"
});
if (isServiceError(disbursement)) {
throw new Error(disbursement.errorMessage);
}
-// Approve the disbursement
-await sdk.onekhusa.disbursements.approveSingle(disbursement.id);
+const approval = await sdk.onekhusa.disbursements.approveSingle({
+ merchantAccountNumber: 35253486,
+ transactionReferenceNumber: disbursement.transactionReferenceNumber,
+ actionedBy: "payments@example.com"
+});
+
+if (isServiceError(approval)) {
+ throw new Error(approval.errorMessage);
+}
```
### Batch Disbursement
-Batch items each carry their own `amount`, `currency`, `paymentMethod` and `recipient` - there is
-no top-level `currency`/`paymentMethod`/`recipients` on the batch request:
+Batch JSON requests contain a header and transaction entries:
```typescript
-const batch = await sdk.onekhusa.disbursements.addBatch({
- name: "January Salaries",
- items: [
+const batch = await sdk.onekhusa.disbursements.addBatchJson({
+ header: {
+ merchantAccountNumber: 35253486,
+ isBatchScheduled: false,
+ capturedBy: "payments@example.com"
+ },
+ transactions: [
{
- amount: 50000,
- currency: "MWK",
- paymentMethod: "MOBILE_MONEY",
- recipient: { name: "John Doe", phone: "265991234567" }
+ beneficiaryName: "John Doe",
+ connectorId: 550044,
+ beneficiaryAccountNumber: "265991234567",
+ transactionDescription: "January salary",
+ transactionAmount: 50000,
+ sourceReferenceNumber: "salary-john-001"
},
{
- amount: 45000,
- currency: "MWK",
- paymentMethod: "MOBILE_MONEY",
- recipient: { name: "Jane Smith", phone: "265999876543" }
+ beneficiaryName: "Jane Smith",
+ connectorId: 550044,
+ beneficiaryAccountNumber: "265999876543",
+ transactionDescription: "January salary",
+ transactionAmount: 45000,
+ sourceReferenceNumber: "salary-jane-001"
}
]
});
@@ -312,9 +398,25 @@ if (isServiceError(batch)) {
throw new Error(batch.errorMessage);
}
-// Approve and transfer funds
-await sdk.onekhusa.disbursements.approveBatch(batch.id);
-await sdk.onekhusa.disbursements.transferBatchFunds(batch.id);
+const approval = await sdk.onekhusa.disbursements.approveBatch({
+ merchantAccountNumber: 35253486,
+ batchNumber: batch.batchNumber,
+ actionedBy: "payments@example.com"
+});
+
+if (isServiceError(approval)) {
+ throw new Error(approval.errorMessage);
+}
+
+const transfer = await sdk.onekhusa.disbursements.transferBatchFunds({
+ merchantAccountNumber: 35253486,
+ batchNumber: batch.batchNumber,
+ transferredBy: "payments@example.com"
+});
+
+if (isServiceError(transfer)) {
+ throw new Error(transfer.errorMessage);
+}
```
## Configuration
@@ -339,6 +441,7 @@ const sdk = ChiaSDK.initialize({
apiKey: process.env.ONEKHUSA_API_KEY,
apiSecret: process.env.ONEKHUSA_API_SECRET,
organisationId: process.env.ONEKHUSA_ORGANISATION_ID,
+ merchantAccountNumber: Number(process.env.ONEKHUSA_MERCHANT_ACCOUNT_NUMBER),
environment: ENVIRONMENTS.PRODUCTION
}
});
@@ -364,6 +467,7 @@ const sdk = ChiaSDK.initialize({
apiKey: "your-key",
apiSecret: "your-secret",
organisationId: "your-org-id",
+ merchantAccountNumber: 35253486,
sandboxUrl: "https://custom-sandbox.onekhusa.com/v1"
}
});
diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts
index acf150b..484016a 100644
--- a/packages/sdk/src/sdk.ts
+++ b/packages/sdk/src/sdk.ts
@@ -145,7 +145,7 @@ export class ChiaSDK {
console.log("- PayChangu (requires PAYCHANGU_SECRET_KEY)");
console.log("- PawaPay (requires PAWAPAY_JWT)");
console.log(
- "- OneKhusa (requires ONEKHUSA_API_KEY, ONEKHUSA_API_SECRET, ONEKHUSA_ORGANISATION_ID)",
+ "- OneKhusa (requires ONEKHUSA_API_KEY, ONEKHUSA_API_SECRET, ONEKHUSA_ORGANISATION_ID, ONEKHUSA_MERCHANT_ACCOUNT_NUMBER)",
);
console.log("- Custom providers (configure via providers option)");
} else {
diff --git a/packages/sdk/src/services/onekhusa/collections/index.ts b/packages/sdk/src/services/onekhusa/collections/index.ts
index e0e72e3..fbe0b85 100644
--- a/packages/sdk/src/services/onekhusa/collections/index.ts
+++ b/packages/sdk/src/services/onekhusa/collections/index.ts
@@ -7,18 +7,69 @@ import {
} from "../../../utils/serviceWrapper";
import type {
CollectionTransaction,
+ DirectPushPaymentResponse,
GetCollectionTransactionRequest,
GetCollectionTransactionsRequest,
GetCollectionTransactionsResponse,
+ InitiateDirectPushPaymentRequest,
InitiateRequestToPayRequest,
RequestToPayResponse,
SimulateRequestToPayRequest,
SimulateRequestToPayResponse,
+ StandardCheckoutRequest,
+ StandardCheckoutResponse,
} from "../types/collection";
export class OneKhusaCollections {
constructor(private readonly network: HttpClient) {}
+ async initiateDirectPushPayment(
+ request: InitiateDirectPushPaymentRequest,
+ idempotencyKey?: string,
+ ): Promise> {
+ logger.info("OneKhusa: Initiating direct push payment", {
+ merchantAccountNumber: request.merchantAccountNumber,
+ connectorId: request.connectorId,
+ sourceReferenceNumber: request.sourceReferenceNumber,
+ transactionAmount: request.transactionAmount,
+ });
+
+ return wrapServiceCall(
+ () =>
+ this.network.post(
+ "/collections/push/direct/initiate",
+ request,
+ "initiating direct push payment",
+ idempotentConfig(idempotencyKey),
+ ),
+ this.network.handleApiError.bind(this.network),
+ "initiating direct push payment",
+ );
+ }
+
+ async initiateStandardCheckout(
+ request: StandardCheckoutRequest,
+ idempotencyKey?: string,
+ ): Promise> {
+ logger.info("OneKhusa: Initiating standard checkout", {
+ merchantAccountNumber: request.payment.merchantAccountNumber,
+ sourceReferenceNumber: request.payment.sourceReferenceNumber,
+ amount: request.payment.amount,
+ });
+
+ return wrapServiceCall(
+ () =>
+ this.network.post(
+ "/checkout/push/initiate",
+ request,
+ "initiating standard checkout",
+ idempotentConfig(idempotencyKey),
+ ),
+ this.network.handleApiError.bind(this.network),
+ "initiating standard checkout",
+ );
+ }
+
/**
* Reserves a timed account number (TAN) the customer pays into. This does
* not contact the customer - surfacing the TAN to them is the caller's job,
diff --git a/packages/sdk/src/services/onekhusa/types/collection.ts b/packages/sdk/src/services/onekhusa/types/collection.ts
index 2ae84d4..a97546e 100644
--- a/packages/sdk/src/services/onekhusa/types/collection.ts
+++ b/packages/sdk/src/services/onekhusa/types/collection.ts
@@ -26,6 +26,51 @@ export interface RequestToPayResponse {
expiryInMinutes: number;
}
+export interface InitiateDirectPushPaymentRequest {
+ merchantAccountNumber: number;
+ connectorId: number;
+ payerAccountNumber: string;
+ sourceReferenceNumber: string;
+ transactionDescription: string;
+ transactionAmount: number;
+ capturedBy: string;
+}
+
+export interface DirectPushPaymentResponse {
+ merchantAccountNumber: number;
+ sourceReferenceNumber: string;
+ responseCode: string;
+ feeRuleCode: string;
+ transactionAmount: number;
+ totalCharges: number;
+ amountToPay: number;
+}
+
+export interface StandardCheckoutPayment {
+ merchantAccountNumber: number;
+ amount: number;
+ description: string;
+ sourceReferenceNumber: string;
+ capturedBy: string;
+}
+
+export interface StandardCheckoutRoute {
+ successRedirectionUrl: string;
+ failureRedirectionUrl: string;
+}
+
+export interface StandardCheckoutRequest {
+ payment: StandardCheckoutPayment;
+ route: StandardCheckoutRoute;
+}
+
+export interface StandardCheckoutResponse {
+ sourceReferenceNumber: string;
+ merchantAccountNumber: number;
+ paymentTransactionId: string;
+ checkoutUrl: string;
+}
+
export type CollectionSearchBy =
| "TransactionReferenceNumber"
| "SourceCustomerName"
@@ -103,8 +148,30 @@ export interface CollectionWebhookPayload {
};
}
+export interface PushPaymentWebhookPayload {
+ connectorId: number;
+ sourceAccountNumber?: string;
+ sourceAccountName?: string;
+ sourceInstitution: string;
+ sourceReferenceNumber?: string;
+ merchantReferenceNumber: string;
+ beneficiaryAccountNumber: string;
+ transactionReferenceNumber?: string;
+ transactionDescription: string;
+ transactionAmount: number;
+ transactionFee?: number;
+ transactionDate: string;
+ transactionStatusCode: TransactionStatusCode;
+ transactionCode?: string;
+ responseCode: string;
+ errorMessages?: string[];
+}
+
export type CollectionWebhookEvent =
| "payment.success"
| "payment.reversed"
| "payrequest.success"
- | "payrequest.reversed";
+ | "payrequest.reversed"
+ | "pushpayment.success"
+ | "pushpayment.failed"
+ | "pushpayment.reversed";
diff --git a/packages/sdk/src/services/onekhusa/types/index.ts b/packages/sdk/src/services/onekhusa/types/index.ts
index de043e1..8dcf097 100644
--- a/packages/sdk/src/services/onekhusa/types/index.ts
+++ b/packages/sdk/src/services/onekhusa/types/index.ts
@@ -31,6 +31,13 @@ export namespace OneKhusaTypes {
export type InitiateRequestToPayRequest =
Collection.InitiateRequestToPayRequest;
export type RequestToPayResponse = Collection.RequestToPayResponse;
+ export type InitiateDirectPushPaymentRequest =
+ Collection.InitiateDirectPushPaymentRequest;
+ export type DirectPushPaymentResponse = Collection.DirectPushPaymentResponse;
+ export type StandardCheckoutPayment = Collection.StandardCheckoutPayment;
+ export type StandardCheckoutRoute = Collection.StandardCheckoutRoute;
+ export type StandardCheckoutRequest = Collection.StandardCheckoutRequest;
+ export type StandardCheckoutResponse = Collection.StandardCheckoutResponse;
export type CollectionSearchBy = Collection.CollectionSearchBy;
export type GetCollectionTransactionsRequest =
Collection.GetCollectionTransactionsRequest;
@@ -42,6 +49,7 @@ export namespace OneKhusaTypes {
export type SimulateRequestToPayRequest =
Collection.SimulateRequestToPayRequest;
export type CollectionWebhookPayload = Collection.CollectionWebhookPayload;
+ export type PushPaymentWebhookPayload = Collection.PushPaymentWebhookPayload;
export type CollectionWebhookEvent = Collection.CollectionWebhookEvent;
export type AddSingleDisbursementRequest =