laravel / cashier-stripe

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.
https://laravel.com/docs/billing
MIT License
2.38k stars 679 forks source link

Plan swaps for multiplan subscriptions #912

Closed Xenonym closed 4 years ago

Xenonym commented 4 years ago

The Cashier documentation suggests that changing plans is only allowed for single plan subscriptions:

When working with multiplan subscriptions, you cannot use the swap and swapAndInvoice methods. Instead, you should use the addPlan, addPlanAndInvoice, and removePlan methods.

However, removing and adding a plan is not equivalent to swapping plans. Specifically, parameters such as metadata, quantity and tax_rates would have to be manually preserved from the removed subscription item and manually added back to the new one.

Would it be possible to extend swap and swapAndInvoice to take an extra plan parameter? For example:

$user->subscription('default')->swap('old-plan-id', 'new-plan-id');
$user->subscription('default')->swapAndInvoice('old-plan-id', 'new-plan-id');

The Stripe docs suggest that swapping plans would work with multiplan subscriptions too. This would ensure that extra parameters attached to the subscription item are not lost when plans in a multiplan subscription need to be changed.

driesvints commented 4 years ago

Specifically, parameters such as metadata, quantity and tax_rates would have to be manually preserved from the removed subscription item and manually added back to the new one.

Hey @Xenonym. Both the addPlan and addPlanAndInvoice have a third $options argument so you can send extra options to Stripe. Their second argument is the $quantity. Is that what you're looking for? Afaik the removePlan doesn't need any more options? Tax Rates are accounted for when using addPlan and addPlanAndInvoice.

Would it be possible to extend swap and swapAndInvoice to take an extra plan parameter?

This isn't possible because these methods already have extra parameters (look at the source code).

The Stripe docs suggest that swapping plans would work with multiplan subscriptions too.

I don't see anything specific in the link that details multiplans? Can you point out the specific parts?

Xenonym commented 4 years ago

Hey there @driesvints, thanks for explaining!

Both the addPlan and addPlanAndInvoice have a third $options argument so you can send extra options to Stripe. Their second argument is the $quantity. Is that what you're looking for?

Not quite - while I can add plans with quantity and options, I can't preserve them when I need to swap plans. For example, say I add a plan with some options and a quantity:

$subscription->addPlan('plan_a', 2, ['metadata' => ['some' => 'data']]);

Suppose I want to switch from 'plan_a' to 'plan_b'. If I remove plan_a, this data is lost:

$subscription->removePlan('plan_a');
$subscription->addPlan('plan_b', /* Oops, we lost the quantity and metadata of plan_a. What now? */); 

With the current implementation, I would have to manually preserve all the options on the existing subscription item, and pass them again when adding plan_b:

$oldPlan = $subscription->findItemOrFail('plan_a');
$subscription->removePlan('plan_a');
$subscription->addPlan('plan_b', $oldPlan->quantity, ['metadata' =>  $oldPlan->metadata]); 

Even with this workaround, I am not sure what the implications are for prorations: since the old plan was removed, we are treating the new plan as an entirely new addition - would the change be prorated then?

I don't see anything specific in the link that details multiplans? Can you point out the specific parts?

Ah, realise I wasn't being clear there. I am referring to the code sample on the page:

\Stripe\Subscription::update('sub_49ty4767H20z6a', [
  'cancel_at_period_end' => false,
  'items' => [
    [
      'id' => $subscription->items->data[0]->id,
      'plan' => 'plan_CBb6IXqvTLXp3f',
    ],
  ],
]);

If you read the code closely, you can pass in an array of subscription items, with the subscription item IDs you want to update, as well as the new plan to update to. This is one mechanism to update only specific subscription items in a subscription AKA swapping plans in a multi plan subscription.

This isn't possible because these methods already have extra parameters (look at the source code).

How about having swap / swapAndInvoice methods on a SubscriptionItem? This would correspond cleanly to updating a subscription item on Stripe, and a more straightforward method than going through the above method for swapping individual plans.

driesvints commented 4 years ago

@Xenonym I spent the day figuring out how we could support swapping for multiplan subscriptions and came up with the following pr: https://github.com/laravel/cashier/pull/915

I'm pretty happy with how it turned out. It should support everything you want from above.

~There's one caveat however. Stripe apparently allows for subscription items with duplicate plans. I'm trying to figure out why that is with the Stripe team.~

This appears to be a misunderstanding from my end. Subscriptions can't have duplicate plans which Stripe confirmed to me.

Xenonym commented 4 years ago

@driesvints Wow, the PR looks great! Thanks so much for working on this so quickly. Just one minor question - will leave it on the PR.