Stripe: downgrade a user at "period end"
Asked Answered
U

10

71

Is it possible to downgrade a user at period end instead of immediately? I've combed through the API Docs but haven't been able to figure out how to accomplish this.

As a workaround I'm currently immediately canceling the user's subscription, then subscribing them to the lesser subscription, trialing until the end of the month. This won't work though - I need to be able to delay the downgrade until the period end (but "record" it w/ Stripe at the time the downgrade is requested).

Clearly there are ways to accomplish this with webhook callbacks and by tracking user subscriptions locally but I'd like to avoid that if possible.


EDIT

Before anyone asks - I'm using Temboo's PHP SDK. However I'm not looking for a language-specific approach, just a high level how-to (if it's possible).

Used answered 29/5, 2013 at 17:21 Comment(2)
I know the question is old, but I think anyone wanting to do this would need to use webhooks and/or track things locally. Since this feature doesn't automatically exist within Stripe, you don't really have a way of expecting them to run a specific behavior at a specific point in time without tracking something on your end and updating via the API.Barrage
@CharlieS Actually, I think it's entirely reasonable to expect Stripe to expose this functionality. The amount of overhead required to manage it from our end is significant. I'd like to make an API call that requests the user be downgraded to a specified plan at the end of the period, and for Stripe to ping a webhook when that happens. They already do plenty of time-driven stuff, why not this? It's a very common use case, I'd like to see it supported.Used
D
42

Most of the solutions presented here look like hacks after stripe's release of subscription schedules, which is probably the most elegant solution. In fact, stripe documentation has an example illustrating exactly the same scenario here.

Step 1: Get current_period_end value from the existing subscription that you wish to downgrade.

Step 2: Create a new subscription schedule from the existing subscription.

$subscriptionSchedule = $stripe->subscriptionSchedules->create([
  'from_subscription' => 'sub_G678SASEGF',
]);

Step 3: Update the newly created schedule with two phases. phase 0 is the current phase that ends at current_period_end and phase 1 is the next phase that start at current_period_end with the downgraded price plan.

