add lifetime 1-4x

This commit is contained in:
2026-04-10 13:56:59 +04:00
parent 180f888819
commit 824cb15faa
8 changed files with 136 additions and 63 deletions
+3 -3
View File
@@ -239,10 +239,10 @@ export function computeApplyPlan(
): AccountUpdate {
const stack = (acc.plan_stack || []).slice();
const newPlan = payment.plan;
const newDays = payment.plan === "lifetime" ? null : (payment.months ?? 1) * 30;
const newDays = payment.plan.startsWith("lifetime") ? null : (payment.months ?? 1) * 30;
const currentExpiry = acc.plan_expires_at ? new Date(acc.plan_expires_at) : null;
const currentIsActive = acc.plan === "lifetime"
const currentIsActive = acc.plan.startsWith("lifetime")
|| (acc.plan !== "free" && currentExpiry && currentExpiry > now);
if (!currentIsActive || acc.plan === "free") {
@@ -256,7 +256,7 @@ export function computeApplyPlan(
}
if (planTier(newPlan) > planTier(acc.plan)) {
const remainingDays = acc.plan === "lifetime"
const remainingDays = acc.plan.startsWith("lifetime")
? null
: Math.ceil((currentExpiry!.getTime() - now.getTime()) / 86400000);
const newStack = insertIntoStack(stack, { plan: acc.plan, remaining_days: remainingDays });
+3 -3
View File
@@ -18,10 +18,10 @@ export async function generateReceipt(paymentId: number): Promise<string> {
? 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<string, string> = { pro: "Pro", pro2x: "Pro 2x", pro4x: "Pro 4x", lifetime: "Lifetime" };
const planNames: Record<string, string> = { pro: "Pro", pro2x: "Pro 2x", pro4x: "Pro 4x", lifetime: "Lifetime", lifetime2x: "Lifetime 2x", lifetime4x: "Lifetime 4x" };
const planName = planNames[payment.plan] || payment.plan;
const planLabel = payment.plan === "lifetime"
? "Lifetime"
const planLabel = payment.plan.startsWith("lifetime")
? planName
: `${planName} × ${payment.months} month${payment.months > 1 ? "s" : ""}`;
const txRows = txs.map((tx: any) => {
+9 -7
View File
@@ -2,7 +2,7 @@ import { Elysia, t } from "elysia";
import sql from "./db";
import { derive } from "./address";
import { getExchangeRates, getAvailableCoins, fetchQrBase64 } from "./freedom";
import { PLAN_PRICING as PLANS, COINS } from "../../shared/plans";
import { PLAN_PRICING as PLANS, COINS, planTier } from "../../shared/plans";
import { generateReceipt } from "./receipt";
import { watchPayment } from "./monitor";
import { resolveKey as sharedResolveKey, extractAuthKey } from "../../shared/auth";
@@ -45,11 +45,13 @@ export const routes = new Elysia()
const { plan, months, coin } = body;
if (plan === "lifetime") {
if (plan.startsWith("lifetime")) {
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" }; }
const buyingTier = planTier(plan);
const ownedLifetimes = [acc.plan, ...stack.map((s: any) => s.plan)].filter((p: string) => p.startsWith("lifetime"));
const maxOwnedTier = Math.max(0, ...ownedLifetimes.map((p: string) => planTier(p)));
if (maxOwnedTier >= buyingTier) { set.status = 400; return { error: "You already have this lifetime tier or higher" }; }
}
if (!COINS[coin]) { set.status = 400; return { error: `Unknown coin: ${coin}` }; }
@@ -60,9 +62,9 @@ export const routes = new Elysia()
if (!planDef) { set.status = 400; return { error: `Unknown plan: ${plan}` }; }
let amountUsd = planDef.priceUsd ?? (planDef.monthlyUsd! * (months ?? 1));
if (plan === "lifetime" && planDef.priceUsd) {
if (plan.startsWith("lifetime") && planDef.priceUsd) {
const [{ total }] = await sql`SELECT COALESCE(SUM(amount_usd), 0)::numeric as total FROM payments WHERE account_id = ${accountId} AND status = 'paid'`;
const credit = Math.min(Number(total), planDef.priceUsd * 0.75);
const credit = Math.min(Number(total), planDef.priceUsd * 0.90);
amountUsd = Math.max(amountUsd - credit, 1);
}
const rates = await getExchangeRates();
@@ -87,7 +89,7 @@ export const routes = new Elysia()
const [payment] = await sql`
INSERT INTO payments (account_id, plan, months, amount_usd, coin, amount_crypto, address, derivation_index, expires_at)
VALUES (${accountId}, ${plan}, ${plan === "lifetime" ? null : months ?? 1}, ${amountUsd}, ${coin}, ${amountCrypto}, ${address}, ${next_index}, ${expiresAt.toISOString()})
VALUES (${accountId}, ${plan}, ${plan.startsWith("lifetime") ? null : months ?? 1}, ${amountUsd}, ${coin}, ${amountCrypto}, ${address}, ${next_index}, ${expiresAt.toISOString()})
RETURNING *
`;