Module 4: Subscriptions and Invoicing
Recurring revenue is the engine of most online businesses — and Stripe's billing engine is where the sharp edges live: trials, proration, metered usage, invoice states, dunning, and the difference between canceled, paused, and past due. Get these right once and they run silently for years. Get them wrong and you bleed revenue through silent churn you never see.
Creating subscriptions: trials, schedules, and anchors
The minimal subscription is three objects: a Customer, a Price (the thing they pay for, e.g. price_abc123 for $29/month), and the Subscription tying them together. Create the customer first, attach a payment method, then subscribe:
# curl: create a customer
curl https://api.stripe.com/v1/customers \
-u sk_test_YOUR_KEY: \
-d email="sam@example.com" \
-d name="Sam Rivera"
# curl: create a subscription with a 14-day trial
curl https://api.stripe.com/v1/subscriptions \
-u sk_test_YOUR_KEY: \
-d customer="cus_ABC123" \
-d "items[0][price]"="price_XYZ789" \
-d trial_period_days=14// Node.js: same, with a billing anchor
const subscription = await stripe.subscriptions.create({
customer: 'cus_ABC123',
items: [{ price: 'price_XYZ789' }],
trial_period_days: 14, // no card charged until day 15
billing_cycle_anchor: Math.floor(Date.now() / 1000) + 30 * 86400,
metadata: { plan: 'pro', source: 'checkout' },
});Trials come in two flavors. Trial without a card (Stripe's default when no payment method is attached) maximizes signups but converts poorly — expect 20–30% trial-to-paid. Trial with a card collected up front (Checkout's trial mode does this) converts at 50–60% but fewer people start. There is no universally right answer; test both against your audience. Note trial_end accepts an explicit Unix timestamp if you want a trial ending on a fixed date rather than N days out.
billing_cycle_anchor controls when the billing period starts. Default is now; set it to align all customers to the 1st of the month for cleaner accounting, and Stripe prorates the stub period automatically. For phased pricing — "$19 for three months, then $49" — use subscription schedules (Dashboard → Billing → Subscriptions → select → Actions → Create schedule, or the subscription_schedules API): define phases with different prices and start/end dates, and Stripe transitions the subscription between them without you touching it. Schedules are also how you do planned upgrades, seasonal pricing, and grandfathering.
Edge cases that bite: a trial ending on a weekend still bills; webhooks fire customer.subscription.trial_will_end three days before — use it to send your "trial ends soon" email, which measurably lifts conversion. And once a subscription exists, its Price is fixed; changing what a customer pays means swapping the subscription item's price (with proration, next lesson) or moving them to a new subscription.
You want new signups to pay $19/month for the first 3 months, then automatically move to $49/month. Which Stripe feature does this?
Proration: the math of mid-cycle changes
When a customer upgrades or downgrades mid-cycle, proration is the credit-and-charge math that keeps billing fair. Stripe prorates to the second: it credits the unused portion of the old plan and charges the remaining portion of the new plan. The formula, for an upgrade from $30/mo to $50/mo exactly halfway through a 30-day cycle: credit = $30 × 15/30 = $15; charge = $50 × 15/30 = $25; net invoice = $25 − $15 = $10. Stripe generates a proration invoice automatically — you can preview it before committing with the upcoming-invoice endpoint.
// Preview first — never surprise a customer with a charge
const preview = await stripe.invoices.retrieveUpcoming({
customer: 'cus_ABC123',
subscription: 'sub_DEF456',
subscription_items: [{ id: 'si_GHI789', price: 'price_NEWPLAN' }],
});
console.log(preview.amount_due); // cents — what the proration invoice would bill
// Then apply with explicit proration behavior
const sub = await stripe.subscriptions.update('sub_DEF456', {
items: [{ id: 'si_GHI789', price: 'price_NEWPLAN' }],
proration_behavior: 'create_prorations', // default: invoice the difference now
});proration_behavior has three settings. create_prorations (default) generates the prorated invoice immediately. none changes the plan with zero invoice — the customer keeps paying the old amount until the next cycle; useful for downgrades where you want to avoid refund math, or when you credit manually. always_invoice forces an immediate invoice even if the net is zero or negative (the default skips invoicing when the proration is purely a credit). A common pattern: upgrades use create_prorations (charge now), downgrades use none (new price at renewal) — customers perceive downgrades-with-refunds as buggy, so most subscription businesses apply the lower price next cycle and say so in the UI.
Two pitfalls. First, proration invoices respect collection_method: if the subscription is send_invoice, the proration becomes a payable invoice rather than an auto-charge. Second, rapid plan changes stack proration line items — a customer flipping plans three times in a week gets an invoice with six confusing lines. If your UI allows instant self-serve changes, debounce them or batch: apply the change at period end via subscription schedules instead.
A customer upgrades from $30/mo to $50/mo exactly halfway through a 30-day cycle. What does Stripe bill on the proration invoice?
Metered billing and the Invoicing API
Metered billing charges by usage — API calls, seats consumed, minutes used. Setup: create a Price with recurring.usage_type: 'metered' and an aggregate_usage strategy (sum, max, last_during_period, or last_ever). Your application then reports usage against the subscription item:
# Report 250 API calls against the subscription item
curl https://api.stripe.com/v1/subscription_items/si_GHI789/usage_records \
-u sk_test_YOUR_KEY: \
-d quantity=250 \
-d "timestamp=$(date +%s)" \
-d action=increment// Node.js equivalent
await stripe.subscriptionItems.createUsageRecord('si_GHI789', {
quantity: 250,
timestamp: Math.floor(Date.now() / 1000),
action: 'increment', // or 'set' to overwrite the period total
});At period end Stripe sums the usage and bills it. Critical detail: usage records are immutable within a short reporting window and late records can land in the next period — report usage promptly, and use action: 'set' only when your counter is the period total. Tiered metered pricing ("first 1,000 calls free, then $0.01 each") uses graduated Price tiers on the metered price object.
The Invoicing API covers everything outside subscriptions: one-off invoices, deposits, custom line items. The lifecycle is strict: draft → finalize → send/collect. Create a draft invoice with line items, review it, then finalizeInvoice — finalization locks the invoice (it gets a number, PDF, and hosted URL) and it can never be edited again, only voided or credited. Then either sendInvoice (emails it; days_until_due, default 30, sets the due date) or let collection_method: 'charge_automatically' charge the customer's card immediately via auto_advance.
// Draft → finalize → send
const invoice = await stripe.invoices.create({
customer: 'cus_ABC123',
collection_method: 'send_invoice',
days_until_due: 14,
auto_advance: true, // finalizes + attempts payment on schedule
});
await stripe.invoiceItems.create({
customer: 'cus_ABC123',
amount: 15000,
currency: 'usd',
description: 'Strategy workshop',
});
const final = await stripe.invoices.finalizeInvoice(invoice.id);
await stripe.invoices.sendInvoice(final.id);Invoice settings live at Dashboard → Settings → Billing → Invoices: default days_until_due, footer text, logo, and whether auto_advance finalizes drafts automatically. Pitfall: voiding a finalized invoice does not refund money already collected — voiding is for unpaid invoices; collected ones need a credit note or refund.
You finalized an invoice and the customer already paid it, but the amount was wrong. What is the correct fix?
Dunning: recovering failed payments
About a quarter of subscription churn is involuntary — cards expire, hit limits, get replaced after fraud. Dunning is the system that wins that money back. Stripe's Smart Retries uses machine-learning timing across its network to retry failed payments at the moments most likely to succeed, over a schedule you configure at Dashboard → Settings → Billing → Revenue recovery. The default cadence retries over roughly three weeks before marking the invoice uncollectible.
Your code hooks in via the invoice.payment_failed webhook. When it fires, the invoice is still open and the subscription is still active — do not cancel anything yet. Your handler should: log the decline code, trigger your email sequence, and surface a prominent "update payment method" prompt. When retries exhaust, Stripe fires invoice.payment_failed with the attempt count at the max and eventually customer.subscription.updated with status past_due.
The highest-converting recovery tool is the customer portal — a Stripe-hosted page where customers update cards, change plans, and download invoices without you building anything. Create a portal session server-side and redirect:
const session = await stripe.billingPortal.sessions.create({
customer: 'cus_ABC123',
return_url: 'https://yoursite.com/account',
});
res.redirect(session.url); // customer updates their own cardConfigure what the portal allows at Dashboard → Settings → Billing → Customer portal. Pair it with your email sequence: day 1 ("payment failed, update here"), day 3, day 7 with the portal link pre-built. Businesses that run portal-link dunning emails recover 30–50% of failed payments.
Know your states: active (paying), trialing (in trial), past_due (payment failed, retries running — access usually continues), paused (you or the customer paused collection; pause_collection with behavior mark_uncollectible, keep_as_draft, or void), and canceled (ended; access per your cancel_at_period_end choice). Pause beats cancel for retention — a paused subscription resumes with one API call and no re-checkout; a canceled one means winning the customer back from zero.
Your turn: Design your subscription tiers in writing. Name each tier, set its price and billing interval, decide your trial length (and whether you collect a card up front), choose your proration rule for upgrades vs downgrades, and sketch your dunning plan: how many emails, on which days, and what the final email says. Be specific — this is your billing blueprint.
Saved.