Skip to main content
This is the bills-specific integration guide. The three steps are the same for every bill; what changes is what the enquiry needs, what it gives back, and what “delivered” means.

The families

The two columns that drive your code are package and amount. Get those wrong and the enquiry refuses before you reach a payment.

Airtime: the exception

Airtime billers sell no packages at all — the customer names an amount.
Send the airtime biller and an amount. Naming a package is bill_package_not_supported, except for the legacy airtime package id, which is still accepted and ignored. You can tell the two shapes apart from the catalog: an airtime biller returns no packages. The phone number is normalised for you, so 0801 234 5678, +2348012345678, and 2348012345678 are the same customer.
For airtime and data, the recipient is the phone number from the enquiry. The phoneNumber in the payment body is contact information, not the destination. Getting the number right matters at enquiry time, not at payment time.

Data: fixed prices

A data bundle is priced by its package, and the price is in the catalog:
isFixedAmount: true means the price is authoritative: send that exact amount, or omit it. Sending a different one is bill_amount_mismatch. Where isFixedAmount is false, amount is a suggestion you may override, and where it is also null you must supply one.

Utilities: minimums and tokens

Prepaid electricity is the richest case, and the one worth building against first. The enquiry gives you a minimum.
Below minimumAmount, the payment is refused with amount_below_minimum. Validate against the value the enquiry returned rather than a constant of your own — it varies by biller and can change. Postpaid gives you a balance instead. totalDue is what the biller says is outstanding. Offering it as the default amount is usually what a customer wants. Prepaid gives you a token.
The token is the value. It is issued once, is not re-issued, and a customer who loses it has lost what they paid for. Store it when it arrives and deliver it through a channel the customer keeps — not only an ephemeral screen.
tokenData is whatever the biller returned; its keys vary. Store the whole object rather than plucking a field you have seen once.

canVend, and what it does not promise

Several billers return canVend on the customer. It means the biller will accept a payment for this customer right now. It does not mean the vend will succeed — a payment can still come back failed after a confirmed enquiry. Treat it as a pre-flight check that saves a wasted debit, not as a guarantee.

Reading vendStatus

vendStatus is the biller’s own word for delivery, upper-cased and passed through. It is not a Sakawave enum, and its vocabulary differs by biller.
  • Branch on status — submitted, pending, confirmed, failed. That is Sakawave’s, and it is stable.
  • Display vendStatus in support tooling, where the biller’s own wording helps someone reading a ticket.
  • Never parse it. A new biller can introduce a word you have never seen.
The same applies to providerStatusCode.

Failure, and who tells the customer

Every family fails the same way: the payment goes failed, and Sakawave reverses the debit automatically. What differs is what the customer expected. Someone who bought electricity and received a silent refund has no electricity and no explanation. Wire bills_payment.failed to a customer-facing message, with failureReason where it is readable.

Choosing amounts per family

Four rules, in that order, cover every family in the catalog.

A checklist per family

  • Airtime: airtime biller’s billerId, no package, recipient from the enquiry
  • Data: package required, amount from the package, never chosen
  • Prepaid utility: validate against minimumAmount, store tokenData
  • Postpaid utility: default the amount to totalDue
  • All: show customer.customerName before paying, and totalDebit as the price
  • All: branch on status, display vendStatus, parse neither the biller’s codes nor its words