Skip to content
CommonLayer
Documentation menu
On this page

ADR-0032: Payout gates are identical for simulated and live runs

Status: Accepted (WP-14). Implemented in crates/commonlayer-funding/src/domain/payout.rs and src/service/payouts.rs.

Context

Paying people is the highest-risk operation of the platform. A simulated mode exists so the payout path can be exercised before any real money moves; if simulated runs skipped gates, the gates themselves would never be exercised before going live.

Decision

  • Execution of a simulated or live payout run requires every gate: the run's mode equals payments.payouts.mode; the provider is configured and reports the payouts capability; the legal entity name, jurisdiction and registration are configured; payments.tax.handling_confirmed = true; the latest reconciliation of each kind is clean and less than 24 h old; a second platform admin approved the run. Simulated runs additionally require a non-production environment.
  • Failed gates are reported by name (payouts.not_enabled).
  • Approving a run requires an interactive session (it is never available to API tokens), and the database refuses an approver equal to the initiator.
  • A dry run is always available, posts nothing, and lists every skipped obligation with its reason (no verified destination, tax status missing, below minimum, held).

Consequences

  • Configuration refuses payments.payouts.mode = "live" without the legal entity fields and a real provider, and refuses simulated payments in production.
  • Live payouts through Stripe Connect are implemented but have only been exercised against a mock server, never against a live Stripe account.