Upgrading and Downgrading Plans

Subscriptions can be changed by switching the plan to which a customer is subscribed. Normally this is a matter of either upgrading or downgrading the subscription, depending upon the price difference between the two plans.

Assuming a customer is currently subscribed to the basic_monthly at $10 per month, here’s how you’d switch the customer to the premium_monthly plan:

# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"

subscription = Stripe::Subscription.retrieve("sub_3R3PlB2YlJe84a")
subscription.plan = "premium_monthly"
subscription.save
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"

subscription = stripe.Subscription.retrieve("sub_3R3PlB2YlJe84a")
subscription.plan = "premium_monthly"
subscription.save()
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

$subscription = \Stripe\Subscription::retrieve("sub_3R3PlB2YlJe84a");
$subscription->plan = "premium_monthly";
$subscription->save();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

stripe.subscriptions.update(
  "sub_3R3PlB2YlJe84a",
  { plan: "premium_monthly" },
  function(err, subscription) {
    // asynchronously called
  }
);
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";

Subscription subscription = Subscription.retrieve("sub_49ty4767H20z6a");

Map<String, Object> subscriptionParams = new HashMap<String, Object>();
subscriptionParams.put("plan", "premium_monthly");

subscription.update(subscriptionParams);

If both plans have the same billing frequency (e.g., interval and interval_count), the customer will retain the same billing dates. If the plans don’t have the same billing frequency, the new plan will be billed at the new interval, starting on the day of the change. For example, if you switch a customer from one monthly plan to another, the billing date does not change. However, if you switch a customer from a monthly plan to a yearly plan, the billing date becomes the date of the switch.

If the subscription change moves the customer from no-pay being required to a paid requirement (e.g., from a trial period to an active subscription, or from a free plan to a paid plan) or moves the customer to a different billing frequency (e.g., yearly to monthly), Stripe will synchronously attempt payment of the amount owed. In other words, in these cases, Stripe will attempt payment as part of the subscription change request. If that payment succeeds, the subscription update request will also succeed. But if that payment fails, the subscription change request will also fail, and the subscription will be left unchanged.

The most confusing aspect to upgrading or downgrading plans is how existing payments are factored into the amount to be invoiced. By default, Stripe prorates subscription costs.

For example, say the customer in the above code is billed on the 1st of each month and the above code is executed exactly half-way through the plan’s billing cycle (i.e., on the 15th of March). In that case, the customer has already paid $10, and used half of the current billing cycle. When the customer is switched to the new plan, with a cost of $25, the unused portion of the previous plan results in a credit of $5. The cost of the new plan for the rest of the billing cycle (i.e., the rest of March) will be $12.50. Therefore, the total prorated cost of switching from the cheaper plan to the most expensive plan is $7.50. This amount will be added to the next invoice.

The next invoice, on April 1st, will also reflect the payment required on the new plan for that upcoming month: $25. Thus, the April 1st invoice would be for a total of $32.50.

Note that changing the plan would not change the billing cycle in this example. The customer will not be billed the $32.50 at the time when the plans are switched. The plan changes impact the next invoice, which remains on the same billing cycle (as both plans have the same billing frequency).

If the new plan is less expensive than the existing plan, the prorated credit will be larger than the new amount due. For example, if the customer is switching from the $25/month plan to the $10/month plan exactly halfway through the billing cycle, the customer will have a net credit of $7.50 resulting from a credit of $12.50 for the unused portion of the old plan less a charge of $5 for the remaining time on the new plan. The next invoice, at the start of the next billing period, will reflect the $7.50 credit and the $10 due for a full, upcoming month on the new plan, resulting in a net charge of of $2.50.

The prorated amount is calculated down to the second by Stripe. This means that Stripe will calculate the differences between the two plans based on the time that the API call was made to change the customer’s plan, using the current billing period’s start and end times.

To preview the cost of a proration before changing the customer’s subscription, you can use the upcoming invoice API. This will show what the customer’s next invoice will look like if the change to their subscription is applied. The first two invoice items will be the prorations created during the update. (If a subscription goes from a paid subscription to a trial, only the negative proration will be created, instead of two prorations.)

Because Stripe prorates to the second, the amount of the prorations can change slightly between the time they are previewed, and the time the update is actually done. To avoid this problem, you can “lock in” the proration amount by passing in a subscription_proration_date (on an invoice) or proration_date parameter (on a subscription), which will cause the proration to be calculated as though the proration was done at that time.

# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"

proration_date = Time.now.to_i
invoice = Stripe::Invoice.upcoming(:customer => "cus_3R1W8PG2DmsmM9", :subscription => "sub_3R3PlB2YlJe84a",
                                   :subscription_plan => "premium_monthly", :subscription_proration_date => proration_date)
current_prorations = invoice.lines.data.select { |ii| ii.period.start == proration_date }
cost = 0
current_prorations.each do |p|
  cost += p.amount
end

# Display the cost of these prorations invoice items to the end user,
# and actually do the update when they agree.
# To make sure that the proration is calculated the same as when it was previewed,
# you need to pass in the proration_date parameter

# later...

subscription = Stripe::Subscription.retrieve("sub_3R3PlB2YlJe84a")
subscription.plan = "premium_monthly"
subscription.proration_date = proration_date
subscription.save
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"

import time
proration_date = int(time.time())
invoice = stripe.Invoice.upcoming(customer="cus_3R1W8PG2DmsmM9", subscription="sub_3R3PlB2YlJe84a",
                                  subscription_plan="premium_monthly", subscription_proration_date=proration_date)
current_prorations = [ii for ii in invoice.lines.data if ii.period.start == proration_date]
cost = sum([p.amount for p in current_prorations])

