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 stateuser sees
success, settledcompletedSent
accepted, awaiting settlementpendingPending: the bank has not confirmed yet
timeout or no responseunknown → status enquiryChecking with the bank. Do not send it again.
invalid account / wallet not foundfailed (before debit)No money left your account. Check the details.
failed after debitreversed (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.