Offline-first is an architecture, not a feature flag
What four years of running ticketing systems across Zambian bus corridors taught us about designing for networks that are not there.
· 3 min
Collecting payment is the easy part. Knowing, hours later, exactly what happened is where integrations quietly fail.
Fred Mwale · Chief Operations Officer · · 2 min read
Every mobile money integration demo works. A payment is initiated, a prompt arrives on the phone, a PIN is entered, a confirmation comes back. Ten seconds, end to end.
Production is not that. In production the confirmation arrives four hours later, or twice, or never while the money has definitely moved. The integration work is not in the happy path — it is in knowing, with certainty, what actually happened.
Teams new to this model tend to build for success and failure. There is a third state that matters more than either: unknown.
A collection request has been sent. No confirmation has arrived. The money may have moved or it may not. Any system that forces this into either bucket will eventually either hand out a free ticket or take money for nothing.
We model it explicitly. An unknown payment holds the seat, does not issue the ticket, and enters a reconciliation queue with an escalating check schedule. Most resolve within minutes. Some take a day. A small number need a human, and the design has to make that a normal outcome rather than an incident.
Provider callbacks arrive out of order, duplicated, and occasionally for transactions you have no record of initiating. Treating them as authoritative is how ledgers drift.
Our rule is that a callback triggers a query, and the query result is authoritative. It costs an extra API call per transaction and removes an entire category of reconciliation defect. Worth it every time.
The single highest-value practice we adopted was daily automated reconciliation against the provider's own settlement file. Every transaction, matched on both sides, with exceptions surfaced to operations by mid-morning.
Monthly reconciliation finds the same defects thirty days later, by which point the agent involved has forgotten the day, the customer has moved on, and the pattern that would have identified the root cause has been buried under thousands of subsequent transactions.
It comes up in every payments post because it is the control that prevents the worst outcome. Client-generated keys, stable across app restarts and network retries, honoured by every endpoint that moves money.
An agent whose screen froze will press the button again. Design for that as the expected behaviour, because it is.
If you are starting a mobile money integration, build the reconciliation report before the payment flow. It will tell you what questions the provider's API can actually answer, and that constrains the design more than anything in the integration guide.
What four years of running ticketing systems across Zambian bus corridors taught us about designing for networks that are not there.
· 3 min
An analysis of nineteen migration programmes, and the three decisions that separated the ones that reduced run cost from the ones that did not.
· 2 min
Feature matrices do not predict whether an ERP programme will succeed. These questions, asked before signature, do.
· 3 min
Monthly at most. Product releases and writing like this.