This is the second part of our two-part series about building upgrade & downgrade flows in SaaS. In the first part, we’ve tested how the top 40 SaaS companies handle their upgrade and downgrade flows and covered the edge cases you’ll need to consider when building your own flows.
For this part, we’ve promised to go behind the scenes and share the 5 most crucial learnings from our engineering team when building scheduled downgrade flows and uncover the technicals behind building upgrades and downgrades for Stigg.
As a reminder, we’re building pricing & packaging APIs for SaaS for a living and put an immense amount of time and effort into understanding best-in-class flows and how to solve it technically.
We hope that by sharing our learnings, building those flows for your business will be less painful.
Recap: what are upgrades & downgrades and how do leading SaaS businesses handle them?
If you haven’t read the first part of this series, here’s a quick summary. If you have, feel free to fast-forward to the next section.
Upgrade describes the process of a user switching to a higher-priced option, like switching to a higher tier, a longer commitment (monthly to annually), buying more units (e.g. seats), buying add-ons, or buying more quantities of an add-on. The most common approach in SaaS is to prorate upgrades immediately (like Zoom or Productboard). This means, the customer gets access to their higher-priced option and will be charged the difference for the rest of the billing period immediately.
Downgrade is the switch to a lower-priced option, such as a lower tier, shorter commitment (annually to monthly), reducing the unit quantity (e.g. reducing seats), removing an add-on, or reducing the quantity of a specific add-on. Building downgrades is technically more complex and our research revealed two common approaches:
- Downgrade immediately and grant credits for the remaining time of the billing period, like Slack, or Notion
- Scheduling a downgrade to take place at the end of the current billing period, like Zoom, Trello, or GitHub
5 tips we wish we knew when building scheduled downgrades for Stigg
When we started building upgrades & downgrades for Stigg, we quickly realized that if we want to support scheduled downgrades, we’ll need a mechanism to support this.
This is the flow we’ve sketched when building upgrades & downgrades in Stigg:
I remember we had an internal debate within the engineering team about how we handle paid subscriptions that are synced to Stripe.
Let’s look into an example we discussed back then.
A customer is scheduled for a downgrade at the end of the billing period and we have a scheduled check that runs regularly to see if it's time to downgrade the customer. Now, Stripe sends an invoice to the customer, before syncing with Stigg about the scheduled downgrade. No matter how frequently we set our checks, there is a chance that this will happen, causing unnecessary prorations and subscription changes.
So, for Stripe users, we’ve decided to create and manage stripe scheduled updates. That means, we are the source of truth of what happens to the subscriptions at the end of each billing period, but we sync this truth to Stripe in the form of subscription schedules and delegate to Stripe to make it happen exactly on time.
How should you implement subscription schedule management on your side?
1. Save the downgrade request in a dedicated table.
When a subscription downgrade is requested the first time, we save the downgrade request in a dedicated table. We decided to allow multiple requests and manage them separately where each downgrade is saved in its own line in the table. For example, if there is a tier downgrade request and a unit amount downgrade change for the same subscription we will save them in separate rows in the database. We strongly advise you to do so as in many cases you would want to cancel some updates while keeping others and it becomes a hassle when modeling it differently. We also suggest having an Enum to mark the type of downgrade. It will help to manage the different schedules for the same subscriptions and alter them as requested.
It could be as simple as:
As you can notice we could have multiple schedules of different types for the same subscription.
2. On each change in the subscription, recreate the subscription schedule.
Schedules have phases. The first phase (phase) is the current subscription and we leverage the next phase (phase) to include the updated subscription. As we manage the subscriptions and have the full current state of the subscription, we suggest recreating the schedules each time a subscription update occurs. Doing so will make sure that your schedule doesn’t have any outdated items, archived prices, or any other stale data. If a schedule has a faulty item, it will fail to save.
Compile all the scheduled changes you have (remember, there could be more than one change to perform) into phase of the subscription.
We advise you to create the schedules with end behavior ‘release’ so that after the change is performed the schedule dies and the subscription just goes on until canceled or new changes are requested.
3. Listen to webhooks from Stripe.
We are known to be obsessed with Stripe webhooks. When Stripe is performing the change at the end of the billing period you can listen to the subscription updated webhook and mark the schedules in your table as done. It can help you monitor that the changes are performed.
When the webhook is processed, you should create the updated subscription with the updated entitlements on your side.
Example of customer.subscription.updated:
4. Tame your schedules!
If you manage the products on your own and have flows where you archive old outdated products programmatically, you will have to update all schedules that are going to use this product.
We suggest finding all the relevant schedules and triggering the same logic that updates the subscription. If you follow suggestion number 2: On each change in the subscription, recreate the subscription schedule. Your schedule will be recreated for you with the newest relevant product version.
5. Support contradictory updates.
In some cases, a customer can request an upgrade in the unit but a downgrade in the tier at the same time. To make it even more fun, the customer may already have a unit downgrade scheduled.
As a rule of thumb, we would schedule for later the downgrades and perform the upgrades immediately while canceling the irrelevant downgrade.
In practice, if you followed the previous steps proposed this kind of use case should be super easy to support. We will schedule a downgrade for the end of the billing period. Then, we remove the scheduled unit downgrade, and lastly, we would upgrade the units requested immediately. Easy? Yes, if you follow the previous steps.
Behind the scenes of Stigg's code. Here's what we did.
We’re an ex-New Relic team that has experienced first-hand how ‘supporting pricing’ always exceeds far more than 1 or 2 sprints. Not just for companies the size of New Relic. Even building your very first pricing becomes a massive project. Because pricing is complex.
So, we’ve made it our mission to give developers flexible pricing & packaging out of the box. This is how we’ve solved the technicals of upgrades & downgrades in Stigg to give you best-in-class flows with maximum flexibility:
✅ Saving changes separately
Each change in a subscription, like a change in the plan, the unit quantity, the add-ons, or the billing cycle, is saved separately.
✅ Handling best-practice upgrade flows
Upgrades in Stigg are always prorated, upgrading the customer immediately and charging only for the difference. This makes sure your system is following the standard in SaaS billing, meeting your customers’ expectations. On top of that, our native integration with Stripe ensures that your customers will only pay for what they use, automatically reflecting any upgrade or downgrade.
✅ Supporting 2 different downgrade flows
Downgrades are more complex - and less covered by Stripe. So, we chose to support both common ways to let you choose between (a) downgrading immediately, granting credits for the rest of the billing period, like Slack or Notion, and (b) scheduling downgrades for the end of the billing period, like Zoom or GitHub.
Once integrated, Stigg’s no-code platform allows other teams to make changes in downgrade flows and customer subscriptions, without any engineering support. If you ever were in a position of rolling out pricing tasks, you know how much time it saves you. Not talking about how defocusing those manual changes are from your main tasks. Very inconvenient to say the least.
When a subscription is updated, we evaluate each of the requested changes separately and determine for every change whether they should take place immediately or in the next billing cycle. Stigg supports multiple environments per account and multiple products per environment. Every configuration is for a single product, since different products can behave differently.
✅ Covering all common edge cases
The tl;dr: We cover all common edge cases.
For example, contradicting changes. Happens more often than you think. Your customer’s plan covers 5 seats. They realize they don’t need 5 seats and remove 1. Now, they’re scheduled to downgrade to 4 seats at the end of the billing period. But then, 2 new team members join and they add 2 more seats before the billing period ends.
Using Stigg’s SDK, this is how the flow looks like:
When a contradicting change takes place we override the schedule for you. In our example:
- A customer changed their seat count from 5 to 4, which are scheduled to take effect at the end of the current billing cycle.
- The customer then invited 2 more users.
- Stigg checks if the updated seat count will be lower or higher than the current seat count. In our example, the updated seat count will be higher than the current seat count (was: 5 seats, will be: 6 seats). The customer will be charged immediately for the prorated amount of the additional seat (here: 1 seat). If the updated seat count will be lower than the current seat count, a scheduled change will still take place with the updated seat count.
- If you’re integrated with Stripe, we also update the scheduled change in Stripe, saving you from having to cancel the existing scheduled change and recreate the new scheduled change…
✅ Giving you buying experiences end-to-end
Any change in a subscription also needs to be reflected to your customers in your app’s frontend. How can a customer upgrade or downgrade? And how will a customer see their scheduled downgrade in-app?
Given that our goal is to take the pricing pressure off your shoulders, we take care of the backend and frontend support for you.
Our snap-in paywall and customer portal widgets allow customers to update subscriptions easily in a self-served manner, track changes, and follow scheduled downgrades. Once integrated, changes in pricing, packaging, and styling can be operated from Stigg’s platform, without additional code changes.
Upgrades and downgrades are essential elements in SaaS buyers’ experience.
When defining your upgrade & downgrade flows, take a look at best practices. This is what your customers will compare your service with. The most common upgrade flow is to prorate the customer immediately, while for downgrades, we found two main approaches - grant access until the end of the billing period or give credits.
Once defined, you’ll need to provide the technical infrastructure to allow upgrades & downgrades. Especially scheduled downgrades are pretty complex to build. So, if you choose to build something yourself, follow the 5 tips our engineering team summarized for you.
If you prefer to spend all of this time building upgrade & downgrade flows (and get a complete and flexible pricing & packaging platform out of the box, with a whole set of additional capabilities), check out what Stigg offers to developers and our docs, or get started for free.