Design a system to handle subscription renewals

Modeling a subscription state machine that decouples billing from access.
States include active, past_due, and cancelled; transitions are payment success, dunning exhaustion, and grace expiry; use idempotent webhooks.
WHAT THIS TESTS: Whether you can model a distributed state machine for recurring billing. Interviewers care about separation of concerns between payment state, subscription state, and entitlement state. They want to see you understand asynchronous events, idempotency, and dunning as a first-class workflow rather than an afterthought.
A GOOD ANSWER COVERS: First, enumerate the core states. Trialing means no payment has been collected yet. Active means the latest invoice was paid or payment is not yet due. Past_due means a renewal payment failed but the account is still within the dunning window or grace period. Unpaid means dunning has exhausted and the grace period has lapsed but the subscription is not yet cancelled. Cancelled means the subscription has been terminated either by the user or by an automated rule. Incomplete and incomplete_expired map to Stripe's initial payment flow where a customer has 23 hours to complete a first payment. Second, map events to transitions. A successful invoice payment moves a subscription from incomplete to active or keeps it active. A failed charge on a renewal invoice moves active to past_due. A dunning retry success moves past_due back to active. Dunning exhaustion plus grace expiry moves past_due to unpaid or directly to cancelled depending on business rules. An explicit cancellation event moves active or past_due to cancelled. Third, separate concerns. The subscription status should not be the sole source of truth for feature access. Maintain an entitlements layer that reads subscription state plus payment state to decide whether to grant access during a grace period. Fourth, handle webhooks idempotently. Payment events can arrive out of order or duplicate, so process invoice paid events with idempotency keys and guard transitions with state preconditions.
COMMON WRONG ANSWERS: Mapping every failed payment straight to cancelled ignores revenue recovery and violates real-world SaaS norms. Conflating invoice status with subscription status leads to race conditions, for example provisioning access based on an open invoice that later fails. Omitting the incomplete state for initial signups misses the 23-hour window where a customer may complete payment asynchronously. Designing a synchronous only flow assumes payment confirmation happens in the same request, which breaks for 3D Secure and other asynchronous methods.
LIKELY FOLLOW-UPS: How would you handle a user who updates their payment method during dunning? How do you safely retry failed payments without violating card network rules? How would you backfill state if a webhook is missed? What happens to annual subscriptions versus monthly when prorating failures?
ONE CONCRETE EXAMPLE: Consider a monthly subscription renewing on the first of the month. On day one, Stripe generates an open invoice and attempts a charge. The card is declined. The subscription moves to past_due. Your dunning schedule retries on day three, day seven, and day ten. During this window the customer retains access because your entitlements service checks both subscription status and a grace timestamp. On day ten the final retry fails. The grace period expires on day fourteen. On day fourteen a scheduled job transitions the subscription to unpaid and revokes entitlements. If the customer pays on day twelve, the invoice paid webhook transitions the subscription back to active and clears the grace timestamp.
Source: docs.stripe.com
Read the original → docs.stripe.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.