Skip to content

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).