Working with a subscription
In order to work with subscriptions, we first need to understand what exactly is the flow for successful or failed subscription. Once we understand the flow, we can work on further steps like provisioning access to product and so on.
Successful subscription flow
The following is the flow of events for a successful subscription from the implementation perspective:
- Subscription is created.
- An invoice is created for the subscription and then it's paid.
- The subscription status is set to
activeand the invoice status is set to
Failed subscription flow
The pseudo-code for a failed subscription flow looks like so:
- Payment fails during first time checkout session based payment or automatic charge attempt.
- Has successful payment been made within 23 hours for the failed attempt?
- If the payment has been made, then Stripe updates subscription to be
activeand invoice to be
- If no payment has been made, then Stripe updates subscription status to
incomplete_expiredand the invoice becomes void.
- If first time payment was made, then Stripe tries to automatically collect recurring payments for the subscription.
- After completing above steps, Stripe tries to automatically attempt to invoice the customer on the recurring invoice dates associated with the active subscription.
- If payment fails for automatic attempts, then Stripe will update the
past_dueand attempt to recover payment based on the retry rules that we set in Stripe dashboard. We will discuss about retry rules in next section.
- If payment recovery fails, then we can cancel the subscription from our side using API calls.
- Or if we want to continue attempting to bill the customer every month, even
when previous invoice payments have failed, then set subscription status as
unpaidand don't cancel the subscription.
Retrying failed payments
When an invoice payment with an iDEAL, Sofort, Bancontact, credit or debit card fails, Stripe retries payments according to our Dashboard's automatic collection settings. To avoid bank fees, Stripe doesn't retry invoice payments made with ACH debit, BECS debit, or SEPA debit.
We can choose the schedule of retries, or we can allow Stripe's Smart Retries feature to optimize the schedule and number of retries.
First let's talk about Smart Retries.
Using machine learning, Smart Retries chooses optimal times to retry failed payment attempts to increase the chance of successfully paying an invoice.
The Smart Retries feature attempts to retry the customer's charge up to four times within a customizable period.
We can override this behavior, for all invoices, in our Dashboard's automatic collection settings by disabling Smart Retries and defining our own custom retry rules.
Handling payment failures
Prevent failed payments
In order to avoid failures in payments due to problems in already provided payment info of the customer, Stripe provides us the option to send email regarding keeping payment info up to date.
We can send mail for two of the following cases by toggling either of them or both in dashboard:
- Send emails about upcoming renewals - this email will be sent 7 days before a customer's subscription renews.
- Send emails about expiring cards - this email will be sent 1 month before a customer's card on file expires. If this feature is enabled and a card is added during the month of expiry, then the email will be sent within 24 hours.
We are also provided with the option to send a webhook event for upcoming renewal events, before n-days of the renewal.
Manage failed payments
Stripe allows us to configure the steps we'd like to take when charging a customer's payment method fails.
This is how the dashboard would look like:
We can delegate the retry logic to Smart Retries feature in Stripe or we can set our own custom retry schedule which can be set to retry 1,3,5 or 7 days after the previous attempt.
If the payment attempt or retries fail, then we toggle the option named
Customer emails to send email regarding card payment fails.
Another important setting over here is, what to do when all retries for a
particular payment fails. In that case for a subscription we can either leave
the subscription as is, mark as
unpaid or cancel the subscription. Similarly
for an invoice, we can leave it as is or mark as
Provisioning access to product based on subscription
The most straightforward way to provide a customer access to a product from the
implementation perspective is to check whether the status of the subscription is
active or not.
We have to make use of Stripe webhook events like
payment_intent.failed to explicitly check
whether the subscription is still active or not, and orient our backend logic to
take into account the subscription status and allow access to the product.
If our application uses Stripe checkout, then we don't need to explicitly create a subscription nor customer, since Stripe itself takes care of that.
If we weren't using Stripe checkout, then we'd have to leverage both the Subscription and Customer API's to complete the first time payment process.
Which means, in this case we would have to invoke the
create API call using
these API's for creating the Customer as well as the Subscription during the
first time payment process.
Steps to provision access to product for first time subscription, if using Stripe checkout:
- Complete the checkout session from Stripe.
- Listen to the
checkout.session.completedwebhook event from Stripe.
- Check if the
statusof the subscription object received is
- If subscription
active, then allow access to our product.
If not using Stripe checkout, then the steps are as follows:
- Create a new Customer at Stripes end if not already existing
- Create a Subscription for that Customer
- Initiate the payment using Stripe elements
- Once payment is successfully made Stripe will send a
- Then we can check the subscription status from that webhook object and allow access to our product as we did during handling checkout session webhook event.
Upgrading and Downgrading Subscriptions
Let's take an example where Oliver uses monthly billed plans, where he uses a $10 plan for half a month and then decides to switch to $20 plan.
The first thing Stripe will check is whether the billing periods of the current subscription and switched subscription are same or not.
If billing periods are same, then the invoice will be charged, with or without prorations, on the same billing dates.
However, take the case where we switch the customer from a monthly plan to an yearly plan.
In that case the billing date is updated to the billing date of the switched subscription.
So in the above scenario, where Oliver switched to another monthly plan itself, the billing would only take place at the start of the next month, given that both plans have same billing period.
In more simpler terms, we have to pay the rest of the amount for switching to the new expensive plan only after the current month gets over, rather than immediately.
There are two ways to bill Oliver during that switch:
- Bill the full amount of the new plan when new invoice is generated. This means
we don't need any proration behavior. From the implementation perspective we
can do this by setting
proration_behavior: 'none'while switching the subscription.
- Or apply the proration explicitly and immediately. Here we need to set the proration behavior to always prorate.
How prorations work?
In Stripe, the term proration is used to denote the amount that needs to be collected, when say switching from one plan to another.
In the above example, the customer upgrades from a $10 per month subscription to a $20 subscription, and the customer is charged the prorated amounts for the time spent on each option.
Assuming the change occurred halfway through the billing period, the customer is billed an additional $5: -5 USD for unused time on the initial price, and 10 USD for the remaining time on the new price.
The prorated amount is calculated as soon as the API updates the subscription to a new plan. The current billing period’s start and end timestamps are used to calculate the cost of the subscription before and after the change.
Let's take one more example to solidify our understanding on how a proration is calculated by Stripe. Say Oliver subscribed to a $30 plan whose billing period is 30 days.
Let's say that he used 1 day from that plan and is switching to an yearly plan priced at $60.
Then stripe will charge him for the n-number of days he used current plan, using an amount calculated from the current plan and charge for the rest of the m-number of days based on the pricing of the newly switched to plan.
Here Oliver will be charged $1 for the 1-day he used in $30 plan and for the rest of the 29 days, the amount will be calculated from $60 plan, and a final bill will be generated either immediately or at the end of current billing period, depending on our configuration of the subscription.
We will take a look into how to configure the billing of a subscription in the following sections.
Attempting immediate payment
It can get confusing at times if we let Stripe decide the billing date when switching between plans.
The most sanest way to bill the customer during a switch is to immediately attempt the payment.
Meaning, if they're switching from a $10 plan to a $20 plan, then immediately attempt payment for the prorated amount, without taking into consideration the billing period.
Stripe does this automatically if the billing periods of the plans differ.
Example: If we switch from a monthly plan to an yearly plan, then payment will be attempted immediately at the moment of switch.
But what if we want to switch from a monthly standard plan to a monthly pro plan?
Usually Stripe will attempt the payment only at the next billing period. But this can often create problems if the customers card declines payment during next billing period.
So it's better to attempt immediate payment and make sure the customer is able to pay for the features of the new plan.
In order to attempt immediate payment at all times, we need to set the proration
behavior to always invoice. From the implementation perspective, we need to set
Previewing a plan before switching
Let's say that in our application, we are attempting immediate payment with proration.
Therefore from a customers point of view, it's essential to show them the amount that they need to pay extra, when switching to a more expensive subscription plan.
We can preview the effects of switching a subscription, including a preview of what proration will take place, by retrieving the upcoming invoice.
Because Stripe prorates to the second, prorated amounts can change between the time they’re previewed and the time the update is made.
To ensure that the actual proration is calculated exactly the same as the previewed proration, we should pass a proration date, which should be the datetime at which we make the API call to retrieve upcoming invoice, parameter when doing the actual subscription update.
The recommended way to get only the prorations being previewed is to consider only the items whose starting period is same as the subscription proration date, of the upcoming invoice object.
Steps to switch the subscription
The following is the pseudo-code for switching to another subscription:
- Retrieve the subscription details using the subscription ID(subId), which will
most probably be associated with the
- Now invoke the subscription update API:
- Set the
always_invoicefor immediate payment attempt.
- Set the
itemsto the ID of the
itemsdata in the retrieved subscription and the
priceto be the ID of the new price to be switched to.
- Await the response from Stripe webhook for the event
customer.subscription.updatedand check the
statusof the subscription object. If
active, then we have switched the subscription.