$stripe->subscriptionSchedules->update(
  $subscriptionSchedule->id,
  [ 
    'end_behavior' => 'release',
    'phases' => [
      [
        'start_date' => $current_period_start,
        'end_date' => $current_period_end,
        'items' => [
          [
            'price' => $current_price_id
          ],
        ],
      ],
      [
        'start_date' => $current_period_end,
        'items' => [
          [
            'price' => $downgraded_price_id,
          ],
        ]
    ],
  ],
]

You can always check the subscription object to see if there is a schedule active, and then retrieve the schedule to tap into any future downgrades. The advantage of this approach is that it could be applied to any downgrade and/or billing cycle change. with a multi plan approach described earlier in the answers, one subscription can only have items with the same billing cycle.

Doughman answered 10/6, 2020 at 16:20 Comment(4)
Thank you so much for the code sample! I'm using the Python API, but this still helped me tremendously. For the case where users continually switch between plans, I'd recommend saving the current plan's start-end dates (as well as the subscription schedule ID) in your DB, and using those instead of looking in the subscription object each time, since the phases will just keep getting propagated forwards. You can create a webhook for invoice.created, and delete the subscription schedule ID from your DB then if needed (since I believe Stripe will delete it from their end after the last phase)Azole
Im getting the error You cannot migrate a subscription that is already attached to a schedule when following these steps (in node) and not entirely sure what they're implying, can't see how what I've got could be different from the above exampleLowpressure
ah - I was running create on a subscription I had already created as I was testing - thanks for the answer !Lowpressure
This is the best answer, should be accepted @UsedRarefy
S
16

As @Safinn and @Martin have mentioned, you can cancel the subscription using at_period_end: true to action the cancellation on a specific date.

In order to downgrade to a different plan, the way I get around it is to do the above and then create a free trial of the new plan ending on the same date/time. This way the old plan will be cancelled on the same date that the new plan's trial will end (causing it to be active).

This lets Stripe handle the downgrade entirely their end (which should be more straightforward IMHO), rather than setting up webhooks or tracking dates in your database.

Substantive answered 5/5, 2017 at 13:31 Comment(7)
Not a bad idea. If I understand your strategy correctly, the subscription will have two plans until the original plan cancels at period end? When it does, the subscription will remain active with the plan for which trial ended simultaneously?Supplicatory
Correct. We've been using it for over 9 months and it works perfectlySubstantive
@Substantive sounds a good idea. From a stripe developer I've got this: " i think it'll be trickier to handle than it sounds. arbitrary time trials are a general pain, and you'll never get quite-exactly the correct cycle (which means you end up with a weird state in between the two subscriptions). my honest recommendation is to pro-rate the upgrade" . My main concern is about that state between two subscriptions. Did you have any issues on that?Rowden
I understand where Stripe are coming from but it is my understanding that trials (and subscriptions in general) are calculated down to the second, so theoretically there shouldn't be a weird state between 2 subs. In my experience using this method I never had any troubles with it, and we did substantial testing with it. I hope that helps.Substantive
This looks like the best approach. The only trouble I can think of is when a customer upgrades and downgrades multiple times. Seems like this will work fine, but may be hard to follow in the Stripe Dashboard. Is it clear enough what's going on?Boltzmann
This is the best solution for upgrades and downgrades where you don't want to prorate. Now that stripe supports multiple plans natively, you can upgrade a single subscription to a new plan, with a trial period set to the amount of time that was left in the previous plan's current period. So if a user is switching from monthly to yearly half way through a month, update the subscription with prorate=false trial_end=+15d plan=yearly, and the yearly amount will be charged in two weeks when the originally billed month was supposed to end.Colicroot
@Colicroot sadly this doesn't work for per-seat licenses, as a customer might be able to add n amount of users and they won't be billed during the trial.Hagiography
S
14

Yes, using the more recent version of the Stripe API.

(the Stripe tokens you may be using in Temboo's SDK are compatible with Stripe's normal PHP Library)

  • Create a product with multiple pricing plans
  • Subscribe a customer to one of these plan ids
  • When updating a customer to a new plan simply do as follows:

    $sub = $myUser->data['stripe_subscription_id'];
    $subscription = \Stripe\Subscription::retrieve($sub);
    \Stripe\Subscription::update($sub, [    
        'cancel_at_period_end' => false,
        'items' => [
            [
                'id' => $subscription->items->data[0]->id,
                'plan' => $plan,
            ],
        ],
        'prorate' => false,
        'trial_end' => $subscription->current_period_end
    ]);
    $subscription->save();
    

By setting prorate to false, trial_end to $subscription->current_period_end and cancel_at_period_end to false, you effectively tell Stripe:

Do not charge this user until the day that their current billing ends (cancel at period end), do not refund them any money on the plan switch (prorate) and start billing again at the end of their current billing cycle (trial end.)

Which has the effect of changing their billing to the new plan when their old plan ends.

Shiau answered 26/7, 2018 at 1:31 Comment(6)
Can you please clarify, you write "don't charge until the day their billing ends", citing "cancel_at_period_end", but the value is set to false, so it doesn't seem like this setting is doing anything? Also note that the new version of the API uses proration_behaviour as prorate is deprecated.Calenture
@AdamReis sure no problem; and yea this was current API in 2018-- i believe v3. I will look up the newest and update thanks. anyway about prorating: if prorate was true, they would get an immediate charge for [number of days left in billing cycle]. cancel at period end makes it so the next billing event will be exactly when the current billing period ends and the new plan beginsShiau
Hi @GaretClaborn did you manage to see if there is an easier way to do this now?Cognition
this is much simpler than creating a new sub at a scheduled time and removing the current sub.Jehoash
I think this will create a trial for the new subscription and discontinue the old one immediately, which is not what OP wants. The company provides their customers service based on current subscription instead of payment history.Sibyls
@Sibyls weellllllll that's not correct. it will update the current subscription to a plan that begins as soon as the current plan's billing period ends. the subscription and plan are two different types of API objects. there may be even cleaner ways now, years later, but this has been used in production on many systems for a few years covering the exact purpose the OP needsShiau
C
12

Stripe has recently introduced subscription schedules that solves this problem: https://stripe.com/docs/api/subscription_schedules

Chippewa answered 18/10, 2019 at 11:4 Comment(0)
K
5

You should keep track of when your user joins a plan - keep a date field in your database next to the customer_id. You can use this date to determine the day of the month they joined and therefore the billing cycle. If the billing day is the 31st of the month then on shorter months Stripe will bill on the last day of those months (https://support.stripe.com/questions/subscription-date-at-end-of-month).

Now when a user wishes to downgrade they complete the action on your website when logged in. You take note of this downgrade request and store it in a, lets call it a "stripe_actionable_table" in your database. Important fields to have in this table would be:

  • actionable_date (the date to action the Stripe request - you would need some logic to determine the shorter months as mentioned above)
  • stripe_customer_id
  • what_to_do (upgrade/downgrade/cancel)
  • change_plan_to (a plan id - can be null if cancel req)

You would then have a cron that runs everyday at a certain time, and checks this stripe_actionable_table and if the day of the month matches with a row in the table then action the Stripe request. Once completed you can delete or mark the row as deleted.

Kurr answered 19/5, 2014 at 6:15 Comment(3)
Thanks. Yes there are all sorts of ways this can be done by managing things on our end - my question was, is there a way I could request Stripe automatically process the downgrade at the end of the period. It's such a common scenario, and in conjunction w/ webhooks could be so easy. But as your answer (and our solution) demonstrate is it's a major hassle.Used
How about cancelling the subscription using the param "at_period_end" set to TRUE. Then setup a webhook that can listen to Stripe events so that you can detect the event: "customer.subscription.deleted". When you pick this up then just setup the customer on a lower subscription plan.Kurr
Another, way would be to keep track in a column of an "actionable event". Then use the webhook when the user pays their invoice. Then when they do, look in the table to see if they need any changes and then handle it at that time...Interpret
Q
4

For everyone looking for an up-to-date solution to this - have a look at the Stripe Subscription Schedules API Link

With this API you can simply:

  • Start a subscription on a future date,
  • Backdate a subscription to a past date and
  • Upgrade or downgrade a subscription

You can create a schedule simply like this:

\Stripe\SubscriptionSchedule::create([
  'customer' => 'cus_ABC',
  'start_date' => 'now',
  'end_behavior' => 'release',
  'phases' => [
    [
      'items' => [
        [
          'price' => 'price_ABC',
          'quantity' => 1,
        ],
      ],
      'iterations' => 12,
    ],
  ],
]);

See the docs for more

Quinones answered 16/8, 2021 at 9:39 Comment(0)
B
3

This is now possible using Stripe's prorate flag.

E.g.

$subscription = \Stripe\Subscription::retrieve("sub_44ty4267H50z6c");
$itemID = $subscription->items->data[0]->id;

\Stripe\Subscription::update("sub_44ty4267H50z6c", array(
  "items" => array(
    array(
      "id" => $itemID,
      "plan" => "basic-plan",
    ),
  ),
  "prorate" => false,
));

By setting prorate to false, you are effectively telling Stripe not to apply the plan change until end of the current period.

Official docs here:

https://stripe.com/docs/subscriptions/upgrading-downgrading#disable-prorations

CLARIFICATION (as per user comment below): Note that Stripe will update its own representation of the active plan immediately (only the charging of the user is deferred), so you will still need to manually manage delaying the active plan change from within your own application.

Bosworth answered 19/12, 2017 at 11:9 Comment(6)
4.5 years later, eesh. ThanksUsed
Have just tested this & setting prorate to false does NOT have the effect you stated of delaying the plan change. It still changes the plan & charges immediately. Only difference is it doesn't credit the user for any unused time. The documentation is in agreement with this.Nial
@Nial the question asks to "delay the downgrade until the period end (but "record" it w/ Stripe at the time the downgrade is requested)." The prorate flag "records it w/Stripe" immediately, but charges them as if the downgrade had been delayed. How you record the plan change in your own application is then up to you. But I see how the wording in my answer can be misinterpreted. Will update now (let me know if you still find it misrepresentative)Bosworth
@TomG Does this also apply to a trial period where it changes the plan after the trial period is complete?Pauli
@Pauli I don't know, unfortunately. You'll have to test it out.Bosworth
Will not deelay the plan change so this does not solve the issue.Suanne
S
1

This is how i do it.

I simply cancelled the existing subscription which will end as per the current billing period. And at the time of cancelling it, i saved the requested downgraded plan id of the customer within my local users table.

Then i set a webhook for customer.subscription.deleted within stripe and created a handler that will just pick the saved downgraded plan from my local users table and create a new subscription immediately using that.

Senarmontite answered 19/11, 2019 at 14:42 Comment(1)
Yep that's what I meant by "Clearly there are ways to accomplish this with webhook callbacks and by tracking user subscriptions locally but I'd like to avoid that if possible"Used
F
0

There doesn't seem to be a way to do it easily with Stripe.

I'm updating quantities rather than changing plans but the idea could be applied as well.

Possible solution is:

  1. Update subscription quantity without proration with Stripe.

  2. Keep previous quantity until the invoice.created event.

  3. When handling invoice.created event, compare previous quantity with quantity user is subscribed to and reduce it if necessary.

Fairlead answered 6/2, 2017 at 5:38 Comment(0)
S
-3

If you instead want to cancel the subscription at the end of the current billing period (i.e., for the duration of time the customer has already paid for), provide an at_period_end value of true

https://stripe.com/docs/subscriptions/canceling-pausing

I think you can update the subscription and add at_period_end: true and that should cancel it at the end of the period.

Saucer answered 15/4, 2017 at 7:41 Comment(1)
Cancel != downgrade.Used

© 2022 - 2025 — McMap. All rights reserved.