mickasmt / next-saas-stripe-starter

Open-source SaaS Starter with User Roles & Admin Panel. Built using Next.js 14, Prisma, Neon, Auth.js v5, Resend, React Email, Shadcn/ui, Stripe, Server Actions.
https://next-saas-stripe-starter.vercel.app
MIT License
1.21k stars 191 forks source link

stripe webhook order issue #42

Closed addlistener closed 6 days ago

addlistener commented 2 weeks ago
image

I tried using the strip webhook and found that invoice.payment_succeeded is triggered 1 second before checkout.session.completed upon initial payment of a customer. So the query by subscription id will fail https://github.com/mickasmt/next-saas-stripe-starter/blob/26cd811f39334a813605a8a98324e44519482de7/app/api/webhooks/stripe/route.ts#L58-L60

It will not affect the final result since we updated the database when checkout.session.completed. However it is indeed wrong logically. And stripe will trigger the webhook 1 hour later and then it succeeds.

The reason I found is that userId is only available in checkout.session.completed. So we need these 2 events in combination. Still trying to figure out a better way to model this process...

mickasmt commented 2 weeks ago

@addlistener Thank you for reporting this issue. I have noticed the bug as well and will work on it this week.

devstack-be commented 2 weeks ago

I have previously worked with Stripe webhooks events in other projects. I believe the "checkout.session.completed" event is always triggered for the initial subscription. When the user updates their subscription, the "invoice.payment_succeeded" event is triggered but not the "checkout.session.completed" event. Therefore, both events are necessary. It is probably just a matter of adding a check on the "invoice.payment_succeeded" event and verifying if a user already has this "subscriptionId".

mrbirddev commented 1 week ago

Folks, this is what I ended up with

    if (event.type === "invoice.payment_succeeded") {
      const invoice = event.data.object;

      // https://medium.com/@nicolas_32131/stripe-billing-webhooks-for-saas-7d835feb30cd
      // when a user creates a new subscription,
      // the billing_reason will be set to subscription_create.
      //
      // We are skipping because the handle this in "checkout.session.completed"
      // The real scenario -> https://github.com/mickasmt/next-saas-stripe-starter/issues/42
      if (invoice.billing_reason !== 'subscription_create') {
        // Retrieve the subscription details from Stripe.
        const subscription = await stripeInstance.subscriptions.retrieve(
          invoice.subscription as string
        );

        invariant(subscription.items.data[0]);
        ...
    }

I guess why the shadcn project does this is that checkout.session.completed contains userId while invoice.xxx does not.

mickasmt commented 1 week ago

Hello everyone! Sorry for my late response. Even after trying the suggestions locally, I'm still facing the same issue. If anyone has code that can solve this problem, please contribute a PR. I'll review and merge it if it works.

mickasmt commented 6 days ago

Thanks everyone for your help! Its solve now.