From 34f5555791fe8a02901ed679df27959f6a4f76f6 Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Wed, 26 Feb 2025 12:48:48 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Keep=20stripe=20customer=20referenc?= =?UTF-8?q?e=20in=20sync=20using=20webhook=20(#1580)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webhook/handlers/customer/created.ts | 30 +++++++++++++++++++ .../webhook/handlers/customer/deleted.ts | 16 ++++++++++ .../stripe/webhook/handlers/customer/index.ts | 2 ++ .../app/api/stripe/webhook/handlers/index.ts | 5 ++++ 4 files changed, 53 insertions(+) create mode 100644 apps/web/src/app/api/stripe/webhook/handlers/customer/created.ts create mode 100644 apps/web/src/app/api/stripe/webhook/handlers/customer/deleted.ts create mode 100644 apps/web/src/app/api/stripe/webhook/handlers/customer/index.ts diff --git a/apps/web/src/app/api/stripe/webhook/handlers/customer/created.ts b/apps/web/src/app/api/stripe/webhook/handlers/customer/created.ts new file mode 100644 index 000000000..91f9abd9b --- /dev/null +++ b/apps/web/src/app/api/stripe/webhook/handlers/customer/created.ts @@ -0,0 +1,30 @@ +import type { Stripe } from "@rallly/billing"; +import { prisma } from "@rallly/database"; +import { z } from "zod"; + +const customerMetadataSchema = z.object({ + userId: z.string(), +}); + +export async function onCustomerCreated(event: Stripe.Event) { + const customer = event.data.object as Stripe.Customer; + + const res = customerMetadataSchema.safeParse(customer.metadata); + + if (!res.success) { + // If there's no userId in metadata, ignore the event + return; + } + + const { userId } = res.data; + + // Update the user with the customer id + await prisma.user.update({ + where: { + id: userId, + }, + data: { + customerId: customer.id, + }, + }); +} diff --git a/apps/web/src/app/api/stripe/webhook/handlers/customer/deleted.ts b/apps/web/src/app/api/stripe/webhook/handlers/customer/deleted.ts new file mode 100644 index 000000000..3df45e44f --- /dev/null +++ b/apps/web/src/app/api/stripe/webhook/handlers/customer/deleted.ts @@ -0,0 +1,16 @@ +import type { Stripe } from "@rallly/billing"; +import { prisma } from "@rallly/database"; + +export async function onCustomerDeleted(event: Stripe.Event) { + const customer = event.data.object as Stripe.Customer; + + // Find and update the user with this customerId + await prisma.user.updateMany({ + where: { + customerId: customer.id, + }, + data: { + customerId: null, + }, + }); +} diff --git a/apps/web/src/app/api/stripe/webhook/handlers/customer/index.ts b/apps/web/src/app/api/stripe/webhook/handlers/customer/index.ts new file mode 100644 index 000000000..f3f797530 --- /dev/null +++ b/apps/web/src/app/api/stripe/webhook/handlers/customer/index.ts @@ -0,0 +1,2 @@ +export * from "./created"; +export * from "./deleted"; diff --git a/apps/web/src/app/api/stripe/webhook/handlers/index.ts b/apps/web/src/app/api/stripe/webhook/handlers/index.ts index a95a6a154..82b401888 100644 --- a/apps/web/src/app/api/stripe/webhook/handlers/index.ts +++ b/apps/web/src/app/api/stripe/webhook/handlers/index.ts @@ -2,6 +2,7 @@ import type { Stripe } from "@rallly/billing"; import { onCheckoutSessionCompleted } from "./checkout/completed"; import { onCheckoutSessionExpired } from "./checkout/expired"; +import { onCustomerCreated, onCustomerDeleted } from "./customer"; import { onCustomerSubscriptionCreated } from "./customer-subscription/created"; import { onCustomerSubscriptionDeleted } from "./customer-subscription/deleted"; import { onCustomerSubscriptionUpdated } from "./customer-subscription/updated"; @@ -17,6 +18,10 @@ export function getEventHandler(eventType: Stripe.Event["type"]) { return onCheckoutSessionCompleted; case "checkout.session.expired": return onCheckoutSessionExpired; + case "customer.created": + return onCustomerCreated; + case "customer.deleted": + return onCustomerDeleted; case "customer.subscription.created": return onCustomerSubscriptionCreated; case "customer.subscription.deleted":