ConceptsMoney invariants

Money invariants

Three rules hold everywhere money is represented or moved in Flightdeck. They are not per-module conventions — they are founding rules.

Integer cents, everywhere

All monetary amounts are integer cents. Conversion to/from a decimal currency representation happens only at provider boundaries (a payment processor’s API, a display string) — never inside domain logic, never in a database column.

Double-entry, immutable, append-only

The accounting module is the platform-wide, per-tenant immutable double-entry ledger — the source of truth for money. The invariant that every transaction’s entries sum to exactly zero is enforced at two independent layers:

  1. Domain — checked in code before any row is written.
  2. Database — a DEFERRABLE constraint trigger re-checks the same invariant at COMMIT, directly against accounting.ledger_entries. This is an independent backstop, not decoration: it catches a bug the domain layer’s check didn’t.

Entries and transactions are immutable and append-only, enforced by DB triggers. Corrections never rewrite history — they happen via reverse, which posts a compensating transaction with every entry inverted and links the two transactions together.

Other modules that move money — cash (store-credit ledger), affiliates (commission payouts), merchant-billing (B2B invoicing) — post to accounting the same way: a second in-process instance of the accounting module, constructed from the same dependencies, so the ledger stays the single source of truth even though the caller is a different module.

Deterministic idempotency

Idempotency keys are deterministic, derived from the business event — never randomUUID. A retried write (a webhook redelivery, a network retry, an operator double-click) with the same key replays the original outcome instead of double-posting. This applies across the money-moving modules: cash grants/spends, affiliates commissions, checkout’s payment-intent creation, and the accounting ledger itself.

Fail-closed on the way in

A debit that would overdraw an account (e.g. a cash spend) is rejected before any row is written — not caught and reversed after the fact. A refund tombstones its order before any reversal work runs, and a database trigger makes it impossible to insert a new commission against a tombstoned order — closing the “late redelivery resurrects a reversed commission” race at the database layer, not just in application code.