diff --git a/apps/api/src/utils/plans.ts b/apps/api/src/utils/plans.ts index 065e57a..8f71835 100644 --- a/apps/api/src/utils/plans.ts +++ b/apps/api/src/utils/plans.ts @@ -1,4 +1,4 @@ -export type Plan = "free" | "pro" | "pro4x" | "lifetime"; +export type Plan = "free" | "pro" | "pro2x" | "pro4x" | "lifetime"; export interface PlanLimits { maxMonitors: number; @@ -6,29 +6,31 @@ export interface PlanLimits { maxRegions: number; } +// Base pro limits — multiplied for 2x/4x tiers +const PRO_BASE = { maxMonitors: 200, minIntervalS: 2, maxRegions: 99 }; + const PLANS: Record = { - free: { - maxMonitors: 10, - minIntervalS: 30, - maxRegions: 1, - }, - pro: { - maxMonitors: 200, - minIntervalS: 2, - maxRegions: 99, - }, - pro4x: { - maxMonitors: 800, - minIntervalS: 2, - maxRegions: 99, - }, - lifetime: { - maxMonitors: 200, - minIntervalS: 2, - maxRegions: 99, - }, + free: { maxMonitors: 10, minIntervalS: 30, maxRegions: 1 }, + pro: { ...PRO_BASE }, + pro2x: { ...PRO_BASE, maxMonitors: PRO_BASE.maxMonitors * 2 }, + pro4x: { ...PRO_BASE, maxMonitors: PRO_BASE.maxMonitors * 4 }, + lifetime: { ...PRO_BASE }, }; export function getPlanLimits(plan: string): PlanLimits { return PLANS[plan as Plan] || PLANS.free; } + +// Display helpers +export const PLAN_LABELS: Record = { + free: "Free", pro: "Pro", pro2x: "Pro 2x", pro4x: "Pro 4x", lifetime: "Lifetime", +}; + +export const PRO_MULTIPLIERS = [ + { plan: "pro", label: "1x", monitors: PRO_BASE.maxMonitors, priceMultiplier: 1 }, + { plan: "pro2x", label: "2x", monitors: PRO_BASE.maxMonitors * 2, priceMultiplier: 2 }, + { plan: "pro4x", label: "4x", monitors: PRO_BASE.maxMonitors * 4, priceMultiplier: 4 }, +]; + +export const PRO_MONTHLY_USD = 12; +export const LIFETIME_USD = 140; diff --git a/apps/pay/src/monitor.ts b/apps/pay/src/monitor.ts index 9c32f86..1cd9bb2 100644 --- a/apps/pay/src/monitor.ts +++ b/apps/pay/src/monitor.ts @@ -231,7 +231,7 @@ async function applyPlan(payment: any) { export async function expireProPlans() { const result = await sql` UPDATE accounts SET plan = 'free', plan_expires_at = NULL - WHERE plan = 'pro' AND plan_expires_at IS NOT NULL AND plan_expires_at < now() + WHERE plan IN ('pro', 'pro2x', 'pro4x') AND plan_expires_at IS NOT NULL AND plan_expires_at < now() `; if (result.count > 0) console.log(`Downgraded ${result.count} expired pro accounts`); } diff --git a/apps/pay/src/plans.ts b/apps/pay/src/plans.ts index f4a0f9c..b914a01 100644 --- a/apps/pay/src/plans.ts +++ b/apps/pay/src/plans.ts @@ -1,17 +1,13 @@ -export const PLANS = { - pro: { - label: "Pro", - monthlyUsd: 12, - }, - pro4x: { - label: "Pro 4x", - monthlyUsd: 48, - }, - lifetime: { - label: "Lifetime", - priceUsd: 140, - }, -} as const; +// Pro pricing — base $12/mo, multiplied for 2x/4x +export const PRO_MONTHLY_USD = 12; +export const LIFETIME_USD = 140; + +export const PLANS: Record = { + pro: { label: "Pro", monthlyUsd: PRO_MONTHLY_USD }, + pro2x: { label: "Pro 2x", monthlyUsd: PRO_MONTHLY_USD * 2 }, + pro4x: { label: "Pro 4x", monthlyUsd: PRO_MONTHLY_USD * 4 }, + lifetime: { label: "Lifetime", priceUsd: LIFETIME_USD }, +}; export const COINS: Record = { btc: { label: "Bitcoin", ticker: "BTC", confirmations: 1, uri: "bitcoin" }, diff --git a/apps/pay/src/receipt.ts b/apps/pay/src/receipt.ts index a3bdd20..a06aadb 100644 --- a/apps/pay/src/receipt.ts +++ b/apps/pay/src/receipt.ts @@ -19,9 +19,11 @@ export async function generateReceipt(paymentId: number): Promise { ? new Date(payment.paid_at).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }) : "—"; const createdDate = new Date(payment.created_at).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }); + const planNames: Record = { pro: "Pro", pro2x: "Pro 2x", pro4x: "Pro 4x", lifetime: "Lifetime" }; + const planName = planNames[payment.plan] || payment.plan; const planLabel = payment.plan === "lifetime" ? "Lifetime" - : `Pro × ${payment.months} month${payment.months > 1 ? "s" : ""}`; + : `${planName} × ${payment.months} month${payment.months > 1 ? "s" : ""}`; const txRows = txs.map((tx: any) => { const date = new Date(tx.detected_at).toLocaleDateString("en-US", { diff --git a/apps/pay/src/routes.ts b/apps/pay/src/routes.ts index 67b48ba..3acc289 100644 --- a/apps/pay/src/routes.ts +++ b/apps/pay/src/routes.ts @@ -72,8 +72,9 @@ export const routes = new Elysia() if (!available.includes(coin)) { set.status = 400; return { error: `${coin} is temporarily unavailable` }; } // Calculate amount - const planPricing = plan === "lifetime" ? null : (plan === "pro4x" ? PLANS.pro4x : PLANS.pro); - const amountUsd = plan === "lifetime" ? PLANS.lifetime.priceUsd : planPricing!.monthlyUsd * (months ?? 1); + const planDef = PLANS[plan]; + if (!planDef) { set.status = 400; return { error: `Unknown plan: ${plan}` }; } + const amountUsd = planDef.priceUsd ?? (planDef.monthlyUsd! * (months ?? 1)); const rates = await getExchangeRates(); const rate = rates[coin]; if (!rate) { set.status = 500; return { error: "Could not fetch exchange rate" }; } diff --git a/apps/web/src/views/checkout.ejs b/apps/web/src/views/checkout.ejs index ca1f021..ae5417b 100644 --- a/apps/web/src/views/checkout.ejs +++ b/apps/web/src/views/checkout.ejs @@ -9,13 +9,15 @@ %> @@ -30,43 +32,64 @@
- - - - + + + -
+
-
- -