From 68ba0258a6b0c96a619d4bca8c21098b0a1173a8 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Thu, 29 Aug 2024 17:49:02 +0800 Subject: [PATCH] (breaking change) use stripe customer id instead of subscription id --- README.md | 6 + frontend/drizzle/0018_living_taskmaster.sql | 2 + frontend/drizzle/0019_chief_mephisto.sql | 1 + frontend/drizzle/meta/0018_snapshot.json | 461 ++++++++++++++++++ frontend/drizzle/meta/0019_snapshot.json | 432 ++++++++++++++++ frontend/drizzle/meta/_journal.json | 14 + frontend/src/lib/db/schema.ts | 18 +- .../(app)/dashboard/billing/+page.server.ts | 12 +- .../billing/plan/pro/+page.server.ts | 32 +- .../billing/plan/pro/success/+page.server.ts | 19 +- .../billing/plan/pro/success/+page.svelte | 23 + .../(auth)/signup/(components)/form.svelte | 1 + frontend/src/routes/webhook/stripe/+server.ts | 5 +- 13 files changed, 980 insertions(+), 46 deletions(-) create mode 100644 frontend/drizzle/0018_living_taskmaster.sql create mode 100644 frontend/drizzle/0019_chief_mephisto.sql create mode 100644 frontend/drizzle/meta/0018_snapshot.json create mode 100644 frontend/drizzle/meta/0019_snapshot.json create mode 100644 frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.svelte diff --git a/README.md b/README.md index 3f6cece..e698d1a 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ go build ## Breaking Changes (For builds before this date) +### 29 August 2024 + +Using stripe customer id instead of stripe subscription id + +Existing customer subscription id will be removed from database, so have to manually find every customer with the same email and fill in their ids + ### 23 July 2024 Transition from using ipbase to using geoipupdate for geolocation. diff --git a/frontend/drizzle/0018_living_taskmaster.sql b/frontend/drizzle/0018_living_taskmaster.sql new file mode 100644 index 0000000..831689c --- /dev/null +++ b/frontend/drizzle/0018_living_taskmaster.sql @@ -0,0 +1,2 @@ +ALTER TABLE "user" ADD COLUMN "stripe_customer_id" varchar(255);--> statement-breakpoint +ALTER TABLE "user" DROP COLUMN IF EXISTS "stripe_subscription"; \ No newline at end of file diff --git a/frontend/drizzle/0019_chief_mephisto.sql b/frontend/drizzle/0019_chief_mephisto.sql new file mode 100644 index 0000000..349370d --- /dev/null +++ b/frontend/drizzle/0019_chief_mephisto.sql @@ -0,0 +1 @@ +DROP TABLE "stripe_session"; \ No newline at end of file diff --git a/frontend/drizzle/meta/0018_snapshot.json b/frontend/drizzle/meta/0018_snapshot.json new file mode 100644 index 0000000..9aade0d --- /dev/null +++ b/frontend/drizzle/meta/0018_snapshot.json @@ -0,0 +1,461 @@ +{ + "id": "227300d6-3473-4b34-90c7-15aa5e594784", + "prevId": "5014f286-7c9c-411b-a03e-16a05b37caa0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.email_verification_token": { + "name": "email_verification_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "domain_status": { + "name": "domain_status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "enable_custom_domain": { + "name": "enable_custom_domain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_ip": { + "name": "custom_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain_id": { + "name": "custom_domain_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.setting": { + "name": "setting", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "ios": { + "name": "ios", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ios_link": { + "name": "ios_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "android": { + "name": "android", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "android_link": { + "name": "android_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shortener_code_unique": { + "name": "shortener_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.stripe_session": { + "name": "stripe_session", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expired": { + "name": "expired", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.visitor": { + "name": "visitor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "shortener_id": { + "name": "shortener_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "country_code": { + "name": "country_code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "device_vendor": { + "name": "device_vendor", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "os": { + "name": "os", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "browser": { + "name": "browser", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/0019_snapshot.json b/frontend/drizzle/meta/0019_snapshot.json new file mode 100644 index 0000000..37b47aa --- /dev/null +++ b/frontend/drizzle/meta/0019_snapshot.json @@ -0,0 +1,432 @@ +{ + "id": "b2f02cb7-53e4-4077-9cea-ad5e3b062f51", + "prevId": "227300d6-3473-4b34-90c7-15aa5e594784", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.email_verification_token": { + "name": "email_verification_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#000000'" + }, + "domain_status": { + "name": "domain_status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "enable_custom_domain": { + "name": "enable_custom_domain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_ip": { + "name": "custom_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain_id": { + "name": "custom_domain_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.setting": { + "name": "setting", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "qr_background": { + "name": "qr_background", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + }, + "qr_foreground": { + "name": "qr_foreground", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.shortener": { + "name": "shortener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "ios": { + "name": "ios", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "ios_link": { + "name": "ios_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "android": { + "name": "android", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "android_link": { + "name": "android_link", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "project_id": { + "name": "project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shortener_code_unique": { + "name": "shortener_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.visitor": { + "name": "visitor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "shortener_id": { + "name": "shortener_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "country_code": { + "name": "country_code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "city": { + "name": "city", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "device_vendor": { + "name": "device_vendor", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "os": { + "name": "os", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "browser": { + "name": "browser", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/frontend/drizzle/meta/_journal.json b/frontend/drizzle/meta/_journal.json index d3efb2b..8924676 100644 --- a/frontend/drizzle/meta/_journal.json +++ b/frontend/drizzle/meta/_journal.json @@ -127,6 +127,20 @@ "when": 1724731642381, "tag": "0017_mysterious_marten_broadcloak", "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1724920767007, + "tag": "0018_living_taskmaster", + "breakpoints": true + }, + { + "idx": 19, + "version": "7", + "when": 1724923406366, + "tag": "0019_chief_mephisto", + "breakpoints": true } ] } \ No newline at end of file diff --git a/frontend/src/lib/db/schema.ts b/frontend/src/lib/db/schema.ts index fe7ad82..2d5e7d4 100644 --- a/frontend/src/lib/db/schema.ts +++ b/frontend/src/lib/db/schema.ts @@ -24,7 +24,7 @@ export const user = pgTable('user', { .notNull() .$type<'free' | 'pro' | 'owner'>() .default('free'), - stripeSubscription: varchar('stripe_subscription', { length: 255 }), + stripeCustomerId: varchar('stripe_customer_id', { length: 255 }), }) export const shortener = pgTable('shortener', { @@ -117,12 +117,6 @@ export const emailVerificationToken = pgTable( }, ) -export const stripeSession = pgTable('stripe_session', { - session_id: varchar('session_id', { length: 255 }).notNull(), - userId: integer('user_id').notNull(), - expired: boolean('expired').notNull().default(false), -}) - // relations export const userRelations = relations(user, ({ one, many }) => ({ shortener: many(shortener), @@ -175,13 +169,3 @@ export const settingRelations = relations(setting, ({ one }) => ({ references: [user.id], }), })) - -export const stripeSessionRelations = relations( - stripeSession, - ({ one }) => ({ - user: one(user, { - fields: [stripeSession.userId], - references: [user.id], - }), - }), -) diff --git a/frontend/src/routes/(app)/dashboard/billing/+page.server.ts b/frontend/src/routes/(app)/dashboard/billing/+page.server.ts index e16faf3..f722d82 100644 --- a/frontend/src/routes/(app)/dashboard/billing/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/billing/+page.server.ts @@ -28,10 +28,16 @@ export const actions = { const user = event.locals.user - if (!user.stripeSubscription) return { form } + if (!user.stripeCustomerId) return { form } - const subscription = await stripe.subscriptions.update( - user.stripeSubscription, + const subscription = await stripe.subscriptions.list({ + customer: user.stripeCustomerId, + price: env.PRIVATE_PRO_PLAN_PRICE_ID, + limit: 1, + }) + + const cancelSubscription = await stripe.subscriptions.update( + subscription.data[0].id, { cancel_at_period_end: true }, ) diff --git a/frontend/src/routes/(app)/dashboard/billing/plan/pro/+page.server.ts b/frontend/src/routes/(app)/dashboard/billing/plan/pro/+page.server.ts index 5024b33..9131fbe 100644 --- a/frontend/src/routes/(app)/dashboard/billing/plan/pro/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/billing/plan/pro/+page.server.ts @@ -2,19 +2,39 @@ import { redirect } from '@sveltejs/kit' import type { PageServerLoad } from './$types' import Stripe from 'stripe' import { db } from '$lib/db' -import { stripeSession } from '$lib/db/schema' import { env } from '$env/dynamic/private' +import { user as userTable } from '$lib/db/schema' +import { eq } from 'drizzle-orm' export const load = (async (events) => { - if (events.locals.user.stripeSubscription) { + if (events.locals.user.plan !== 'free') { redirect(301, '/dashboard/billing') } if (!events.locals.user.email_verified) { redirect(301, '/dashboard/billing') } + + const user = events.locals.user + const stripe = new Stripe(env.PRIVATE_STRIPE_SECRET_KEY) + + let stripeCustomerId = events.locals.user.stripeCustomerId + + if (!stripeCustomerId) { + const customer = await stripe.customers.create({ + email: user.email, + }) + stripeCustomerId = customer.id + await db + .update(userTable) + .set({ + stripeCustomerId: customer.id, + }) + .where(eq(userTable.id, user.id)) + } + const session = await stripe.checkout.sessions.create({ - customer_email: events.locals.user.email, + customer: stripeCustomerId, line_items: [ { price: env.PRIVATE_PRO_PLAN_PRICE_ID, quantity: 1 }, ], @@ -30,11 +50,5 @@ export const load = (async (events) => { redirect(301, '/dashboard/billing') } - await db.insert(stripeSession).values({ - session_id: session.id, - userId: events.locals.user.id, - expired: false, - }) - redirect(301, session.url) }) satisfies PageServerLoad diff --git a/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.server.ts b/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.server.ts index 53abbaa..9e0cee2 100644 --- a/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.server.ts @@ -2,7 +2,7 @@ import { redirect } from '@sveltejs/kit' import type { PageServerLoad } from './$types' import Stripe from 'stripe' import { db } from '$lib/db' -import { user, stripeSession } from '$lib/db/schema' +import { user } from '$lib/db/schema' import { eq } from 'drizzle-orm' import { env } from '$env/dynamic/private' @@ -17,27 +17,14 @@ export const load = (async ({ url }) => { session.status === 'complete' && session.payment_status === 'paid' ) { - const stripe_session = await db.query.stripeSession.findFirst({ - where: (stripeSession, { eq }) => - eq(stripeSession.session_id, session_id), - with: { - user: true, - }, - }) - if (!stripe_session) redirect(301, '/dashboard/billing') + if (!session.customer) redirect(301, '/dashboard/billing') await db .update(user) .set({ plan: 'pro', - stripeSubscription: session.subscription?.toString(), }) - .where(eq(user.id, stripe_session.user.id)) - - await db - .update(stripeSession) - .set({ expired: true }) - .where(eq(stripeSession.session_id, session_id)) + .where(eq(user.stripeCustomerId, session.customer?.toString())) redirect(301, '/dashboard/billing') } diff --git a/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.svelte b/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.svelte new file mode 100644 index 0000000..2e65c9e --- /dev/null +++ b/frontend/src/routes/(app)/dashboard/billing/plan/pro/success/+page.svelte @@ -0,0 +1,23 @@ + + + + + + + Payment Success + + You have subscribed to the pro plan, thank you for the + support. + + +
+ +
+
+
diff --git a/frontend/src/routes/(auth)/signup/(components)/form.svelte b/frontend/src/routes/(auth)/signup/(components)/form.svelte index 0e0ae68..1c42e14 100644 --- a/frontend/src/routes/(auth)/signup/(components)/form.svelte +++ b/frontend/src/routes/(auth)/signup/(components)/form.svelte @@ -20,6 +20,7 @@ if (result.status === 200) { toast.success( 'Welcome to kon.sh, a verification email has been sent to your mailbox', + { duration: 10000 }, ) } }, diff --git a/frontend/src/routes/webhook/stripe/+server.ts b/frontend/src/routes/webhook/stripe/+server.ts index 2f78fdb..b985b4d 100644 --- a/frontend/src/routes/webhook/stripe/+server.ts +++ b/frontend/src/routes/webhook/stripe/+server.ts @@ -27,7 +27,10 @@ export const POST: RequestHandler = async (event) => { .update(user) .set({ plan: 'free' }) .where( - eq(user.stripeSubscription, stripeEvent.data.object.id), + eq( + user.stripeCustomerId, + stripeEvent.data.object.customer.toString(), + ), ) } return new Response('Success', { status: 200 })