Payment rails & the ledger
9 payment rails behind one provider interface — because in most of the world, the card is the fallback, not the default. The resolver prefers each market's local rail; instruction-then-confirm rails (M-Pesa STK push, OXXO vouchers, Pix) run on an async-outcome path built for them.
The rails
Cards where cards win, mobile money where it doesn't: M-Pesa in Kenya, Paystack and Flutterwave across Africa, GCash in the Philippines, OXXO cash vouchers in Mexico (top-up only, deliberately below cards — a voucher that confirms in days can't pay for tonight's ride), Pix in Brazil, UPI/Razorpay in India, Moyasar/mada in Saudi Arabia, Stripe for global cards.
Honest integration status — the amber pill system
Five newer adapters are built against recorded protocol behavior but haven't yet cleared a live transaction. They say so in code (integration: 'unverified-adapter'), and the admin shows operators that exact amber status until a real transaction proves the rail. No key configured → the market falls back to cash; a rail error surfaces as a friendly message, never an HTTP 500 to a rider.
A real double-entry ledger underneath
Every settlement, refund, credit note, payout, top-up and promo discount posts as balanced journal lines in the currency's true minor units. DB-level invariants reject corrupting writes. Refunds debit the wallet first, then reverse the gateway leg with compensation on failure — and refuse amounts beyond the max refundable. Payout reconciliation returns driver funds only on confirmed failure: uncertainty never moves money.
Read the amber pills. 5 newer rails declare integration: 'unverified-adapter' in code until they clear a real transaction against the live provider — and the admin panel shows operators exactly that, never a fake "Connected". With no keys configured, every market falls back to cash and nothing crashes. That honesty is a feature you inherit.