From 5d4a7a3878bd81fcae23b9d1ea430efb3bdde0d1 Mon Sep 17 00:00:00 2001 From: ukrocks007 Date: Wed, 17 Jan 2024 20:57:54 +0530 Subject: [PATCH] Add plan, price, and subscription models --- models/plan.ts | 6 ++ models/{stripePrice.ts => price.ts} | 2 +- models/stripeProduct.ts | 6 -- models/stripeSubscription.ts | 53 ----------------- models/subscription.ts | 50 ++++++++++++++++ pages/api/teams/[slug]/payments/products.ts | 12 ++-- pages/api/webhooks/stripe.ts | 2 +- .../migration.sql | 33 ++++------- prisma/schema.prisma | 59 ++++++++----------- syncStripe.js | 28 +++------ 10 files changed, 106 insertions(+), 145 deletions(-) create mode 100644 models/plan.ts rename models/{stripePrice.ts => price.ts} (64%) delete mode 100644 models/stripeProduct.ts delete mode 100644 models/stripeSubscription.ts create mode 100644 models/subscription.ts rename prisma/migrations/{20240110102235_stripe => 20240117134554_stripe}/migration.sql (50%) diff --git a/models/plan.ts b/models/plan.ts new file mode 100644 index 000000000..97b47e515 --- /dev/null +++ b/models/plan.ts @@ -0,0 +1,6 @@ +import { prisma } from '@/lib/prisma'; + +export const getAllPlans = async () => { + const plans = await prisma.plan.findMany({}); + return plans; +}; diff --git a/models/stripePrice.ts b/models/price.ts similarity index 64% rename from models/stripePrice.ts rename to models/price.ts index a00cac247..5561cac82 100644 --- a/models/stripePrice.ts +++ b/models/price.ts @@ -1,6 +1,6 @@ import { prisma } from '@/lib/prisma'; export const getAllPrices = async () => { - const prices = await prisma.stripePrice.findMany({}); + const prices = await prisma.price.findMany({}); return prices; }; diff --git a/models/stripeProduct.ts b/models/stripeProduct.ts deleted file mode 100644 index ba4283ad1..000000000 --- a/models/stripeProduct.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { prisma } from '@/lib/prisma'; - -export const getAllProducts = async () => { - const products = await prisma.stripeProduct.findMany({}); - return products; -}; diff --git a/models/stripeSubscription.ts b/models/stripeSubscription.ts deleted file mode 100644 index c00ae65d6..000000000 --- a/models/stripeSubscription.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { prisma } from '@/lib/prisma'; - -export const createStripeSubscription = async ( - customerId: string, - subscriptionId: string, - active: boolean, - startDate: Date, - endDate: Date, - priceId: string -) => { - const stripeSubscription = await prisma.stripeSubscription.create({ - data: { - customerId, - subscriptionId, - active, - startDate, - endDate, - priceId, - }, - }); - return stripeSubscription; -}; - -export const deleteStripeSubscription = async (subscriptionId: string) => { - const stripeSubscription = await prisma.stripeSubscription.deleteMany({ - where: { - subscriptionId, - }, - }); - return stripeSubscription; -}; - -export const updateStripeSubscription = async ( - subscriptionId: string, - data: any -) => { - const stripeSubscription = await prisma.stripeSubscription.updateMany({ - where: { - subscriptionId, - }, - data, - }); - return stripeSubscription; -}; - -export const getByCustomerId = async (customerId: string) => { - const stripeSubscription = await prisma.stripeSubscription.findMany({ - where: { - customerId, - }, - }); - return stripeSubscription; -}; diff --git a/models/subscription.ts b/models/subscription.ts new file mode 100644 index 000000000..c2dd9fb09 --- /dev/null +++ b/models/subscription.ts @@ -0,0 +1,50 @@ +import { prisma } from '@/lib/prisma'; + +export const createStripeSubscription = async ( + customerId: string, + id: string, + active: boolean, + startDate: Date, + endDate: Date, + priceId: string +) => { + const subscription = await prisma.subscription.create({ + data: { + customerId, + id, + active, + startDate, + endDate, + priceId, + }, + }); + return subscription; +}; + +export const deleteStripeSubscription = async (id: string) => { + const subscription = await prisma.subscription.deleteMany({ + where: { + id, + }, + }); + return subscription; +}; + +export const updateStripeSubscription = async (id: string, data: any) => { + const subscription = await prisma.subscription.updateMany({ + where: { + id, + }, + data, + }); + return subscription; +}; + +export const getByCustomerId = async (customerId: string) => { + const subscription = await prisma.subscription.findMany({ + where: { + customerId, + }, + }); + return subscription; +}; diff --git a/pages/api/teams/[slug]/payments/products.ts b/pages/api/teams/[slug]/payments/products.ts index 9d44e26ed..393a96eeb 100644 --- a/pages/api/teams/[slug]/payments/products.ts +++ b/pages/api/teams/[slug]/payments/products.ts @@ -3,9 +3,9 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { getStripeCustomerId } from '@/lib/stripe'; import { getSession } from '@/lib/session'; import { throwIfNoTeamAccess } from 'models/team'; -import { getAllProducts } from 'models/stripeProduct'; -import { getAllPrices } from 'models/stripePrice'; -import { getByCustomerId } from 'models/stripeSubscription'; +import { getAllPlans } from 'models/plan'; +import { getAllPrices } from 'models/price'; +import { getByCustomerId } from 'models/subscription'; export default async function handler( req: NextApiRequest, @@ -37,13 +37,13 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => { const [subscriptions, products, prices] = await Promise.all([ getByCustomerId(customerId), - getAllProducts(), + getAllPlans(), getAllPrices(), ]); // create a unified object with prices associated with the product const productsWithPrices = products.map((product: any) => { - product.prices = prices.filter((price) => price.productId === product.id); + product.prices = prices.filter((price) => price.planId === product.id); return product; }); @@ -51,7 +51,7 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => { const _subscriptions: any[] = subscriptions.map((subscription: any) => { const _price = prices.find((p) => p.id === subscription.priceId); if (!_price) return undefined; - const subscriptionProduct = products.find((p) => p.id === _price.productId); + const subscriptionProduct = products.find((p) => p.id === _price.planId); return { ...subscription, diff --git a/pages/api/webhooks/stripe.ts b/pages/api/webhooks/stripe.ts index 9268b661e..ebf28ab92 100644 --- a/pages/api/webhooks/stripe.ts +++ b/pages/api/webhooks/stripe.ts @@ -7,7 +7,7 @@ import { createStripeSubscription, deleteStripeSubscription, updateStripeSubscription, -} from 'models/stripeSubscription'; +} from 'models/subscription'; export const config = { api: { diff --git a/prisma/migrations/20240110102235_stripe/migration.sql b/prisma/migrations/20240117134554_stripe/migration.sql similarity index 50% rename from prisma/migrations/20240110102235_stripe/migration.sql rename to prisma/migrations/20240117134554_stripe/migration.sql index 364a79061..a6af6b126 100644 --- a/prisma/migrations/20240110102235_stripe/migration.sql +++ b/prisma/migrations/20240117134554_stripe/migration.sql @@ -2,9 +2,8 @@ ALTER TABLE "Team" ADD COLUMN "stripeCustomerId" TEXT; -- CreateTable -CREATE TABLE "StripeSubscription" ( +CREATE TABLE "Subscription" ( "id" TEXT NOT NULL, - "subscriptionId" TEXT NOT NULL, "customerId" TEXT NOT NULL, "priceId" TEXT NOT NULL, "active" BOOLEAN NOT NULL DEFAULT false, @@ -12,50 +11,38 @@ CREATE TABLE "StripeSubscription" ( "endDate" TIMESTAMP(3) NOT NULL, "cancelAt" TIMESTAMP(3), "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "StripeSubscription_pkey" PRIMARY KEY ("id") + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- CreateTable -CREATE TABLE "StripeProduct" ( +CREATE TABLE "Plan" ( "id" TEXT NOT NULL, "description" TEXT NOT NULL, "features" TEXT[], "image" TEXT NOT NULL, - "metadata" JSONB NOT NULL, "name" TEXT NOT NULL, - "unitLabel" TEXT, "created" TIMESTAMP(3) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "StripeProduct_pkey" PRIMARY KEY ("id") + CONSTRAINT "Plan_pkey" PRIMARY KEY ("id") ); -- CreateTable -CREATE TABLE "StripePrice" ( +CREATE TABLE "Price" ( "id" TEXT NOT NULL, "billingScheme" TEXT NOT NULL, - "created" TIMESTAMP(3) NOT NULL, "currency" TEXT NOT NULL, - "customUnitAmount" TEXT, - "livemode" BOOLEAN NOT NULL, - "lookupKey" TEXT, - "metadata" JSONB NOT NULL, - "nickname" TEXT, - "productId" TEXT NOT NULL, + "planId" TEXT NOT NULL, "recurring" JSONB NOT NULL, - "tiersMode" TEXT NOT NULL, "type" TEXT NOT NULL, - "unitAmount" TEXT, - "unitAmountDecimal" TEXT, + "created" TIMESTAMP(3) NOT NULL, - CONSTRAINT "StripePrice_pkey" PRIMARY KEY ("id") + CONSTRAINT "Price_pkey" PRIMARY KEY ("id") ); -- CreateIndex -CREATE UNIQUE INDEX "StripeSubscription_subscriptionId_key" ON "StripeSubscription"("subscriptionId"); +CREATE UNIQUE INDEX "Subscription_id_key" ON "Subscription"("id"); -- AddForeignKey -ALTER TABLE "StripePrice" ADD CONSTRAINT "StripePrice_productId_fkey" FOREIGN KEY ("productId") REFERENCES "StripeProduct"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Price" ADD CONSTRAINT "Price_planId_fkey" FOREIGN KEY ("planId") REFERENCES "Plan"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 03b9f7b90..993199a84 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -137,49 +137,38 @@ model ApiKey { team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) } -model StripeSubscription { - id String @id @default(uuid()) - subscriptionId String @unique - customerId String - priceId String - active Boolean @default(false) - startDate DateTime - endDate DateTime - cancelAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) +model Subscription { + id String @unique + customerId String + priceId String + active Boolean @default(false) + startDate DateTime + endDate DateTime + cancelAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } -model StripeProduct { - id String @id @default(uuid()) +model Plan { + id String @id @default(uuid()) description String features String[] image String - metadata Json name String - unitLabel String? created DateTime - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) - StripePrice StripePrice[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + Price Price[] } -model StripePrice { - id String @id @default(uuid()) - billingScheme String - created DateTime - currency String - customUnitAmount String? - livemode Boolean - lookupKey String? - metadata Json - nickname String? - productId String - recurring Json - tiersMode String - type String - unitAmount String? - unitAmountDecimal String? +model Price { + id String @id @default(uuid()) + billingScheme String + currency String + planId String + recurring Json + type String + created DateTime - product StripeProduct @relation(fields: [productId], references: [id], onDelete: Cascade) + plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade) } diff --git a/syncStripe.js b/syncStripe.js index 315e10f6b..642d9d31f 100644 --- a/syncStripe.js +++ b/syncStripe.js @@ -53,8 +53,8 @@ process.on('uncaughtException', (error) => { async function printStats(prisma) { const [productCount, priceCount] = await Promise.all([ - prisma.stripeProduct.count(), - prisma.stripePrice.count(), + prisma.plan.count(), + prisma.price.count(), ]); console.log('Products synced:', productCount); @@ -63,9 +63,9 @@ async function printStats(prisma) { async function cleanup(prisma) { // delete all prices from the database - await prisma.stripePrice.deleteMany({}); + await prisma.price.deleteMany({}); // Delete all products and prices from the database - await prisma.stripeProduct.deleteMany({}); + await prisma.plan.deleteMany({}); } function getStripeInstance() { @@ -86,25 +86,15 @@ function getStripeInstance() { async function seedPrices(prices, prisma) { for (const data of prices) { try { - await prisma.stripePrice.create({ + await prisma.price.create({ data: { id: data.id, billingScheme: data.billing_scheme, - created: new Date(data.created * 1000), currency: data.currency, - customUnitAmount: data.custom_unit_amount - ? data.custom_unit_amount.toString() - : null, - livemode: data.livemode, - lookupKey: data.lookup_key, - metadata: data.metadata, - nickname: data.nickname, - productId: data.product, + planId: data.product, recurring: data.recurring, - tiersMode: data.tiers_mode ? data.tiers_mode.toString() : '', type: data.type, - unitAmount: data.unit_amount ? data.unit_amount.toString() : null, - unitAmountDecimal: data.unit_amount_decimal, + created: new Date(data.created * 1000), }, }); } catch (error) { @@ -116,15 +106,13 @@ async function seedPrices(prices, prisma) { async function seedProducts(products, prisma) { for (const data of products) { try { - await prisma.stripeProduct.create({ + await prisma.plan.create({ data: { id: data.id, description: data.description || '', features: (data.features || []).map((a) => a.name), image: data.images.length > 0 ? data.images[0] : '', - metadata: data.metadata, name: data.name, - unitLabel: data.unit_label, created: new Date(data.created * 1000), }, });