How to architect subscription billing systems that handle plan changes, prorations, trials, and failed payments reliably.
Charging a customer monthly sounds simple. But real subscription billing involves plan upgrades, downgrades, prorated charges, trial periods, grace periods, failed payment retries, tax calculation, and invoice generation. Each of these has edge cases that multiply when combined.
This article covers the architectural decisions that make or break a subscription billing system.
Start with a clean separation of concerns:
Plan - defines the product offering (name, price, billing interval, features). Plans are templates, not per-customer records.
Subscription - the relationship between a customer and a plan. Contains the current state, billing cycle dates, and any customer-specific overrides.
Billing Period - a discrete time window within a subscription. Each period generates one invoice.
Invoice - the financial record for a billing period. Contains line items, tax, discounts, and payment status.
Payment - a record of a payment attempt against an invoice. An invoice may have multiple payment attempts if the first one fails.
Customer -> Subscription -> Billing Period -> Invoice -> Payment
|
v
Plan
Keep these as separate models. Flattening subscriptions and invoices into a single table creates a mess when you need prorations, credits, or partial refunds.
The billing cycle engine is the heart of your system. It runs on a schedule (typically daily) and does three things:
For each active subscription where current_period_end <= today:
1. Calculate the next billing period (start date, end date)
2. Calculate the amount (plan price, adjusted for proration/credits)
3. Create an invoice with line items
4. Attempt to charge the customer's payment method
5. If successful: extend the subscription period
6. If failed: enter retry logic
Critical rule: Generate the invoice before attempting payment. This creates an auditable record regardless of whether the charge succeeds or fails. Never charge first and record later.
When a customer changes plans mid-cycle, you need a proration strategy:
Calculate the unused portion of the current plan and credit it, then charge the prorated amount for the new plan:
Days remaining in current period: 15 out of 30
Current plan: 100 EUR/month
New plan: 200 EUR/month
Credit for unused current plan: 100 * (15/30) = 50 EUR
Charge for remaining period on new plan: 200 * (15/30) = 100 EUR
Net charge: 50 EUR
This is fair but adds complexity. You need to track credits and apply them to the next invoice.
Apply the plan change at the next renewal date. Simpler to implement and easier for customers to understand. The downside is that feature access either changes immediately (customer pays old price for new features) or at renewal (customer cannot access new features until they pay).
For most SaaS platforms, apply the feature change immediately but defer the billing change to the next renewal. This gives customers instant access to the new plan while keeping billing predictable. If the new plan is cheaper, credit the difference on the next invoice.
Trials add a state before the first payment:
Trial -> Active -> (Canceled | Past Due | Expired)
Trial expiration handling:
Gotcha: Some customers sign up for trials with invalid payment methods. Validate the payment method at signup (a zero-amount authorization) to catch this early.
Payments fail. Credit cards expire, insufficient funds, temporary bank issues. A good dunning process recovers most of these:
Retry schedule example:
| Attempt | Timing | Action |
|---|---|---|
| 1 | Day 0 (renewal) | Initial charge attempt |
| 2 | Day 3 | Retry + email notification |
| 3 | Day 7 | Retry + stronger warning |
| 4 | Day 14 | Final retry + cancellation warning |
| - | Day 21 | Cancel subscription |
During retries, the subscription enters a "past due" state. Decide whether customers retain access during this period. Most SaaS platforms maintain access for the first 7-14 days, then restrict features.
Update payment method flow: Make it trivially easy for customers to update their card. Include a direct link in every dunning email. Do not make them log in, navigate to settings, and hunt for the billing page.
EU VAT rules for digital services are complex:
Validate VAT IDs against the VIES system. Cache validation results (VIES has downtime) but re-validate periodically.
Store the tax amount, rate, and customer location on each invoice. Tax authorities want to see this breakdown.
Every billing event should produce a proper invoice with:
Generate invoices as PDF and store them permanently. Retroactively changing invoice data is illegal in most jurisdictions. If a correction is needed, issue a credit note and a new invoice.
Build your billing system around events:
subscription.createdsubscription.renewedsubscription.plan_changedsubscription.canceledinvoice.createdinvoice.paidinvoice.payment_failedpayment.succeededpayment.failedInternal consumers react to these events: feature provisioning, email notifications, analytics, and accounting integrations. External consumers (your customers' systems) receive webhooks for the same events.
Subscription billing rewards careful architecture. Get the data model right, handle prorations cleanly, build robust dunning, and generate proper invoices. The complexity is finite and well-understood. Rush it, and you will spend years patching edge cases.
Whether you're modernizing your infrastructure, navigating compliance, or building new software - we can help.
Book a 30-min Call