Rent collection is rarely broken because someone cannot send money. It breaks when the result is difficult to connect to the right tenant, property, and balance.
That was the operational problem behind Kodara. Kenyan landlords were working across WhatsApp conversations, spreadsheets, manual rent chasing, and disconnected maintenance follow-up. Adding an M-Pesa button would not solve that fragmentation. Reconciliation had to be part of the product’s core architecture.
Success is an event, not the finish line.
A successful callback from a payment provider reports an important event. It does not answer every question the product must resolve:
- Which tenancy owns this payment?
- Has this result already been processed?
- Can only the correct landlord and tenant see it?
- What happens when an automatic match cannot be made?
Until those questions have answers, the provider may be finished while the product is not. For Kodara, the callback is therefore an input to a payment workflow—not the final state of that workflow.
Give the payment a clear place to belong.
Kodara follows a strict ownership model:
Landlord → Property → Unit → Tenancy
That hierarchy gives every rent payment a business context. A transaction is not merely an amount and a provider reference; it must belong to the correct tenancy inside the correct landlord account.
The same model supports account isolation. PostgreSQL Row Level Security enforces the boundary at the data layer, so payment visibility does not depend only on checks in an interface or application route.
Protect the callback path.
Kodara places M-Pesa operations behind Supabase Edge Functions. Its callback handling is built around two guarantees: atomic processing and idempotency.
Atomic processing keeps related payment changes together. Idempotency ensures that receiving the same external result more than once cannot create the same payment more than once. Together, they turn an asynchronous provider message into a controlled state transition.
- 01Tenant initiates STK Push
- 02Daraja returns a callback
- 03Protected Edge Function
- 04Idempotency and tenancy matching
- MatchedBalance and history update UnmatchedReconciliation queue
An unmatched payment is still a real state.
Automatic reconciliation can fail even when an incoming payment result should not be discarded. Kodara sends failed matches into a resolvable queue instead of allowing them to become lost transactions.
This creates an honest third state: the payment is known, but it has not yet been assigned successfully. Keeping that state visible makes investigation and resolution part of the workflow.
Evidence should match the claim.
Kodara’s current build treats data rules and failure paths as implementation work. The evidence is specific—and deliberately not presented as production transaction volume.
- Unit tests
- 26
- Database assertions
- 22
- Daraja integration
- Sandbox verified
The practical definition of complete.
A dependable payment workflow is complete when the result has been processed without duplication, matched to the correct business record or placed in a visible reconciliation queue, isolated to the correct account, and reflected in the balance and history seen by the user.
That is the difference between adding payments to a product and building a payment system.