I was doing some research on how upgrading subscriptions works on Stripe. Stripe's default system is to charge the customer for the proration cost in their next invoice, along with the fee for the upcoming billing cycle (docs).
For example, if I upgraded my subscription to some service from the standard plan ($5/month) to the premium plan ($10/month) at the midpoint of my billing cycle, my invoice on the first of the next month would be something like $12.50. $10 for the charge for the next month, $5 for the premium plan portion I started using half a month ago, and $2.50 refund for the unused portion of the standard plan after the upgrade.
But this billing system could be confusing for customers, and I wanted to see if there was a way to charge customers immediately for the proration ($2.50 in our example above). There was no prorate_now
flag or anything, but it could be done manually. Here are the high level steps and a gist to walk you through it. The code will most likely require customization for your use case.
Note: I used Stripe's test environment for this as the docs suggest.
Prerequisites
- Add the
stripe
gem to your Rails app by adding the line below to yourGemfile
and runningbundle
.
gem 'stripe'
- Assign your Stripe publishable and secret keys to environment variables, and create
config/initializers/stripe.rb
(you can get your keys from the Stripe dashboard):
Rails.configuration.stripe = {
publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
secret_key: ENV['STRIPE_SECRET_KEY']
}
Stripe.api_key = Rails.configuration.stripe[:secret_key]
In the Stripe test environment, you need to create at least one product, two plans, and a customer who is subscribed to the lower tier plan. These can be created from the Stripe dashboard, or by sending requests to their API; I've linked the relevant docs for making API requests in the previous sentence.
The customer also needs to have a credit card so they can be charged (not a real one, but a mock card for testing purposes). If you created the customer via the API, a mock card was probably auto-created for you. You can make sure of this by going to the dashboard and checking that customer's details.
High level steps
- Create a preview invoice, which simulates what a customer's next invoice would look like if they upgraded now
- Calculate the proration cost from the preview invoice
- Create a new one-off invoice with that amount
- Charge the customer immediately
- Check if the invoice has been paid, then upgrade the customer with proration disabled (otherwise, they will be charged twice for the upgrade!)
Note: Creating a preview invoice does not create an actual invoice, so you don't have to worry about billing the customer accidentally (explained in the docs).
Gist as a proof of concept
The test below follows the steps I described above. The customer is subscribed to my cheaper plan, which costs JPY 100 per month, but they want to upgrade to the plan that costs JPY 1,000, so we charge them for the difference before upgrading them. I also have a User
model and a Plan
model in my Rails app (which correspond to customer and plan respectively in Stripe) that I occasionally refer to in my test.
And now for the actual code:
I will also link the relevant parts of the Stripe docs, because I personally found it a chore to sift through the detailed but scattered docs.
Read about...
- creating a preview invoice and calculating the proration cost here
- creating an one-off invoice here
- synchronously paying an invoice here
- checking if an invoice was paid successfully here
- upgrading a customer with proration disabled here
Please let me know in the comments if any part of the code is unclear, or if there are alternative ways to do this. Thank you!
Top comments (15)
Just a heads up for anyone else that gets erratic behavior with this code:
All line items seem to match the select statement, so it returns whatever line item happens to be first in the array. It seems that order is random. So sometimes you end up getting the pro-rated amount the customer needs to pay, while other times, you get the "unused amount" on the current subscription period, which is basically the discount amount itself.
I think the official Stripe Docs had this same code, but this part has since been removed. They don't provide an alternative, but one way would be to look for the line item with "Remaining time on" in the description.
I haven't thoroughly tested, but this seems to work more reliably:
👆 This returns the 'unused amount'. So if a customer is halfway through a $25/mo period, this would be about $12.50
It looks through all the line items, selects just the negative amounts, and adds them all up. Usually this is just one line item, but in theory it could be multiple.
👆 This is pretty simple. It's the total amount (including any taxes) the customer were to pay. So continuing the $25/mo example, with let's say a $200/year annual plan, this would be about $200 - $12.50 (see above) = $187.50
Hey Risa! This was helpful.
I wanted to clarify something in the above though in regards to upgrading a customer with proration disabled.
My understanding is that disabling proration will not prevent the customer from being charged on the next invoice - rather it would pro-rate the amount they will be charged and that amount would find it's way onto the next invoice.
See Stripe docs
Did you find something different and maybe I'm misinterpreting the documentation?
Thanks mate! 😀
Thanks for the comment :)
Full disclosure, I haven’t used Stripe since I wrote this, so let me refresh my memory and get back to you!
Regarding the part of the docs you quoted - I believe this is expected behaviour.
Let me illustrate with the example plans I used in the post ($5/month standard plan and $10/month premium plan).
What would happen normally in Stripe:
What we’re trying to achieve in the gist:
When they say “the customer is billed the full amount for the new plan”, I think they just mean the customer gets billed normally for the upcoming month, i.e. $10 for October.
The fact that no proration is applied to the next billing cycle’s invoice is also tested in my gist, in the final assertion (JPY 1,000 is the monthly price for the premium plan).
Hope this makes sense! Please let me know if I’m misinformed/you need more clarifications.
Legend! Thanks for such a thorough reply. The proration stuff all makes sense, it's that last point that gets me which is what will show on the final invoice? I want exactly the outcome you describe.
So I think I need to get off this board and prototype this functionality today ;) Thanks again for the post 💪
Update: Confirmed the approach works 🥳
Good luck, let me know if it works! :)
Thank you, Risa, for the thorough write-up!
My problem was somewhat different, but I used some of the ideas from here to end up with my solution. I didn't really have the confidence to just manually add in my own proration lines but it turns out it's not an issue if you understand what you're doing (which was easier with your help).
If you end up needing to support SCA you might find yourself following a similar approach. There's little documentation on this since it's very new and I had to contact Stripe support just for the idea (which might obvious in hindsight).
This article is exactly what I needed! Thanks!
Happy to be of help :)
Thank you, so helpful.
Thanks, happy to hear it!
Nice solution! By the way, how to you handle downgrade cenarios?
Thanks and sorry, just saw this!
I haven't looked into downgrades, but I believe the default behaviour is to prorate the customer in the next invoice, just like upgrades (i.e. the customer gets refunded for the portion of the original plan that they didn't use).
I'm not sure about anything beyond what the docs say, so it might be good to contact Stripe's customer support for details.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.