Overview / Ledger / money
ADR 0033
Merchant payouts are reviewable, balance-validated financial requests materialized by a worker
AcceptedLedger / money
Context
Merchants withdraw their accumulated balance. A withdrawal must not overdraw the wallet, should be reviewable/gateable, must have a defined timing, and must land in the ledger exactly once. The naive version — debit the ledger synchronously when the merchant clicks "withdraw" — couples the request to ledger writes and payment-rail timing and offers no review point.
Decision
Model a payout as a financial_requests row — a reviewable request, validated against the merchant's available balance (ADR 0031), with a plan-driven T+N due date — that a background worker materializes into the ledger when processed. The payout endpoint does not write the ledger directly.
- Request, then materialize. Creating a payout records a request and reserves the funds as a pending-payout overlay (ADR 0031), so a second concurrent payout sees reduced availability and cannot overdraw. A worker later books the actual ledger debit and advances the request.
- Balance-validated and gated. The request is checked against available balance at creation and can be reviewed before it settles; timing (T+N) comes from the merchant's plan.
Consequences
- Positive: withdrawals cannot overdraw (reservation is immediate) and can be reviewed; the request is a durable record distinct from the money movement; scheduling is plan-driven.
- Neutral / to revisit: a payout is eventually consistent with the ledger (request → reservation → worker → ledger entry); consumers must read "available" (ADR 0031), not raw balance, to see the effect immediately.
- Neutral / to revisit: materialization runs in commerce-worker (ADR 0014/0019), so payout completion depends on the job runtime being healthy.
Alternatives considered
- Debit the ledger synchronously at the payout endpoint: rejected — no review gate, and it couples the request UX to the ledger write and the payout rail's timing; a request + worker separates intent from execution.
- Validate against raw balance without reserving: rejected — two concurrent payouts could each pass the check and overdraw; reserving the funds at request time (ADR 0031) closes the race.