AzamPay
A pure-Dart SDK for the AzamPay payment APIs, covering Tanzania, Rwanda and International Money Transfer (IMT). It works in Flutter, server-side Dart and CLI apps.
Official AzamPay developer docs: developerdocs.azampay.co.tz/
Highlights
- Full API coverage — collections, checkout, OTP, disbursements, transfers, balance, name lookup and transaction status across all three regions.
- Automatic auth — tokens are generated and cached per region; you never call the token endpoint yourself.
- Self-documenting — every endpoint has a
.docyou can print while coding (path, request/response fields and a runnable example). - Typed & safe — enums for providers, a single
AzamPayResponsewrapper, andAzamPayExceptionfor transport errors. - Testable — inject your own
http.Client(e.g. aMockClient).
Install
dependencies:
azampay: ^1.0.0
import 'package:azampay/azampay.dart';
Quick start
final azampay = AzamPay(
appName: '<your app name>',
clientId: '<your client id>',
clientSecret: '<your client secret>',
// sandbox is the default; pass sandbox: false for production.
// sandbox: false,
);
final res = await azampay.tanzania.mnoCheckout(
accountNumber: '<Customer Phone Number>', // country-code prefixed (255...)
amount: 1000,
currency: 'TZS',
provider: MnoProvider.azampesa, // Airtel | Tigo | Halopesa | Azampesa | Mpesa
externalId: '<your unique reference>',
);
if (res.success == true) {
print('Reference: ${res.transactionId}');
} else {
print('Failed: ${res.message}');
}
Everything is reached through a region: azampay.tanzania,
azampay.rwanda, azampay.imt.
Phone numbers must include the country code —
255...for Tanzania,250...for Rwanda.
externalId/referenceIdmust be unique per transaction (AzamPay uses it to de-duplicate). A good pattern is a prefix plus a timestamp or UUID:String reference(String prefix) => '$prefix-${DateTime.now().millisecondsSinceEpoch}';
The response object
Every call returns an AzamPayResponse:
| Getter | Meaning |
|---|---|
isSuccessful |
HTTP status is 2xx |
statusCode |
HTTP status code |
success |
AzamPay's success flag |
message |
AzamPay's message |
data |
AzamPay's data payload |
transactionId |
Reference (any of transactionId / pgReferenceId / referenceId) |
body |
The decoded JSON (Map/List) |
rawBody |
The verbatim response string |
field<T>('key') |
Any top-level field |
A normal business failure (e.g. insufficient balance) is returned as a response — inspect
isSuccessful/success.AzamPayExceptionis only thrown for transport failures (network down, bad credentials, unparseable token).
Self-documenting endpoints
Every endpoint is a callable object that also carries its documentation:
// Call it:
await azampay.tanzania.mnoCheckout(...);
// Read its full docs inline (path, fields, example):
print(azampay.tanzania.mnoCheckout.doc);
// List everything a region offers:
print(azampay.rwanda.describe());
print(azampay.tanzania.mnoCheckout.doc) prints:
┌─ mnoCheckout
│ POST /azampay/mno/checkout
│ Mobile-money (MNO) push checkout.
│
│ Request body:
│ accountNumber string required Customer mobile number to charge
│ amount number required
│ currency string required e.g. TZS
│ provider string required Mobile operator [Airtel, Tigo, Halopesa, Azampesa, Mpesa]
│ externalId string required Your unique transaction reference
│ ...
│ Example:
│ await azampay.tanzania.mnoCheckout(...);
└─
Tanzania
final tz = azampay.tanzania;
| Endpoint | Description |
|---|---|
mnoCheckout(...) |
Mobile-money push checkout |
bankCheckout(...) |
Bank checkout (confirmed with an OTP) |
generateCrdbOtp({...}) / generateNmbOtp({...}) |
Request a bank OTP |
getPaymentPartners() |
List hosted-checkout partners |
postCheckout(...) |
Create a hosted checkout page |
disburse(...) |
Pay out to a wallet |
nameLookup(...) |
Resolve a disbursement account name |
transactionStatus(...) |
Disbursement transaction status |
Mobile checkout — charge a customer's wallet.
await tz.mnoCheckout(
accountNumber: '<Customer Phone Number>', // 255...
amount: 1000,
currency: 'TZS',
provider: MnoProvider.azampesa, // Airtel | Tigo | Halopesa | Azampesa | Mpesa
externalId: '<your unique reference>',
);
Bank checkout — charge a bank account (generate an OTP first).
await tz.bankCheckout(
merchantAccountNumber: '<Your Merchant Account Number>',
merchantMobileNumber: '<Customer Phone Number>',
amount: 5000,
currencyCode: 'TZS',
provider: BankProvider.crdb, // CRDB | NMB
otp: '<OTP from generateCrdbOtp / generateNmbOtp>',
referenceId: '<your unique reference>',
);
Disbursement — pay money out, from your wallet to a recipient.
await tz.disburse(
// SOURCE = you (the sender / payer).
source: const DisbursementAccount(
accountNumber: '<Your Payout Wallet Number>',
fullName: '<Your Company Name>',
bankName: 'Azampesa', // Airtel | Tigo | Azampesa
currency: 'TZS',
countryCode: 'TZ',
),
// DESTINATION = the recipient.
destination: const DisbursementAccount(
accountNumber: '<Recipient Phone Number>',
fullName: '<Recipient Name>',
bankName: 'Tigo', // Airtel | Tigo | Azampesa
currency: 'TZS',
countryCode: 'TZ',
),
transferDetails: const TransferDetails(amount: 2000, type: 'SendMoney'),
externalReferenceId: '<your unique reference>',
);
Rwanda
final rw = azampay.rwanda;
Collection (v1): checkout, transactionStatus, transactionStatusByReference,
accountLookup.
Disbursement (v1): nameLookup, checkBalance, disburse, disbursementStatus.
v2: accountLookupV2, initiatePayment, paymentStatus,
paymentStatusByReference, balance, initiateTransfer, transferStatus,
transferStatusByReference.
final payment = await rw.initiatePayment(
provider: 'Airtel',
currencyCode: 'RWF',
amount: '1000',
referenceId: '<your unique reference>',
accountNumber: '<Customer Phone Number>', // 250...
);
final status = await rw.paymentStatus(pgReferenceId: payment.transactionId!);
International Money Transfer (IMT)
final imt = azampay.imt; // sendMoney, nameLookup, checkBalance, transactionStatus
IMT payloads are compliance-heavy (sender identity, nationality, reason…), so
sendMoney takes the source / destination / transferDetails objects as
maps. Print azampay.imt.sendMoney.doc for the exact fields.
await imt.sendMoney(
source: {
'fullName': '<Sender Name>',
'nationality': '<Sender Country Code>',
// ... see azampay.imt.sendMoney.doc
},
destination: {
'fullName': '<Recipient Name>',
'bankName': 'Azampesa',
'accountNumber': '<Recipient Account Number>',
},
transferDetails: {'amount': 50000, 'dateInEpoch': 1700000000},
externalReferenceId: '<your unique reference>',
checksum: '<checksum>',
remarks: '<remarks>',
);
Environments & custom hosts
Sandbox is the default. Switch to production with sandbox: false (or
environment: AzamEnvironment.production).
AzamPay splits its API across several hostnames (auth / checkout / disbursement, different per country). Sandbox hosts come straight from the official specs; a few production hosts aren't published there and are derived by convention. You can override any host:
final azampay = AzamPay(
appName: '<your app name>',
clientId: '<your client id>',
clientSecret: '<your client secret>',
sandbox: false,
baseUrlOverrides: {
// key: "<region>.<service>.<environment>"
'rwanda.checkout.production': 'https://checkout.azampay.co.rw',
},
);
Testing
Inject a mock client — no network needed:
import 'package:http/testing.dart';
import 'package:http/http.dart' as http;
final azampay = AzamPay(
appName: '<your app name>',
clientId: '<your client id>',
clientSecret: '<your client secret>',
httpClient: MockClient((request) async {
if (request.url.path.contains('GenerateToken')) {
return http.Response('{"data":{"accessToken":"t"},"success":true}', 200);
}
return http.Response('{"success":true,"transactionId":"txn-1"}', 200);
}),
);
Run the SDK's own suite with dart test.
Migrating from 0.0.x
The old top-level calls still work (deprecated):
| Old | New |
|---|---|
azampay.accessToken |
azampay.token() |
azampay.mobileCheckout(merchantMobileNumber: ...) |
azampay.tanzania.mnoCheckout(accountNumber: ...) |
azampay.bankCheckout(currency: ...) |
azampay.tanzania.bankCheckout(currencyCode: ...) |
The main change: calls now return an AzamPayResponse (with success,
message, data, transactionId) instead of a raw http.Response.
Credits
Built and maintained by Brightius Kalokola at TRIXA. Thanks to AzamPay for the payment platform.
Support
Please open an issue on GitHub, or contact the maintainer at brightius@trixa.net
Licensed under the MIT License.
Libraries
- azampay
- AzamPay Dart SDK.