Hook
When a customer upgrades from your Starter plan to Pro mid-cycle, billing gets tricky—do you charge them for the full month, or adjust for the time they've already paid? Proration explained: a practical Payments & Stripe guide walks you through the mechanics and code you need to handle fair billing when plans change. Without it, you either leave money on the table or frustrate customers with unexpected charges.
What is proration and why it matters
Proration is the practice of adjusting charges when a customer changes their subscription plan mid-cycle. Instead of forcing them to wait until renewal, you calculate what they've already paid for the old plan and credit or charge the difference for the new one.
Think of it like a gym membership. If you're halfway through a month on the basic plan and upgrade to premium, the gym doesn't make you pay for two full months. They credit back half the basic fee and charge you the pro-rata portion of the premium rate for the remaining days. Stripe handles this automatically, but you need to understand the mechanics to implement it correctly.
From a business perspective, proration removes friction at upgrade moments—often when customer commitment is highest. From an engineering perspective, it's a calculation problem: figuring out daily rates, time elapsed, and credit amounts.
Credit unused time on the old plan
When a customer downgrades or changes plans, they've already paid for days they won't use. Stripe calculates this credit automatically, but understanding it helps you debug invoices and explain charges to customers.
The formula is straightforward:
Credit amount = (plan price / days in billing period) × (remaining days on old plan)
Let's say a customer paid $120 for a 30-day billing cycle on Plan A. Halfway through (day 15), they change plans. Stripe calculates:
- Daily rate: $120 ÷ 30 = $4/day
- Remaining days: 30 - 15 = 15 days
- Credit: $4 × 15 = $60
This $60 credit appears as a negative line item on the next invoice. Stripe manages this behind the scenes when you use the proration_behavior parameter, but you can also retrieve the calculation from the Invoice object for transparency.
Here's how to inspect prorated credits in your system:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const invoice = await stripe.invoices.retrieve('in_1234567890');
// Line items include proration credits
invoice.lines.data.forEach((line) => {
if (line.proration) {
console.log(`Proration adjustment: ${line.description}`);
console.log(`Amount: ${line.amount} (${line.currency})`);
}
});
In production, you'll want to store this in your database so your support team can explain charges and your accounting system can reconcile properly.
Charge remaining time on the new plan
After crediting unused time, Stripe charges for the new plan's pro-rata amount. Again, the daily rate applies to the remaining days in the cycle.
Using the same example: Plan B costs $240/month. For the remaining 15 days:
- Daily rate: $240 ÷ 30 = $8/day
- Remaining days: 15
- Charge: $8 × 15 = $120
The customer sees: –$60 (credit) + $120 (new plan) = $60 net charge for the upgrade. This typically appears as a single line on the invoice with the new plan name and the pro-rata amount.
Stripe's behavior here depends on your proration_behavior setting, which we'll cover next. If you're manually managing this (not recommended), you'd calculate these amounts and create separate invoice items. But Stripe's built-in approach is cleaner and less error-prone.
The proration_behavior parameter controls the math
Stripe offers three proration behaviors when you update a subscription. This is the critical lever that controls whether and how proration happens.
| Behavior | What happens | Use case |
|---|---|---|
create_prorations |
Default. Credits and charges pro-rata amounts immediately. | Most SaaS products. Fair and transparent. |
none |
No proration. New plan starts at next billing cycle. | Simple models or when you want to avoid partial charges. |
always_invoice |
Creates invoice immediately, even if no proration. | Rare. Useful if you want immediate invoicing regardless of cycle. |
Here's how to update a subscription with proration:
const subscription = await stripe.subscriptions.update(
'sub_1234567890',
{
items: [
{
id: 'si_1234567890', // subscription item to replace
price: 'price_newplan', // new price ID
},
],
proration_behavior: 'create_prorations', // explicit (also default)
}
);
console.log('Subscription updated with proration.');
console.log(`Next billing: ${new Date(subscription.current_period_end * 1000)}`);
If you want to defer the new plan until the next cycle without any mid-cycle charges:
const subscription = await stripe.subscriptions.update(
'sub_1234567890',
{
items: [
{
id: 'si_1234567890',
price: 'price_newplan',
},
],
proration_behavior: 'none',
billing_cycle_anchor: 'now', // changes cycle anchor; use with caution
}
);
The none behavior doesn't charge immediately—it just queues the plan change for the next cycle. This can surprise customers if they don't understand they won't be charged until renewal. Be explicit in your UI about the timing.
Preview invoices before changes
Showing customers what they'll be charged before they confirm is good UX and reduces support requests. Stripe's upcoming_invoice endpoint lets you preview the exact invoice without committing the change.
This is invaluable: you can show the credit, the new charge, and the net amount in your UI before the customer clicks "Confirm upgrade."
const preview = await stripe.invoices.retrieveUpcoming({
customer: 'cus_1234567890',
subscription: 'sub_1234567890',
subscription_items: [
{
id: 'si_1234567890',
price: 'price_newplan', // simulated new price
},
],
subscription_proration_behavior: 'create_prorations',
});
console.log('Upcoming invoice preview:');
preview.lines.data.forEach((line) => {
console.log(`${line.description}: ${(line.amount / 100).toFixed(2)} ${preview.currency.toUpperCase()}`);
});
console.log(`\nTotal: ${(preview.total / 100).toFixed(2)} ${preview.currency.toUpperCase()}`);
In your front-end, you'd call this endpoint when a customer selects a new plan, display the breakdown, and then proceed with the update only when they confirm. This prevents surprises and builds trust.
A few notes: the preview includes the next invoice (if billing is due), so the total might include both the current subscription and the new charges. Always parse the lines array to explain what each line is.
Common mistakes
Forgetting to specify proration_behavior
The default is create_prorations, which is usually what you want. But if you assume it and your code context changes, you might accidentally charge customers without expecting to. Always be explicit in your code: state your intention.
Not previewing before charging
Skipping the preview step means customers see unexpected charges on their invoice. The extra API call is cheap and prevents churn. Always preview.
Confusing billing cycle anchor changes with proration
If you use billing_cycle_anchor: 'now' with proration_behavior: 'none', you're both deferring the plan change and resetting the customer's billing date. This can lead to longer or shorter cycles than expected. Use anchoring sparingly and document why you're doing it.
Not storing proration details
Stripe calculates proration correctly, but if a customer disputes a charge six months later, you need to explain it. Log the prorated amounts and invoice details to your database when plan changes happen.
Ignoring currency and rounding
Stripe amounts are in the smallest currency unit (cents for USD). When displaying or calculating, always divide by 100 for display and be careful with rounding. A penny difference across thousands of customers adds up.
Key takeaways
- Proration credits unused time on the old plan and charges the pro-rata amount on the new plan, creating a fair mid-cycle transition.
proration_behavior: 'create_prorations'handles the calculation automatically;'none'defers to the next cycle.- Always preview upcoming invoices with
stripe.invoices.retrieveUpcoming()before updating a subscription, and show customers the exact charges. - Store proration details in your database for auditing, support explanations, and accounting reconciliation.
- Be explicit about billing cycle timing in your UI—customers often don't realize when they'll be charged, and clarity reduces friction.






Comments
Loading comments…