diff --git a/apps/pay/src/routes.ts b/apps/pay/src/routes.ts index 60be715..5c8db62 100644 --- a/apps/pay/src/routes.ts +++ b/apps/pay/src/routes.ts @@ -60,10 +60,12 @@ export const routes = new Elysia() const { plan, months, coin } = body; - // Validate plan + // Validate plan — block duplicate lifetime if (plan === "lifetime") { - const [acc] = await sql`SELECT plan FROM accounts WHERE id = ${accountId}`; - if (acc.plan === "lifetime") { set.status = 400; return { error: "Already on lifetime plan" }; } + const [acc] = await sql`SELECT plan, plan_stack FROM accounts WHERE id = ${accountId}`; + const stack = typeof acc.plan_stack === "string" ? JSON.parse(acc.plan_stack) : (acc.plan_stack || []); + const hasLifetime = acc.plan === "lifetime" || stack.some((s: any) => s.plan === "lifetime"); + if (hasLifetime) { set.status = 400; return { error: "You already have a lifetime plan" }; } } // Validate coin diff --git a/apps/web/src/routes/dashboard.ts b/apps/web/src/routes/dashboard.ts index b3ccb1a..8ffc0ce 100644 --- a/apps/web/src/routes/dashboard.ts +++ b/apps/web/src/routes/dashboard.ts @@ -270,8 +270,10 @@ export const dashboard = new Elysia() .get("/dashboard/checkout", async ({ cookie, headers }) => { const resolved = await getAccountId(cookie, headers); if (!resolved?.accountId) return redirect("/dashboard"); - const [acc] = await sql`SELECT plan, plan_expires_at FROM accounts WHERE id = ${resolved.accountId}`; - if (acc.plan === "lifetime") return redirect("/dashboard/settings"); + const [acc] = await sql`SELECT plan, plan_expires_at, plan_stack FROM accounts WHERE id = ${resolved.accountId}`; + const stack = typeof acc.plan_stack === "string" ? JSON.parse(acc.plan_stack) : (acc.plan_stack || []); + const hasLifetime = acc.plan === "lifetime" || stack.some((s: any) => s.plan === "lifetime"); + if (acc.plan === "lifetime" && stack.length === 0) return redirect("/dashboard/settings"); // Total spent on paid invoices (for lifetime discount) const [{ total_spent }] = await sql`SELECT COALESCE(SUM(amount_usd), 0)::numeric as total_spent FROM payments WHERE account_id = ${resolved.accountId} AND status = 'paid'`; @@ -285,7 +287,7 @@ export const dashboard = new Elysia() coins = data.coins || []; } catch {} - return html("checkout", { nav: "settings", account: acc, payApi, invoiceId: null, coins, invoice: null, totalSpent: Number(total_spent) }); + return html("checkout", { nav: "settings", account: acc, payApi, invoiceId: null, coins, invoice: null, totalSpent: Number(total_spent), hasLifetime }); }) // Existing invoice by ID — SSR the payment status diff --git a/apps/web/src/views/checkout.ejs b/apps/web/src/views/checkout.ejs index eacf1df..73c3887 100644 --- a/apps/web/src/views/checkout.ejs +++ b/apps/web/src/views/checkout.ejs @@ -7,8 +7,9 @@ const invoice = it.invoice; const payApi = it.payApi || ''; const totalSpent = it.totalSpent || 0; + const hasLifetime = it.hasLifetime || false; const lifetimeBase = 140; - const lifetimeDiscount = Math.min(totalSpent, lifetimeBase * 0.75); + const lifetimeDiscount = hasLifetime ? 0 : Math.min(totalSpent, lifetimeBase * 0.75); const lifetimePrice = lifetimeBase - lifetimeDiscount; %> @@ -46,6 +47,14 @@