#stiggz_subscription-incomplete
1 messages ¡ Page 1 of 1 (latest)
đ 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.
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
}
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.
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:
- Customer started checkout session A
- Subscription A was created
- Payment for subscription A failed
- Customer started checkout session B
- Subscription B was created
- Payment for subscription B succeeded
- Subscription B was persisted to our DB
- Checkout B completed successfully
- Subscription A was voided
- Subscription A updated, replacing subscription B in our DB
- 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.
still looking into this
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.
Do you have the counter-example for Customer B?
Sorry? I don't follow.
In my example above, both Checkout A and Checkout B were the same customer
Apologies, I misread your description, but I understand what you mean now
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.
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.
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.
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.
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!
You're welcome! Also, if you aren't aware, our Test Clocks feature is really helpful for watching these kinds of scenarios play out:
Great, I was not aware. I'll play around with these a bit as well to test