Chia SDK
A TypeScript SDK for integrating African mobile money payments. Unified, type-safe API for PayChangu, PawaPay, and OneKhusa.
Standalone by design
The SDK talks directly to the provider APIs with your own credentials - a PayChangu secret key, a PawaPay API token, or OneKhusa API credentials. There is no Chia account, no proxy, and no platform dependency: your requests go straight to the providers, and nothing here phones home.
Reach for the SDK when you need:
- Custom payment flows under your full control
- Direct access to provider APIs (deposits, payouts, refunds, wallets)
- Integration into an existing backend where you manage your own provider accounts
- Batch disbursements or approval workflows (OneKhusa)
- The unified payments surface: one call that routes across whichever providers you configure
The hosted platform (optional)
If what you actually want is recurring billing - plans, hosted checkout, automatic renewals and retries, failed-payment recovery, webhooks, and weekly payouts - the Chia platform runs all of that on top of this SDK. Sign up, create a plan, share a link; no provider accounts needed. The SDK remains the right tool for custom flows; the platform is the shortcut for subscriptions. Neither requires the other.
Features
- Multi-Provider Support - PayChangu, PawaPay, and OneKhusa in one SDK
- Full TypeScript Support - Comprehensive type definitions for all APIs
- Sandbox & Production - Easy environment switching
- Comprehensive Error Handling - Detailed error messages and types
Supported Providers
| Provider | Region | Capabilities |
|---|---|---|
| PayChangu | Malawi | Payments, Mobile Money, Bank Transfers |
| PawaPay | Sub-Saharan Africa | Deposits, Payouts, Refunds, Wallets |
| OneKhusa | Malawi & Southern Africa | Collections, Disbursements, Batch Payouts |
Installation
npm install @chiahq/sdk
# or
pnpm add @chiahq/sdk
# or
yarn add @chiahq/sdk
Quick Start
import { ChiaSDK } from "@chiahq/sdk";
const sdk = ChiaSDK.initialize({
pawapay: {
jwt: "your-pawapay-jwt"
},
paychangu: {
secretKey: "your-paychangu-secret"
},
onekhusa: {
apiKey: "your-onekhusa-api-key",
apiSecret: "your-onekhusa-api-secret",
organisationId: "your-organisation-id"
}
});
ChiaSDK has a private constructor - use ChiaSDK.initialize(config) to create (or fetch) the
singleton, or ChiaSDK.create(config) for an independent instance. Both accept the same
SDKConfig shape.
You only need to configure the providers you plan to use.
Unified payments API
Instead of calling each provider directly, sdk.payments and sdk.payouts route a request to
whichever configured provider can serve it:
const payment = await sdk.payments.initiate({
reference: "order-123",
amount: "50.00",
currency: "ZMW",
msisdn: "260971234567",
country: "ZMB",
description: "Payment for services",
});
payment.provider // "pawapay" - paychangu was skipped, it cannot serve ZMW in ZMB
payment.status // "pending"
payment.nextAction // { type: "pin_prompt" }
payment.operator // "AIRTEL_ZMB" - inferred from the msisdn
Routing is by country and currency, preferring PayChangu, then PawaPay, then OneKhusa.
Providers that cannot serve the route are skipped, not contacted. Pin a single provider with
provider: "pawapay", or give an ordered shortlist with providers: ["pawapay", "paychangu"].
payment.attempts records every provider tried, skipped or refused, with the reason.
payment.raw always carries the untouched response from whichever provider handled the
request.
Coverage today
| Provider | Countries |
|---|---|
| PawaPay | 19 countries across Sub-Saharan Africa |
| PayChangu | Malawi only |
| OneKhusa | Malawi only |
Outside Malawi, PawaPay is currently the only candidate.
Failover is deliberately conservative
This is not retry-on-failure. When a shortlist is given, the SDK advances to the next provider only on outcomes that provably moved no money: the provider isn't configured, it can't serve the route, validation was rejected, or it explicitly refused the request. On a timeout or a 5xx after the request was sent, it aborts rather than trying another provider - a client-side timeout is indistinguishable from a successful charge, and retrying elsewhere would risk charging the customer twice.
Fetching a payment later
sdk.payments.get(id, { provider }) requires an explicit provider: an opaque id carries no
routing information on its own. Pass back the provider field from the ChiaPayment that
initiate() returned.
Provider-specific features
Anything the providers do not share stays on its own namespace: sdk.pawapay.refunds,
sdk.pawapay.remittances, sdk.paychangu hosted checkout, sdk.onekhusa.disbursements, and
so on.
OneKhusa payouts need approval
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.
PawaPay
Mobile money payments across Sub-Saharan Africa.
Request a Deposit via the Hosted Payment Page
const deposit = await sdk.pawapay.payments.initiatePayment({
depositId: "order-123",
returnUrl: "https://your-app.com/callback",
msisdn: "260971234567",
amount: "50.00",
country: "ZMB",
reason: "Service payment",
language: "EN"
});
// Redirect the customer to deposit.redirectUrl to complete the payment
Send a Payout
const payout = await sdk.pawapay.payouts.sendPayout({
payoutId: "payout-123",
amount: "50.00",
currency: "ZMW",
recipient: {
type: "MMO",
accountDetails: {
phoneNumber: "260701234567",
provider: "AIRTEL_ZMB"
}
}
});
Check Wallet Balance
const balances = await sdk.pawapay.wallets.getAllBalances();
PayChangu
Payment services in Malawi.
Initiate Payment
const txRef = "order-456";
const payment = await sdk.paychangu.initiatePayment({
amount: "1000",
currency: "MWK",
tx_ref: txRef,
email: "customer@example.com",
first_name: "John",
last_name: "Doe",
callback_url: "https://your-app.com/webhook",
return_url: "https://your-app.com/success"
});
// Redirect customer to checkout
console.log("Checkout URL:", payment.data.checkout_url);
Verify Transaction
const verification = await sdk.paychangu.verifyTransaction(txRef);
Mobile Money Payout
initializeMobileMoneyPayout takes positional arguments, not an options object:
const payout = await sdk.paychangu.initializeMobileMoneyPayout(
"265991234567", // recipient mobile number
"operator-ref-id", // from sdk.paychangu.getMobileMoneyOperators()
2000, // amount
"payout-123" // charge_id
);
OneKhusa
Enterprise payment platform with collections and disbursements.
Request-to-Pay Collection
initiateRequestToPay returns CollectionResponse | ServiceError - narrow with
isServiceError before reading the success fields:
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"
});
if (isServiceError(collection)) {
throw new Error(collection.errorMessage);
}
console.log("TAN:", collection.tan);
Single Disbursement
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"
});
if (isServiceError(disbursement)) {
throw new Error(disbursement.errorMessage);
}
// Approve the disbursement
await sdk.onekhusa.disbursements.approveSingle(disbursement.id);
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:
const batch = await sdk.onekhusa.disbursements.addBatch({
name: "January Salaries",
items: [
{
amount: 50000,
currency: "MWK",
paymentMethod: "MOBILE_MONEY",
recipient: { name: "John Doe", phone: "265991234567" }
},
{
amount: 45000,
currency: "MWK",
paymentMethod: "MOBILE_MONEY",
recipient: { name: "Jane Smith", phone: "265999876543" }
}
]
});
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);
Configuration
Use environment variables for secure credential management. There is no top-level environment
option - each provider takes its own environment field ("DEVELOPMENT" or "PRODUCTION",
defaulting to "DEVELOPMENT"):
import { ChiaSDK, ENVIRONMENTS } from "@chiahq/sdk";
const sdk = ChiaSDK.initialize({
pawapay: {
jwt: process.env.PAWAPAY_TOKEN,
environment: ENVIRONMENTS.PRODUCTION // or ENVIRONMENTS.DEVELOPMENT
},
paychangu: {
secretKey: process.env.PAYCHANGU_SECRET,
environment: ENVIRONMENTS.PRODUCTION
},
onekhusa: {
apiKey: process.env.ONEKHUSA_API_KEY,
apiSecret: process.env.ONEKHUSA_API_SECRET,
organisationId: process.env.ONEKHUSA_ORGANISATION_ID,
environment: ENVIRONMENTS.PRODUCTION
}
});
Custom API URLs
Override default provider endpoints for testing or regional deployments:
const sdk = ChiaSDK.initialize({
pawapay: {
jwt: "your-jwt",
environment: "DEVELOPMENT",
sandboxUrl: "https://custom-sandbox.pawapay.io/v1",
productionUrl: "https://custom-prod.pawapay.io/v1"
},
paychangu: {
secretKey: "your-secret",
sandboxUrl: "https://custom.paychangu.com"
},
onekhusa: {
apiKey: "your-key",
apiSecret: "your-secret",
organisationId: "your-org-id",
sandboxUrl: "https://custom-sandbox.onekhusa.com/v1"
}
});
Both sandboxUrl and productionUrl are optional. The URL used depends on the environment setting.
Type Definitions
All types are exported from the main package:
import type {
PayChanguTypes,
PawaPayTypes,
OneKhusaTypes
} from "@chiahq/sdk";
Requirements
- Node.js 18.0.0 or higher
- TypeScript 5.0+ (optional, but recommended)
Getting API Credentials
These credentials are needed when using the SDK directly. If you're using the Chia platform, you don't need provider API keys - Chia handles payment collection for you.
PawaPay
- Visit PawaPay and create a developer account
- Complete onboarding and verification
- Get your API token from the dashboard
PayChangu
- Sign up at PayChangu
- Complete business verification
- Get your secret key from the merchant dashboard
OneKhusa
- Contact OneKhusa to create a business account
- Complete KYC verification
- Get your API Key, API Secret, and Organisation ID
Documentation
For full documentation, visit docs.usechia.com or see the docs.
MCP Server
For AI-powered payment operations with Claude, check out @chiahq/mcp.
License
MIT