#stiggz_subscription-incomplete

1 messages ¡ Page 1 of 1 (latest)

boreal waspBOT
#

👋 Welcome to your new thread!

⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

🔗 This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1293683232445042701

📝 Have more to share? Add more details, code, screenshots, videos, etc. below.

tough sentinel
#

Attached is an example of a series of events that can cause this problem.

#

I guess I find it slightly surprising that when a checkout session expires and its associated subscription updates, that that subscription should update instead of delete. If it were to delete, there'd be no problem, since the webhook handler for customer.subscription.deleted would just delete the subscription if it exists in the DB. But since it updates, it's possible to replace the existing valid subscription.

#

If useful, here's the relevant code block for upserting the subscription in my DB:

static async upsert(
    stripeSubscriptionId: string,
  ): Promise<Model.Subscription> {
    const subscription = await Stripe.subscription(stripeSubscriptionId)
    const stripeCustomerId = subscription.customer as string
    let user = await DB.User.firstWhere({ stripeCustomerId }, 'writer')
    if (!user) user = await this.createCustomer(stripeCustomerId)

    const res = await DB.Subscription.upsert(
      {
        userId: user.id,
        stripeSubscriptionId,
        ...this.extractSubscription(subscription),
      },
      'userId',
    )

    if (user.email) Email.updateSubscription(user.email, res.tier)
    return res
  }
celest vessel
#

Hi there! Looking into this

#

"when a checkout session expires and its associated subscription updates, that that subscription should update instead of delete."

Do you have an example of what you're referring to here? A Checkout Session id (cs_live_abc123) or Subscription id (sub_123) would be great.

tough sentinel
#

Yeah give me one sec to look here

#

I think the subscription is: sub_1Q31pxCjVzeFIn9gD68ECoWL

#

And the corresponding checkout session is: cs_live_b1n2Xt7zBsEpAqch2bKE1VZzxkB8WX5uaUMqCOZODyKN7B2tLtGSSZ8Cqp

#

From the event log it appears that what happened was:

  1. Customer started checkout session A
  2. Subscription A was created
  3. Payment for subscription A failed
  4. Customer started checkout session B
  5. Subscription B was created
  6. Payment for subscription B succeeded
  7. Subscription B was persisted to our DB
  8. Checkout B completed successfully
  9. Subscription A was voided
  10. Subscription A updated, replacing subscription B in our DB
  11. Checkout A expired
#

Or something close to that. In that case, I'm not quite sure why subscription A being voided/cancelled doesn't trigger a subscription.delete event instead of a subscription.update event. If I look at this customer in our dashboard ( cus_QurZ5qxijmIMmA), I can see 2 subscriptions and 2 invoices. One is cancelled/voided, one is active/paid.

celest vessel
#

still looking into this

tough sentinel
#

Cool, thank you! I can get more examples if you need, had a handful of customers experience something similar. Looks like it usually happens after a payment confirmation/failure. I could probably do something like check the cancellation status on the update event and then call delete instead of upsert to fix the issue, but mostly curious what the recommended approach is.

celest vessel
#

Do you have the counter-example for Customer B?

tough sentinel
#

Sorry? I don't follow.

#

In my example above, both Checkout A and Checkout B were the same customer

celest vessel
#

Apologies, I misread your description, but I understand what you mean now

tough sentinel
#

Here's an annotated event log for the customer, where A = failure and B = success. Looks like there's a 3rd checkout session as well, but it doesn't make a difference.

celest vessel
#

The answer is essentially that we don't send a customer.subscription.deleted event because the Subscription isn't "deleted." We move the Subscription into incomplete_expired, (which is the status you get on the customer.subscription.updated) event, but this isn't the same thing as deleting the Subscription. There's some documentation and a helpful graphic here: https://docs.stripe.com/billing/subscriptions/overview#subscription-lifecycle

#

The idea about reacting to the customer.subscription.updated event where the Subscription status has moved into incomplete_expired and hitting the 'delete Subscription' endpoint seems reasonable.

tough sentinel
#

If the user were to go into the stripe customer portal and cancel their subscription, then at the end of the month I thiiink what happens is we get a customer.subscription.deleted event when the subscription lapses at the period_end date. But maybe that's not quite right, and we instead get an updated event with the incomplete_expired status?

#

The only place it might make a material difference is if a user were to cancel their existing subscription, then come back a couple months later, say, and re-subscribe. In that case, rather than going through the checkout flow again, we might have a shorter checkout where they don't need to re-input their billing details, and they can reinstate their subscription in one using the billing info on file.

celest vessel
#

incomplete_expired status is only for the case where the first payment fails - we have a reference for this lower on that same docs page.

tough sentinel
#

Ok perfect! In that case it should be perfectly safe to just treat that the same as the customer.subscription.deleted webhook event. Thanks so much for your help!

celest vessel
tough sentinel
#

Great, I was not aware. I'll play around with these a bit as well to test

boreal waspBOT