Billing State Machine Foundation — Event Ledger, Fourteen-Arrow Morphism & Admission Control
Why This Matters
Subscription billing is the one domain where SpeyBooks is not its own authority: Stripe’s billing engine owns the subscription’s status, and the local record is a mirror. Mirrors drift, webhooks arrive twice or out of order, and a payment provider’s vocabulary evolves underneath you. This release gives the billing mirror the same discipline the financial kernel already has: a closed set of declared state transitions enforced at the database, an append-only event record with exactly-once processing, and a refusal path for anything the system does not support — refused with evidence, never with an error loop. The first real subscription charges land in September; the state machine and its proofs are in place before a single card gets the chance to decline.
Billing State Machine
-
Five subscription states, Stripe’s vocabulary verbatim The local record now carries exactly the states Stripe can assert for a SpeyBooks subscription — trialing, active, past_due, unpaid, and canceled — plus “no subscription” for organisations that have never checked out. No translation layer sits between the payment provider and the record: what Stripe says is what is stored, and friendlier wording is the user interface’s job.
-
Fourteen declared transitions, enforced at the database Every legal path through the subscription lifecycle — trial converting to paid, a failed charge entering its grace window, retries exhausting, cancellation, and resubscription — is a declared transition. Anything outside the declared set is rejected at the database layer before the write lands, including attempts to erase a status or to create an organisation already carrying one. Resubscription after cancellation is permitted only alongside a genuinely new subscription, so a cancelled record cannot be quietly revived. (Proof anchor: database trigger, migration 100, with an in-transaction self-test witnessing a rejected illegal transition before the migration committed. The companion test layer asserting the application’s transition table matches the database arrives with the next release in this series.)
-
Exactly-once event processing Every Stripe webhook event is recorded in a dedicated event ledger with a uniqueness guarantee at the database layer: duplicate deliveries cannot apply a change twice, and each event’s outcome — applied, discarded as stale, no-op, or unsupported — is named on the record. (Proof anchor: database uniqueness constraint, migration 099.)
-
Admission control for unsupported states Stripe statuses SpeyBooks does not support never reach the record. They are refused with a receipt in the event ledger and a logged alert, while Stripe receives a normal acknowledgment — a refusal must never invite an infinite retry. The record never lies and never fights its authority.
-
Cancellation flag hardened The cancel-at-period-end flag is now constrained at the database to states where the payment provider can legitimately assert it, including a payment-failure grace window — deliberately wider than the obvious rule, because a constraint that rejects a legitimate provider assertion converts one webhook into a permanent failure.
Operational Impact
- Undeclared subscription state transitions are impossible at the database layer, regardless of which code path attempts them
- Duplicate webhook deliveries are structurally incapable of double-applying a change
- An unrecognised subscription status produces an evidence trail and an alert instead of a retry loop
- Out-of-order delivery has its substrate in place: each event’s provider timestamp is recorded as the ordering authority for the staleness checks shipping next in this series
- All three schema changes shipped with rehearsal runs against live data and in-transaction self-verification before commit
Files Changed
Backend:
- Three schema migrations (099–101) — event ledger, state vocabulary and transition enforcement, and cross-reference documentation
- Webhook handling — admission control ahead of all state writes
Frontend / Docs site: None
Known Issues
- The grace-window and suspension behaviours described by the state machine are enforced for state transitions but not yet surfaced in the billing page interface; the banner variants ship later in this series.
The state machine is the foundation; the series continues with the handler and test layers that complete its proofs, then the billing page that lets customers see — and leave — their subscription in two clicks.