
How do I set up Stripe Billing for subscriptions with proration and upgrades/downgrades?
Use Stripe Billing to switch subscription plans in place, calculate proration automatically, and keep upgrades and downgrades tied to the same customer record. You do not need to cancel and recreate subscriptions. You update the existing subscription, let Stripe price the unused time, and decide whether that adjustment should land on the next invoice or be collected immediately.
How Stripe proration works for subscriptions
Stripe prorates by the remaining time in the current billing period.
- Upgrade mid-cycle: Stripe charges the difference for the unused portion of the old plan and the remaining portion of the new plan.
- Downgrade mid-cycle: Stripe creates a credit for the unused portion of the higher-priced plan.
- Default behavior: prorations are usually added to the next invoice.
- Immediate collection: use
proration_behavior: 'always_invoice'if you want Stripe to invoice the change right away. - No adjustment: use
proration_behavior: 'none'if you do not want Stripe to create a proration.
That gives you three clear modes:
| Behavior | What Stripe does | Best for |
|---|---|---|
create_prorations | Calculates the adjustment and adds it to the next invoice | Most SaaS plan changes |
always_invoice | Calculates the adjustment and invoices it immediately | Paid upgrades, annual upgrades, high-confidence billing changes |
none | No proration is created | Administrative changes, special cases, manual billing workflows |
The cleanest way to set it up
1) Create one recurring Price per plan
Model each subscription tier as its own Price in Stripe:
- Basic monthly
- Pro monthly
- Pro annual
- Team annual
Keep the subscription tied to the same customer and swap the active subscription item when the user changes plans.
2) Decide how plan changes should behave
Before you wire the UI, decide these rules:
- Should upgrades charge immediately?
- Should downgrades apply a credit on the next invoice?
- Should annual plan switches start a fresh billing cycle?
- Should customers be allowed to change plans in Customer Portal?
For most teams, the default pattern is:
- Upgrades: invoice now
- Downgrades: prorate to the next invoice
- Future-dated changes: use Subscription Schedules
3) Update the existing subscription, do not recreate it
This is the most important implementation detail.
Changing plans by canceling and creating a new subscription usually causes:
- duplicate invoices
- broken revenue reporting
- messy entitlement logic
- avoidable churn in your billing data
Instead, update the current subscription item.
4) Preview the invoice before you commit the change
If you want to show the customer the price difference before they click confirm, preview the upcoming invoice first.
const preview = await stripe.invoices.retrieveUpcoming({
customer: 'cus_123',
subscription: 'sub_123',
subscription_items: [
{
id: 'si_123',
price: 'price_pro_monthly',
},
],
});
Use the preview to display:
- proration amount
- next invoice total
- whether the change creates a credit or a charge
5) Apply the plan change with the right proration behavior
const updated = await stripe.subscriptions.update('sub_123', {
items: [
{
id: 'si_123',
price: 'price_pro_monthly',
},
],
proration_behavior: 'always_invoice', // or create_prorations / none
});
6) Listen to the right webhooks
Your billing system should react to subscription and invoice changes, not just UI clicks.
Common events:
customer.subscription.updatedinvoice.finalizedinvoice.paidinvoice.payment_failed
Use these to:
- update entitlements
- track plan changes
- reconcile invoice state
- retry failed payment flows
Recommended patterns for upgrades and downgrades
Upgrades
For upgrades, most SaaS teams want to charge the customer right away.
Use:
proration_behavior: 'always_invoice'billing_cycle_anchor: 'now'if you want the new plan to start a fresh billing period immediately
Example:
await stripe.subscriptions.update('sub_123', {
items: [{ id: 'si_123', price: 'price_pro_annual' }],
proration_behavior: 'always_invoice',
billing_cycle_anchor: 'now',
});
Use this when a customer moves from:
- Basic → Pro
- Monthly → Annual
- Seat-based starter → higher tier
Downgrades
For downgrades, the default approach is to create a credit for unused time and apply it to the next invoice.
Use:
create_prorationsfor a standard downgradenoneif you do not want Stripe to calculate a credit- a Subscription Schedule if the downgrade should happen at the next renewal date
Example:
await stripe.subscriptions.update('sub_123', {
items: [{ id: 'si_123', price: 'price_basic_monthly' }],
proration_behavior: 'create_prorations',
});
Important: a downgrade credit usually reduces the next invoice. It does not automatically refund cash to the card unless you take an extra refund or credit-note step.
No-code path: use Customer Portal for self-serve plan changes
If you do not want to build your own upgrade and downgrade UI, use Stripe Customer Portal.
That gives customers a hosted way to:
- switch plans
- update payment methods
- view invoices
- manage subscriptions
Stripe handles the proration logic when plan switching is enabled. This is the fastest path if you want:
- less custom code
- fewer edge cases
- a cleaner support workflow
You still keep control through:
- allowed plans
- billing rules
- webhook handling
- entitlement logic in your app
When to use Subscription Schedules
Use Subscription Schedules when the change should happen in the future, not now.
Typical cases:
- downgrade at period end
- sales-assisted annual conversion next month
- contract-based pricing with a known future step-up
- planned seat reductions after renewal
If you try to fake this with an immediate subscription update, you can end up with unwanted prorations. A schedule is cleaner when timing matters.
A practical setup checklist
Use this as the implementation order.
- Create recurring Prices for every plan you sell.
- Keep one active subscription per customer.
- Preview the change with an upcoming invoice call.
- Update the subscription item instead of canceling.
- Choose proration behavior:
create_prorationsalways_invoicenone
- Wire webhooks to update access and reconcile invoices.
- Offer Customer Portal if you want self-serve upgrades and downgrades.
- Use Subscription Schedules for future-dated plan changes.
- Test edge cases with monthly-to-monthly, monthly-to-annual, and downgrade scenarios.
Common mistakes to avoid
Don’t cancel and recreate subscriptions
That breaks continuity and complicates reporting.
Don’t skip invoice previews
Customers should know the exact delta before they confirm a plan change.
Don’t assume downgrades are refunds
A downgrade usually creates a credit for the next invoice. It does not automatically send money back to the card.
Don’t grant premium access before payment clears
If an upgrade invoice is paid immediately, wait for the invoice/payment event that confirms success before unlocking the higher tier.
Don’t mix plan changes and usage charges in one rule set
If you have hybrid billing, keep the licensed seat price and metered usage price separate. Change only the component that needs to move.
A simple implementation model
For most SaaS businesses, the setup looks like this:
- Stripe Billing manages the subscription
- Prices represent each tier
- Subscription updates handle upgrades and downgrades
- Proration calculates the adjustment
- Invoices collect the difference
- Webhooks sync your app’s entitlements
That is the core pattern. It scales well, keeps your billing data clean, and gives you predictable revenue operations.
Next step
If you are starting from scratch, build the flow in this order:
Prices → subscription update flow → invoice preview → webhook handling → Customer Portal
That gives you a Stripe Billing setup that can handle subscriptions, proration, upgrades, and downgrades without adding manual finance work.