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 =