Cyril N.
Cyril N.

Reputation: 39889

Changing a Stripe plan with the same price

I recently changed the names of my plans, including their ID.

Some of my plans have the same structure (same amount, period, trial, etc), it's just the name that have changed.

Using the Stripe API, I'd like to move all my current customers to the new plan that is similar in term of features.

The problem is that I don't want to charge them now, nor change their period, just change the Stripe subscription.

I'm pretty sure it's possible using the update a subscription API endpoint, but by playing with the trials, billing_cycle_anchor and days_until_due.

I'm hoping this has already been done and someone already has a working solution for this :)

Since it's API related, there is no need for a specific language.

Upvotes: 2

Views: 1723

Answers (2)

Cyril N.
Cyril N.

Reputation: 39889

Here's a pratical response to my issue. It updates the plans to another that have the same properties (recurring, amount, etc).

processed = 0
starting_after = None
while True:
    results = stripe('get', '/subscriptions', params={
        'limit': 100,
        'starting_after': starting_after
    }).json()

    for sub in results.get('data'):
        print("Checking {0} for customer {1}".format(sub.get('id'), sub.get('customer')))
        starting_after = sub.get('id')

        if sub.get('items').get('total_count') != 1:
            print('Customer {0} has {1} subscriptions ????'.format(sub.get('customer'), sub.get('items').get('total_count')))
            continue

        plan = sub.get('items').get('data')[0].get('plan')
        if plan.get('id') not in maps:
            print('Plan {0} was not found'.format(plan.get('id')))
            continue

        try:
            invoice = stripe('get', '/invoices/upcoming', params={
                'customer': sub.get('customer')
            }).json()
        except requests.exceptions.HTTPError as e:
            if e.response.status_code != 404:
                print(e.json())
                continue

            print("Ignoring customer {0} because subscription will end ...".format(sub.get('customer')))
            continue

        expected_amount = invoice.get('total')

        try:
            new_invoice = stripe('get', '/invoices/upcoming', params={
                'customer': sub.get('customer'),
                'subscription': sub.get('id'),
                'subscription_items[0][id]': sub.get('items').get('data')[0].get('id'),
                'subscription_items[0][deleted]': True,
                'subscription_items[1][plan]': maps[plan.get('id')],
                'subscription_prorate': False
            }).json()
        except Exception as e:
            print(e.response.json())
            continue

        assert invoice.get('total') == new_invoice.get('total')
        assert invoice.get('amount_due') == new_invoice.get('amount_due')
        assert invoice.get('next_payment_attempt') == new_invoice.get('next_payment_attempt')
        assert invoice.get('period_start') == new_invoice.get('period_start')
        assert invoice.get('period_end') == new_invoice.get('period_end')

        stripe('post', '/subscriptions/{0}'.format(sub.get('id')), data={
            'items[0][id]': sub.get('items').get('data')[0].get('id'),
            'items[0][deleted]': True,
            'items[1][plan]': maps[plan.get('id')],
            'prorate': False
        }).json()

        updated_invoice = stripe('get', '/invoices/upcoming', params={
            'customer': sub.get('customer')
        }).json()

        assert invoice.get('total') == updated_invoice.get('total')
        assert invoice.get('amount_due') == updated_invoice.get('amount_due')
        assert invoice.get('next_payment_attempt') == updated_invoice.get('next_payment_attempt')
        assert invoice.get('period_start') == updated_invoice.get('period_start')
        assert invoice.get('period_end') == updated_invoice.get('period_end')

        processed += 1

    if not results.get('has_more'):
        break

And the stripe function:

def stripe(method, url, **kwargs):
    headers = {
        'Stripe-Version': '2019-08-14'
    }

    response = getattr(requests, method)(
        'https://api.stripe.com/v1{0}'.format(url),
        auth=('STRIPE_PRIVATE_KEY', ''),
        headers=headers,
        timeout=15,
        **kwargs
    )

    response.raise_for_status()
    return response

Upvotes: 1

duck
duck

Reputation: 5470

From what you've described, it sounds like this should be a fairly easy transition! That said, my advice when attempting any kind of Subscription change, is to try it out in Stripe's test mode first.

If the period of your old and new subscription plans are the same (i.e. monthly -> monthly), the billing period will not change, and Stripe will not attempt to collect payment until the current billing period ends.

Here's what Stripe has to say about it:

https://stripe.com/docs/billing/subscriptions/upgrading-downgrading

If both plans have the same billing periods—combination of interval and interval_count, the subscription retains the same billing dates.

Stripe immediately attempts payment for these subscription changes:

  • From a subscription that doesn’t require payment (e.g., due to a trial or free plan) to a paid subscription
  • When the billing period changes

As far as how to implement this process:

  1. If you are making any pricing changes, communicate these clearly to your users ahead of time.
  2. List your Subscriptions, https://stripe.com/docs/api/subscriptions/list
  3. For each Subscription, look at the attached plan(s), update the Subscription to use the appropriate new plan(s). If you don't want the next Invoice to reflect a proration from a change part-way through a billing cycle, be sure to pass prorate=false https://stripe.com/docs/api/subscriptions/update

The /v1/invoices/upcoming endpoint can also be a helpful tool to preview what subscription changes might look like at the next billing period.

At any time, you can preview the upcoming invoice for a customer. This will show you all the charges that are pending, including subscription renewal charges, invoice item charges, etc. It will also show you any discount that is applicable to the customer. [...] You can preview the effects of updating a subscription, including a preview of what proration will take place.

https://stripe.com/docs/api/invoices/upcoming

Upvotes: 2

Related Questions