The Subscription State Machine — Lifecycle, Proration, and the Edges Between

A subscription looks like a row with a plan and a date. It behaves like a state machine with a dozen states, transitions triggered by humans, payments, and the calendar, and money consequences on nearly every edge. Teams that model the lifecycle explicitly handle upgrades, trials, and cancellations as transitions with rules; teams that don't accumulate booleans — `is_trial`, `is_paused`, `cancel_at_period_end` — until no one can say what state a customer is actually in.

Posts 2–3 built prices and quantities. This post builds the object that binds them to a customer over time — and the arithmetic of changing that binding mid-period.

The states, and the discipline of naming them

The core lifecycle is small; its value is in being explicit:

Every transition answers four questions: who may trigger it, what happens to money (charge, credit, nothing), what happens to entitlements, and what happens to the period clock. Writing those four answers per edge is the design work.

Proration: the arithmetic of mid-period change

Change a subscription mid-period and someone owes someone money. A customer upgrading on day 10 of 30 has paid for 20 days of the old plan they won’t use and owes 20 days of the new plan. Proration computes both: credit the unused remainder of the old price, charge the remainder-period at the new price, both proportional to time left.

The engineering content is in the policies around the arithmetic, each a decision to make once and encode:

Scheduled change: the lifecycle’s time dimension

Half the lifecycle’s complexity is that changes take effect later: cancel at period end, downgrade at renewal, a price-version migration next anniversary (post 2’s versioning meeting the calendar). Model these as scheduled transitions — first-class records of (what changes, when, triggered by whom) — rather than flags interpreted by a nightly job. The payoff: the current state and the future state are both queryable (“what will this account look like on Dec 1?”), support can see and cancel pending changes, and the renewal job becomes a pure executor of declared intent instead of a nest of conditional policy. Scheduled transitions are also where testing (post 7) earns its clock-simulation machinery — nearly every bug in this post’s territory is a “what fires at the boundary?” bug.

The takeaway: a subscription is a state machine — trialing, active, past-due, paused, canceled (now vs at-period-end), ended — and every edge needs four explicit answers: trigger, money effect, entitlement effect, clock effect. Mid-period change is proration — credit the unused old, charge the remaining new — governed by encoded policies for settlement timing, upgrade/downgrade asymmetry, rounding, seats, and the usage split that justifies separating rating from metering. Model deferred changes as first-class scheduled transitions, making future state queryable and the renewal job an executor of intent — because in billing, most states are really appointments with the calendar.

Key takeaways

Further reading

Sources & References

the principle under every mid-period adjustment