hssm
hssm

Reputation: 31

Is there a way to avoid updating the subscription object until the payment has succeeded?

Suppose a customer goes to my online shop and purchases an upgrade to their existing subscription. They insert their credit card details and submit the purchase. The payment fails.

There are two scenarios where this is a problem that I have not found a proper solution to.

Scenario 1

Some subscription updates are not charged within the same update function (e.g., monthly to monthly plan), meaning you have to update the subscription first, generate an invoice, then pay it manually. The subscription update and payment are no longer atomic. That means even if the payment fails, the subscription now contains the details of the desired plan that they haven't paid for yet.

My current solution is to record the starting subscription details and restore them at the end if the payment fails. This feels intuitively wrong, but it works in keeping the subscription in sync with what the user has actually paid for.

I am interested in keeping Stripe as the "source of truth" about my subscriptions, so leaving unpaid changes lingering around in an incomplete state makes this difficult.

Scenario 2

The above strategy falls apart when SCA is introduced, because now I can't know when the payment fails until a webhook on another server is notified, and it doesn't have the original subscription details anymore. The subscription and invoice are then left in a "Past Due" state. I don't want to be in that state, because the purchase is happening on a website shopping cart, so it either succeeds or fails at that point only.

So I am stuck in a scenario where I have an incomplete subscription and incomplete payment, and on the next attempt to update the subscription (e.g., user enters a working credit card), my server rejects the purchase because it thinks the new plan is identical to the current plan.

So my main question is, is there a way to defer updates to Subscription objects until payments are complete?

Or is there a better approach to handling subscription updates entirely?


The more I look into this the more hopeless this seems. Imagine you have a customer with this subscription:

Plan: Pro
Interval: Yearly
Quantity: 5
Due: June 20th
Status: active

They try to buy an upgrade to a quanttiy of 10. The product is very expensive, so we want to charge them for it now before provisioning instead of waiting until the next billing day, which is 7 months away. The payment fails SCA authentication. Now the customer has this subscription:

Plan: Pro
Interval: Yearly
Quantity: 10
Due: Nov 13 (now + 1 year)
Status: past_due

What do we do now? You cannot revert the subscription, the customer has a higher plan without paying for it, and they won't be prompted to pay for their accumulated charges until Nov 2020 (I think that's how the due date works).

There must be a way to pay for a plan before changing the subscription that I am missing.

Upvotes: 3

Views: 1240

Answers (1)

hssm
hssm

Reputation: 31

Stripe support suggested I create a new subscription to charge against and cancel the old one once a successful payment comes through. This works, but not without some difficulties.

Since I also need proration, I had to do some of the invoice steps manually. My solution ends up being this:

  • I feed the existing subscription into an Invoice::upcoming for the purpose of grabbing the monetary value change that proration will cause. I set the quantity of the plan to 0 which simulates a cancellation (at least, money-wise).

  • Use the result to help create an InvoiceItem. Fetch the amount, currency, and description from the upcoming invoice, so the line item appears to be identical to the built-in ones that normally get generated.

  • Create a new subscription with the desired upgraded plan. The invoice item from above is automagically included on the invoice that the user ends up paying from this action.

  • If the payment fails, simply ignore the new subscription, or clean it up if you need to.

  • If the payment succeeds, cancel the old subscription. This step needs to be done in two places, the server code that initiated the transaction, and the webhook that would ordinarily provision subscriptions once they are paid for. Payment success can happen in different places now with SCA.

This solves my issue of polluting a Subscription object for the sake of issuing a payment for an upgrade. It handles the SCA workflow for both success and failure, and the original Subscription is untouched if the payment fails at any stage.

I followed the example from this excellent write-up for the first half.

Upvotes: 0

Related Questions