Inkress ADRsarchitecture decisions
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.
← 0032 Currencies hard-partition balances; no in-ledger FX; rates from cached external APIs 0034 Orders are immutable commercial records; money movement lives in separate ledger transactions →