Reconciling mobile money is not hard because the arithmetic is difficult. It is hard because the payment arrives carrying an identifier from the rail it travelled on, and your ledger is organised around an identifier you issued. Nothing joins the two unless you make it join.
What each rail actually gives you
| Rail | You reliably get | You often do not get | Workable match key |
|---|---|---|---|
| Wallet payment to a merchant code (EcoCash and similar) | A transaction identifier, the payer’s number, the amount, a timestamp | Your invoice number, unless the customer typed it and typed it correctly | Payer number plus amount inside a time window |
| ZIPIT and bank transfer | A bank reference, the amount, a value date, a narration field | The full narration — it is frequently truncated, and the customer wrote it anyway | Narration fragment plus amount, then payer name |
| Gateway-initiated payment (you created the transaction) | Your own reference, because you set it, plus the gateway status | Nothing. This is the case you should be engineering towards | Your reference. Exact. |
The reference field is the whole game
Every hour spent on reference discipline removes a day of matching later. There are only three situations, and each has one right answer.
- 01You initiate the paymentSet your own reference on the request. Then the match is exact and there is nothing to reconcile. Route as much volume here as you can — this is the argument for a payment link on the invoice rather than a number on it.
- 02The customer types a referenceMake it short, unambiguous and checkable. Drop characters people confuse, avoid spaces, keep it under about eight characters, and print it once, large, on the invoice. Accept it case-insensitively and accept it with the punctuation stripped, because that is how it will arrive.
- 03There is no reference at allMatch on payer identity plus amount inside a window, and treat the result as a suggestion for a human to confirm. Never post it automatically.
A match order that works
- 01Exact reference matchReference found, amount equals the open balance. Post it, no review.
- 02Reference match with a different amountPost it as a payment against that invoice and let the balance stand as a part-payment or an overpayment. Do not force it to the invoice total.
- 03Payer identity plus amount inside a windowOne candidate only. Present it to a person with both records side by side. Two candidates means no match.
- 04Everything else goes to a queueA named person works the queue daily. The queue having a name and a size is what stops it becoming a spreadsheet nobody owns.
The five cases that break a naive matcher
- Fees deducted in transit
- The customer sends the invoice amount and you receive slightly less. Post the receipt gross against the invoice and the charge to its own expense account. If you post the net amount, that invoice never closes and your ledger accumulates cents forever.
- One payment, several invoices
- A customer clears three invoices with one transfer. Your allocation model has to be many-to-many from the start; retrofitting it later means restating history.
- Partial payment
- Common and legitimate, especially on the 50% deposit model. An invoice needs an amount-paid and a balance, not a paid flag.
- Reversal and duplicate
- The same transaction identifier arriving twice must credit once. A reversal must reopen the invoice it closed rather than posting a negative receipt with no link back.
- Timing across a cut-off
- The customer paid at 23:50, your statement dates it the next day, and the invoice was raised in the previous month. Match on a window, and report on the invoice date rather than the receipt date.
Where the money sits while it is unmatched
In a suspense account for unallocated receipts, aged by day, with someone accountable for driving it to zero. Not in revenue, because you do not yet know whose revenue it is, and not in a spreadsheet, because a spreadsheet is not part of your trial balance.
Controls that keep it honest
- Statements imported daily, not at month end. A three-week-old mismatch is an investigation; a one-day-old mismatch is a phone call.
- An ageing report on unallocated receipts, reviewed weekly by someone who is not the person allocating them.
- Manual matches recorded with who, when and why — the same audit trail you would want on a manual payment.
- A monthly tie-out from your ledger balance to the wallet and bank statements. If it does not tie, you stop and find out why before you close.
What good looks like, in numbers
| Measure | Struggling | Workable | Good |
|---|---|---|---|
| Receipts matched with no human touch | Under 40% | 60–80% | Over 90% |
| Days from receipt to allocation | Month end | Two to three days | Same day |
| Oldest unallocated receipt | Older than a month | Under two weeks | Under three days |
| Customers wrongly chased for a paid invoice | Every month | Occasionally | It has stopped happening |
None of this needs an expensive system. It needs the reference decided before the invoice is printed, the match order written down, and one person who owns the queue. The software should make those three things easy — that is genuinely the whole job.