Card-first payment thinking does not survive contact with the Ghanaian market. Building GHVouchers and GetDataGH taught us a set of things that are obvious in hindsight and expensive to learn in production.
The payment is asynchronous, and your UI must agree
A card charge resolves in a couple of seconds. A mobile money charge involves the customer receiving a prompt on their handset, finding it, entering a PIN, and confirming, while your request sits open.
That gap can be thirty seconds. It can be three minutes if they are on a call. It can never complete at all, because they dismissed the prompt and forgot.
Any flow that assumes a synchronous answer will fail. What works:
- Treat the charge as a long-running job from the outset, not a request
- Show the customer explicitly what to do on their phone, network by network
- Poll for status, and also accept the provider callback. Never rely on only one
- Give the customer a way back to a pending transaction after they close the tab
Idempotency is not optional
Customers on unreliable connections retry. They tap twice. They refresh mid-charge. Providers occasionally deliver the same callback more than once.
Every operation that moves money or provisions a product needs an idempotency key, checked before any side effect. Without one you will double-charge somebody, and you will discover it from a support message rather than from your monitoring.
The same applies to fulfilment. On a voucher platform, delivering the same code twice is as bad as not delivering it at all: one of those customers now has a code that does not work.
The hard part of payments is not taking the money. It is guaranteeing that exactly one thing happened, once, even when the network disagreed with itself three times along the way.
Reconciliation is a feature, not a chore
Assume that at some point your record of a transaction and the provider's record will diverge. The question is whether you find out the same day or at the end of the month.
Build the reconciliation view early: every transaction, its provider reference, its status on both sides, and a clear flag where they disagree. It will feel like overhead until the first time it saves you a day of manual investigation.
Three networks are three integrations
MTN, Telecel and AirtelTigo differ in callback format, in timeout behaviour, in how they report a failed charge, and in how they behave under load. Abstracting them behind one interface is right, but the abstraction must be thin enough that you can still see per-network behaviour in your logs and metrics.
When one network degrades (and one always does eventually), you need to know which, within minutes, and ideally be able to steer around it.
Delivery time is the product
On both platforms, the promise is not really "buy a voucher" or "buy a bundle". It is "receive it immediately". A thirty-second SMS delivery is the difference between a repeat customer and a support ticket.
That shapes the architecture more than any other requirement. Fulfilment runs on its own queue, with its own alerting, and a fallback path for when the SMS gateway is slow. Everything else on the platform can degrade gracefully. That path cannot.
If you are building something that moves money in West Africa, we are happy to compare notes.


