ADR-025: Bank Gateway adapter contract — bank-sandbox is a dev adapter; real FIs plug into the same port
- Status: Accepted
- Date: 2026-06-18
- Deciders: Principal Architect, Engineering Lead
- Relates to: ADR-010, ADR-014, ADR-018, ADR-024
Context
DemozPay moves money through partner financial institutions (ADR-014/024). At launch the only "bank" running is the bank-sandbox sandbox (a Go service that simulates Dashen: signed HMAC calls, seeded accounts, async settlement webhooks). Real partners (Dashen, Commercial Bank of Ethiopia, Telebirr, EthSwitch) will follow. The risk is that bank-sandbox's specifics leak into the core and the integration gets coded around the sandbox instead of around a stable abstraction.
Decision
All partner FIs sit behind one Bank Gateway adapter port. The core orchestration depends only on that port — never on a specific bank. bank-sandbox is exactly one adapter (the dev/sandbox implementation); real banks plug into the same port. Adding a partner is: a new adapter package + a registry entry — no other code moves.
- The adapter interface is the contract: initiate transfer, parse/verify inbound settlement webhook, report status. Partner-specific auth (HMAC today, mTLS later), payload shapes, and endpoints live inside the adapter.
- The gateway's enabled partners and per-partner config are environment-driven (e.g.
GATEWAY_ENABLED_PARTNERS,GATEWAY_<PARTNER>_BASE_URL/_SIGNING_KEY); the dev wiring points the Dashen adapter at bank-sandbox, and the production swap is those env vars — no code change. - We architect around the Bank Gateway abstraction, never around bank-sandbox.
Alternatives considered
- Code directly against bank-sandbox / Dashen — rejected: couples the core to one partner's quirks; every new bank becomes a rewrite; testing requires the real partner.
- A single generic "bank client" with per-partner
ifbranches — rejected: partner divergence (auth, settlement model, error taxonomy) makes branching unmaintainable; separate adapters keep each partner's mess contained.
Consequences
- Positive: new partner = new adapter + registry line; bank-sandbox gives deterministic local/e2e testing without a real bank; partner quirks are contained; aligns with the orchestrator model (ADR-024).
- Negative / accepted: an abstraction layer to maintain even while only one real partner exists; the adapter contract must be general enough to fit banks we haven't integrated yet (revisit as real partners reveal divergence).
- Follow-ups: keep bank-sandbox as the canonical sandbox; when the first real partner integrates, validate the port shape against its actual API and widen it if needed (it is internal, so it can evolve).
Revisit when
- The first real partner (e.g. Dashen) reveals a capability the port can't express → widen the adapter contract; partners remain swappable behind it.