# Display the cost of these prorations invoice items to the end user,
# and actually do the update when they agree.
# To make sure that the proration is calculated the same as when it was previewed,
# you need to pass in the proration_date parameter

# later...

subscription = stripe.Subscription.retrieve("sub_3R3PlB2YlJe84a")
subscription.plan = "premium_monthly"
subscription.proration_date = proration_date
subscription.save()
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

$proration_date = time();
$invoice = \Stripe\Invoice::upcoming(array(
  "customer" => "cus_3R1W8PG2DmsmM9",
  "subscription" => "sub_3R3PlB2YlJe84a",
  "subscription_plan" => "premium_monthly",
  "subscription_proration_date" => $proration_date
));

$cost = 0;
$current_prorations = array();
foreach ($invoice->lines->data as $line) {
  if ($line->period->start == proration_date) {
    array_push($current_prorations, $line);
    $cost += $line->amount;
  }
}

// Display the cost of these prorations invoice items to the end user,
// and actually do the update when they agree.
// To make sure that the proration is calculated the same as when it was previewed,
// you need to pass in the proration_date parameter

// later...

$subscription = \Stripe\Subscription::retrieve('sub_3R3PlB2YlJe84a');
$subscription->plan = 'premium_monthly';
$subscription->proration_date = proration_date;
$subscription->save();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

var proration_date = Math.floor(Date.now() / 1000);
stripe.invoices.retrieveUpcoming(
  "cus_3R1W8PG2DmsmM9",
  "sub_3R3PlB2YlJe84a",
  {
    subscription_plan: 'premium_monthly',
    subscription_proration_date: proration_date
  },
  function(err, invoice) {
    // asynchronously called
    if (err === null) {
      var current_prorations = [];
      var cost = 0;
      for (var i = 0; i < invoice.lines.data.length; i++) {
        var invoice_item = invoice.lines.data[i];
        if (invoice_item.period.start == proration_date) {
          current_prorations.push(invoice_item);
          cost += invoice_item.amount;
        }
      }

      // Display the cost of these prorations invoice items to the end user,
      // and actually do the update when they agree.
      // To make sure that the proration is calculated the same as when it was previewed,
      // you need to pass in the proration_date parameter

    } else {
      // handle error
    }
  }
);

// later...

stripe.subscriptions.update(
  "sub_3R3PlB2YlJe84a",
  {
    plan: "premium_monthly",
    proration_date: proration_date
  },
  function(err, subscription) {
    // asynchronously called
  }
);
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";

long prorationDate = System.currentTimeMillis() / 1000L;

Map<String, Object> invoiceParams = new HashMap<String, Object>();
invoiceParams.put("customer", "cus_3R1W8PG2DmsmM9");
invoiceParams.put("subscription", "sub_3R3PlB2YlJe84a");
invoiceParams.put("subscription_plan", "premium_monthly");
invoiceParams.put("subscription_proration_date", prorationDate);

Invoice invoice = Invoice.upcoming(invoiceParams);

long cost = 0;
List<InvoiceLineItem> currentProrations = new LinkedList<InvoiceLineItem>();
for (InvoiceLineItem line: invoice.getLines().getData()) {
  if (line.getPeriod().getStart() == prorationDate) {
    currentProrations.add(line);
    cost += line.getAmount();
  }
}

// Display the cost of these prorations invoice items to the end user,
// and actually do the update when they agree.
// To make sure that the proration is calculated the same as when it was previewed,
// you need to pass in the proration_date parameter

// later...

Subscription subscription = Subscription.retrieve("sub_3R3PlB2YlJe84a");

Map<String, Object> subscriptionParams = new HashMap<String, Object>();
subscriptionParams.put("plan", "premium_monthly");
subscriptionParams.put("proration_date", prorationDate);

subscription.update(subscriptionParams);

One approach to locking in the proration date is to specify the timestamp as a hidden form field on the preview page shown to customers. This will get submitted to your server when they submit the form, and you can pass this value through to the subscription update API. It’s a good idea to make sure that the proration date value submitted with the form is from the last 10 minutes or so, to prevent the price from being affected too much. The length of the lock-in period will vary depending on what you think is appropriate for your business. It’s also a good idea to make sure this value isn’t in the future, since this would only happen if the customer tampers with their form.

Prorating the costs of plans is the default behavior. You can disable proration by setting prorate to false when making a subscription change:

# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"

subscription = Stripe::Subscription.retrieve("sub_3R3PlB2YlJe84a")
subscription.plan = "premium_monthly"
subscription.prorate = false
subscription.save
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"

subscription = stripe.Subscription.retrieve("sub_3R3PlB2YlJe84a")
subscription.plan = "premium_monthly"
subscription.prorate = false
subscription.save()
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

$subscription = \Stripe\Subscription::retrieve("sub_3R3PlB2YlJe84a");
$subscription->plan = 'premium_monthly';
$subscription->prorate = false;
$subscription->save();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

stripe.subscriptions.update(
  "sub_3R3PlB2YlJe84a",
  {
    plan: "premium_monthly",
    prorate: false
  },
  function(err, subscription) {
    // asynchronously called
  }
);
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";

Subscription subscription = Subscription.retrieve("sub_49ty4767H20z6a");

Map<String, Object> subscriptionParams = new HashMap<String, Object>();
subscriptionParams.put("plan", "premium_monthly");
subscriptionParams.put("prorate", false);

subscription.update(subscriptionParams);

With proration disabled, the customer will be billed the full amount for the new plan when the next invoice is generated.

If you want the customer to immediately pay the price difference when switching plans, you’ll need to generate an invoice after making the switch (explained below).

Note that changing a plan will trigger a customer.subscription.updated event, among other possible events.

Next up