Part 8 · 1 chapters · ~11 min
Payment Rails per Country
Moving money on each country's rails behind one journey: how NIP, M-Pesa, GhIPSS, Pix, UPI and SEPA Instant address recipients, a shared transfer model with seven states, rail adapters that validate, pre-check, submit and track, deliberate status mapping with unknown as a first-class state, and limits, hours and fees in configuration.
17
One journey, many rails
code
// the shared model the core and the app speak
type Destination =
| { kind: 'bank_account'; country: 'NG' | 'GH'; bankCode: string; accountNumber: string }
| { kind: 'mobile_wallet'; country: 'KE'; msisdn: string }
| { kind: 'alias'; country: 'BR' | 'IN'; alias: string }
| { kind: 'iban'; country: string; iban: string };
type TransferState = 'draft' | 'submitted' | 'pending' | 'completed' | 'failed' | 'reversed' | 'unknown';
interface RailAdapter {
validate(d: Destination): Result<void, FieldError[]>;
resolveRecipient(d: Destination): Promise<{ name: string } | { notFound: true }>; // name enquiry, alias lookup
submit(t: Transfer, idempotencyKey: string): Promise<TransferState>;
status(ref: string): Promise<TransferState>; // for unknown and pending
mapCode(code: string): { state: TransferState; messageKey: string };
}| rail response (illustrative) | shared state | user sees |
|---|---|---|
| success, settled | completed | Sent |
| accepted, awaiting settlement | pending | Pending: the bank has not confirmed yet |
| timeout or no response | unknown → status enquiry | Checking with the bank. Do not send it again. |
| invalid account / wallet not found | failed (before debit) | No money left your account. Check the details. |
| failed after debit | reversed (after refund) | Refund on the way |
verify against the rail's documentation
The exact codes, limits and confirmation behaviour of each rail change, and the ones above are illustrative. The design point holds regardless: each code is mapped deliberately, reviewed with operations, and covered by a test, and "unknown" is never collapsed into success or failure.
PAYMENT RAILS PER COUNTRY
one transfer journey on top of NIP, M-Pesa, GhIPSS, Pix, UPI and SEPA Instant: a common model, rail adapters and status mapping
swipe the figure sideways, or tap expand for full screen
1/6
the rails
The rails differ: Nigeria's NIBSS Instant Payment (NIP) addresses a bank account by bank code and 10-digit NUBAN, with a name enquiry first; Kenya's M-Pesa addresses a phone number in a mobile-money wallet; Ghana has GhIPSS Instant Pay; Brazil's Pix uses keys (tax id, phone, email or random); India's UPI uses virtual payment addresses; the EU has SEPA Instant with IBANs.