azampay 1.0.0 copy "azampay: ^1.0.0" to clipboard
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 #

Made in Tanzania

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 .doc you can print while coding (path, request/response fields and a runnable example).
  • Typed & safe — enums for providers, a single AzamPayResponse wrapper, and AzamPayException for transport errors.
  • Testable — inject your own http.Client (e.g. a MockClient).

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 / referenceId must 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. AzamPayException is 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.

3
likes
160
points
184
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A pure-Dart SDK for the AzamPay payment APIs — Tanzania, Rwanda and International Money Transfer (IMT). Works in Flutter, server-side Dart and CLI.

Repository (GitHub)
View/report issues

Topics

#payments #azampay #tanzania #rwanda #mobile-money

License

MIT (license)

Dependencies

http

More

Packages that depend on azampay