From aebea5a41c430dec17a717df209ff3f32943920c Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Thu, 27 Feb 2025 15:23:01 +0000 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Make=20user=20required=20i?= =?UTF-8?q?n=20subscription=20model=20(#1585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handlers/customer-subscription/created.ts | 74 ++++++++++++++----- packages/billing/package.json | 1 - .../normalize-subscription-metadata.ts | 67 ----------------- .../migration.sql | 30 ++++++++ packages/database/prisma/schema.prisma | 7 +- 5 files changed, 87 insertions(+), 92 deletions(-) delete mode 100644 packages/billing/src/scripts/normalize-subscription-metadata.ts create mode 100644 packages/database/prisma/migrations/20250227110115_make_subscription_user_required/migration.sql diff --git a/apps/web/src/app/api/stripe/webhook/handlers/customer-subscription/created.ts b/apps/web/src/app/api/stripe/webhook/handlers/customer-subscription/created.ts index 4013a3376..0f1b42d7a 100644 --- a/apps/web/src/app/api/stripe/webhook/handlers/customer-subscription/created.ts +++ b/apps/web/src/app/api/stripe/webhook/handlers/customer-subscription/created.ts @@ -24,26 +24,60 @@ export async function onCustomerSubscriptionCreated(event: Stripe.Event) { throw new Error("Missing user ID"); } - // Create and update user - await prisma.user.update({ - where: { - id: res.data.userId, - }, - data: { - subscription: { - create: { - id: subscription.id, - active: isActive, - priceId, - currency, - interval, - amount, - status: subscription.status, - createdAt: toDate(subscription.created), - periodStart: toDate(subscription.current_period_start), - periodEnd: toDate(subscription.current_period_end), + const userId = res.data.userId; + + // Check if user already has a subscription + const existingUser = await prisma.user.findUnique({ + where: { id: userId }, + include: { subscription: true }, + }); + + if (!existingUser) { + throw new Error(`User with ID ${userId} not found`); + } + + // If user already has a subscription, update it or replace it + if (existingUser.subscription) { + // Update the existing subscription with new data + await prisma.subscription.update({ + where: { id: existingUser.subscription.id }, + data: { + id: subscription.id, + active: isActive, + priceId, + currency, + interval, + amount, + status: subscription.status, + createdAt: toDate(subscription.created), + periodStart: toDate(subscription.current_period_start), + periodEnd: toDate(subscription.current_period_end), + cancelAtPeriodEnd: subscription.cancel_at_period_end, + }, + }); + } else { + // Create a new subscription for the user + await prisma.user.update({ + where: { + id: userId, + }, + data: { + subscription: { + create: { + id: subscription.id, + active: isActive, + priceId, + currency, + interval, + amount, + status: subscription.status, + createdAt: toDate(subscription.created), + periodStart: toDate(subscription.current_period_start), + periodEnd: toDate(subscription.current_period_end), + cancelAtPeriodEnd: subscription.cancel_at_period_end, + }, }, }, - }, - }); + }); + } } diff --git a/packages/billing/package.json b/packages/billing/package.json index cbf279a12..04036951d 100644 --- a/packages/billing/package.json +++ b/packages/billing/package.json @@ -7,7 +7,6 @@ "./*": "./src/*.ts" }, "scripts": { - "normalize-subscription-metadata": "dotenv -e ../../.env -- tsx ./src/scripts/normalize-metadata.ts", "checkout-expiry": "dotenv -e ../../.env -- tsx ./src/scripts/checkout-expiry.ts", "subscription-data-sync": "dotenv -e ../../.env -- tsx ./src/scripts/subscription-data-sync.ts", "sync-payment-methods": "dotenv -e ../../.env -- tsx ./src/scripts/sync-payment-methods.ts", diff --git a/packages/billing/src/scripts/normalize-subscription-metadata.ts b/packages/billing/src/scripts/normalize-subscription-metadata.ts deleted file mode 100644 index aba1b136e..000000000 --- a/packages/billing/src/scripts/normalize-subscription-metadata.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable no-console */ -/** - * This script will go through all subscriptions and add the userId to the metadata. - */ -import { prisma } from "@rallly/database"; - -import { stripe } from "../lib/stripe"; - -async function getSubscriptionsWithMissingMetadata( - starting_after?: string, -): Promise { - const res: string[] = []; - - const subscriptions = await stripe.subscriptions.list({ - limit: 100, - starting_after, - }); - subscriptions.data.forEach((subscription) => { - if (!subscription.metadata.userId) { - res.push(subscription.id); - } - }); - if (subscriptions.has_more) { - return [ - ...res, - ...(await getSubscriptionsWithMissingMetadata( - subscriptions.data[subscriptions.data.length - 1].id, - )), - ]; - } else { - return res; - } -} - -async function normalizeSubscriptionMetadata() { - const subscriptions = await getSubscriptionsWithMissingMetadata(); - - console.log( - `Found ${subscriptions.length} subscriptions with missing metadata`, - ); - - for (const subscriptionId of subscriptions) { - const user = await prisma.user.findFirst({ - select: { - id: true, - }, - where: { - subscriptionId: subscriptionId, - }, - }); - - if (!user) { - console.log("User not found for subscription", subscriptionId); - continue; - } - - await stripe.subscriptions.update(subscriptionId, { - metadata: { - userId: user.id, - }, - }); - - console.log("Updated subscription", subscriptionId); - } -} - -normalizeSubscriptionMetadata(); diff --git a/packages/database/prisma/migrations/20250227110115_make_subscription_user_required/migration.sql b/packages/database/prisma/migrations/20250227110115_make_subscription_user_required/migration.sql new file mode 100644 index 000000000..95033e956 --- /dev/null +++ b/packages/database/prisma/migrations/20250227110115_make_subscription_user_required/migration.sql @@ -0,0 +1,30 @@ +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_subscription_id_fkey"; + +-- DropIndex +DROP INDEX "users_subscription_id_key"; + +-- AlterTable +ALTER TABLE "subscriptions" ADD COLUMN "user_id" TEXT; + +-- Populate user_id in subscriptions table using data from users table +UPDATE "subscriptions" s +SET "user_id" = u.id +FROM "users" u +WHERE u."subscription_id" = s.id; + +-- Delete orphaned subscriptions (subscriptions without a corresponding user) +DELETE FROM "subscriptions" +WHERE "user_id" IS NULL; + +-- Make user_id NOT NULL after populating data +ALTER TABLE "subscriptions" ALTER COLUMN "user_id" SET NOT NULL; + +-- AlterTable +ALTER TABLE "users" DROP COLUMN "subscription_id"; + +-- CreateIndex +CREATE UNIQUE INDEX "subscriptions_user_id_key" ON "subscriptions"("user_id"); + +-- AddForeignKey +ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index dadb59d99..efa51168c 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -49,7 +49,6 @@ model User { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime? @updatedAt @map("updated_at") customerId String? @map("customer_id") - subscriptionId String? @unique @map("subscription_id") comments Comment[] polls Poll[] @@ -58,8 +57,7 @@ model User { accounts Account[] participants Participant[] paymentMethods PaymentMethod[] - - subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull) + subscription Subscription? @relation("UserToSubscription") @@map("users") } @@ -111,8 +109,9 @@ model Subscription { periodStart DateTime @map("period_start") periodEnd DateTime @map("period_end") cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end") + userId String @unique @map("user_id") - user User? + user User @relation("UserToSubscription", fields: [userId], references: [id], onDelete: Cascade) @@map("subscriptions") }