ADR-0001: Phase 1 uses debit-as-authorisation on the existing wallet API
Status: Accepted · Date: 2026-10-19
Context
Today: GET /balances, then decide, then debit. That is a check-then-act race (two payments both read
₦100 and both approve ₦80), and it happens on a healthy system. Legacy systems also debit the same
accounts, so a local reservation ledger cannot make a stale balance conservative. The wallet team
cannot ship a new API today. The existing POST /debits is atomic and funds-checked (A1), dedups on
reference (A2), and supports lookup by reference (A3).
Decision
The wallet debit is the authorisation. POST /debits {reference: paymentId}:
201 → approve, 409 INSUFFICIENT_FUNDS → decline. We never use the balance read to decide.
Ambiguous timeouts are resolved by lookup inside the deadline. Otherwise we decline (retryable) and
auto-reverse with a compensating credit after T_settle.
Options considered
| Option | Verdict |
|---|---|
| Read balance + local reservation ledger | ✗ Unsafe: legacy debiters make the estimate non-conservative |
| Read + per-account serialisation in our service | ✗ Only protects against our own concurrency |
| Funds-checked debit as the decision | ✓ The wallet enforces the invariant atomically |
| Wait for holds | ✗ Not available now, and the bug is live |
Consequences
- + The race is gone even with legacy debiters. No new wallet API is needed.
- − Cancelling means a compensating credit (debit and credit both appear on the statement), not a clean release.
- − Depends on A1–A4. These are verified by contract tests against the stub and confirmed with the Wallet team (OQ-1, OQ-2).