From 19e960f5932ce8066cd0abc555c7c38bdaa9a53f Mon Sep 17 00:00:00 2001 From: Mythie Date: Wed, 31 May 2023 21:11:54 +1000 Subject: [PATCH] fix: improve stripe webhook endpoint Improve the stripe webhook endpoint by checking for subscriptions prior to performing an update to handle cases where accounts have no created subscription. This can happen in sitations such as when a checkout_session has been created but the payment fails. --- packages/lib/stripe/handlers/webhook.ts | 85 ++++++++++++++++++------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/packages/lib/stripe/handlers/webhook.ts b/packages/lib/stripe/handlers/webhook.ts index 390fc21de..609e652fe 100644 --- a/packages/lib/stripe/handlers/webhook.ts +++ b/packages/lib/stripe/handlers/webhook.ts @@ -25,9 +25,7 @@ export const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) }); } - log("constructing body...") const body = await buffer(req); - log("constructed body") const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!); log("event-type:", event.type); @@ -70,23 +68,38 @@ export const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) if (event.type === "invoice.payment_succeeded") { const invoice = event.data.object as Stripe.Invoice; + if (invoice.billing_reason !== "subscription_cycle") { + return res.status(200).json({ + success: true, + message: "Webhook received", + }); + } + const customerId = typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id; const subscription = await stripe.subscriptions.retrieve(invoice.subscription as string); - await prisma.subscription.update({ + const hasSubscription = await prisma.subscription.findFirst({ where: { customerId, }, - data: { - status: SubscriptionStatus.ACTIVE, - planId: subscription.id, - priceId: subscription.items.data[0].price.id, - periodEnd: new Date(subscription.current_period_end * 1000), - }, }); + if (hasSubscription) { + await prisma.subscription.update({ + where: { + customerId, + }, + data: { + status: SubscriptionStatus.ACTIVE, + planId: subscription.id, + priceId: subscription.items.data[0].price.id, + periodEnd: new Date(subscription.current_period_end * 1000), + }, + }); + } + return res.status(200).json({ success: true, message: "Webhook received", @@ -98,15 +111,23 @@ export const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) const customerId = failedInvoice.customer as string; - await prisma.subscription.update({ + const hasSubscription = await prisma.subscription.findFirst({ where: { customerId, }, - data: { - status: SubscriptionStatus.PAST_DUE, - }, }); + if (hasSubscription) { + await prisma.subscription.update({ + where: { + customerId, + }, + data: { + status: SubscriptionStatus.PAST_DUE, + }, + }); + } + return res.status(200).json({ success: true, message: "Webhook received", @@ -118,18 +139,26 @@ export const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) const customerId = updatedSubscription.customer as string; - await prisma.subscription.update({ + const hasSubscription = await prisma.subscription.findFirst({ where: { customerId, }, - data: { - status: SubscriptionStatus.ACTIVE, - planId: updatedSubscription.id, - priceId: updatedSubscription.items.data[0].price.id, - periodEnd: new Date(updatedSubscription.current_period_end * 1000), - }, }); + if (hasSubscription) { + await prisma.subscription.update({ + where: { + customerId, + }, + data: { + status: SubscriptionStatus.ACTIVE, + planId: updatedSubscription.id, + priceId: updatedSubscription.items.data[0].price.id, + periodEnd: new Date(updatedSubscription.current_period_end * 1000), + }, + }); + } + return res.status(200).json({ success: true, message: "Webhook received", @@ -141,15 +170,23 @@ export const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) const customerId = deletedSubscription.customer as string; - await prisma.subscription.update({ + const hasSubscription = await prisma.subscription.findFirst({ where: { customerId, }, - data: { - status: SubscriptionStatus.INACTIVE, - }, }); + if (hasSubscription) { + await prisma.subscription.update({ + where: { + customerId, + }, + data: { + status: SubscriptionStatus.INACTIVE, + }, + }); + } + return res.status(200).json({ success: true, message: "Webhook received",