azampay 1.0.0
azampay: ^1.0.0 copied to clipboard
A pure-Dart SDK for the AzamPay payment APIs — Tanzania, Rwanda and International Money Transfer (IMT). Works in Flutter, server-side Dart and CLI.
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: https://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.