update: lifetime discounts based on spent

This commit is contained in:
nate 2026-03-22 06:11:49 +04:00
parent b014cbf98a
commit a1d37c6215
3 changed files with 26 additions and 3 deletions

View File

@ -74,7 +74,14 @@ export const routes = new Elysia()
// Calculate amount // Calculate amount
const planDef = PLANS[plan]; const planDef = PLANS[plan];
if (!planDef) { set.status = 400; return { error: `Unknown plan: ${plan}` }; } if (!planDef) { set.status = 400; return { error: `Unknown plan: ${plan}` }; }
const amountUsd = planDef.priceUsd ?? (planDef.monthlyUsd! * (months ?? 1)); let amountUsd = planDef.priceUsd ?? (planDef.monthlyUsd! * (months ?? 1));
// Lifetime discount: credit up to 50% of lifetime price from previous payments
if (plan === "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.5);
amountUsd = Math.max(amountUsd - credit, 1);
}
const rates = await getExchangeRates(); const rates = await getExchangeRates();
const rate = rates[coin]; const rate = rates[coin];
if (!rate) { set.status = 500; return { error: "Could not fetch exchange rate" }; } if (!rate) { set.status = 500; return { error: "Could not fetch exchange rate" }; }

View File

@ -273,6 +273,9 @@ export const dashboard = new Elysia()
const [acc] = await sql`SELECT plan, plan_expires_at FROM accounts WHERE id = ${resolved.accountId}`; const [acc] = await sql`SELECT plan, plan_expires_at FROM accounts WHERE id = ${resolved.accountId}`;
if (acc.plan === "lifetime") return redirect("/dashboard/settings"); if (acc.plan === "lifetime") 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'`;
// Fetch coins server-side for no-JS rendering // Fetch coins server-side for no-JS rendering
const payApi = process.env.PAY_API || "https://pay.pingql.com"; const payApi = process.env.PAY_API || "https://pay.pingql.com";
let coins: any[] = []; let coins: any[] = [];
@ -282,7 +285,7 @@ export const dashboard = new Elysia()
coins = data.coins || []; coins = data.coins || [];
} catch {} } catch {}
return html("checkout", { nav: "settings", account: acc, payApi, invoiceId: null, coins, invoice: null }); return html("checkout", { nav: "settings", account: acc, payApi, invoiceId: null, coins, invoice: null, totalSpent: Number(total_spent) });
}) })
// Existing invoice by ID — SSR the payment status // Existing invoice by ID — SSR the payment status

View File

@ -6,6 +6,10 @@
const coins = it.coins || []; const coins = it.coins || [];
const invoice = it.invoice; const invoice = it.invoice;
const payApi = it.payApi || ''; const payApi = it.payApi || '';
const totalSpent = it.totalSpent || 0;
const lifetimeBase = 140;
const lifetimeDiscount = Math.min(totalSpent, lifetimeBase * 0.5);
const lifetimePrice = lifetimeBase - lifetimeDiscount;
%> %>
<style> <style>
@ -44,9 +48,18 @@
</label> </label>
<label for="plan-lifetime" <label for="plan-lifetime"
class="cursor-pointer text-left bg-surface border-2 border-border-subtle hover:border-yellow-500/40 rounded-xl p-5 transition-colors relative"> class="cursor-pointer text-left bg-surface border-2 border-border-subtle hover:border-yellow-500/40 rounded-xl p-5 transition-colors relative">
<% if (lifetimeDiscount > 0) { %>
<span class="absolute top-3 right-3 text-[10px] font-semibold px-2 py-0.5 rounded-full bg-green-500/15 text-green-400 border border-green-500/20">Loyalty Discount</span>
<% } else { %>
<span class="absolute top-3 right-3 text-[10px] font-semibold px-2 py-0.5 rounded-full bg-yellow-500/15 text-yellow-500 border border-yellow-500/20">Launch Deal</span> <span class="absolute top-3 right-3 text-[10px] font-semibold px-2 py-0.5 rounded-full bg-yellow-500/15 text-yellow-500 border border-yellow-500/20">Launch Deal</span>
<% } %>
<div class="text-xs text-yellow-500/70 uppercase tracking-wider font-mono mb-1">Lifetime</div> <div class="text-xs text-yellow-500/70 uppercase tracking-wider font-mono mb-1">Lifetime</div>
<div class="text-2xl font-bold text-gray-100">$140</div> <% if (lifetimeDiscount > 0) { %>
<div class="text-2xl font-bold text-gray-100">$<%= lifetimePrice.toFixed(0) %> <span class="text-sm font-normal text-gray-600 line-through">$<%= lifetimeBase %></span></div>
<div class="text-xs text-green-400/70 mt-1">You've paid us $<%= totalSpent.toFixed(0) %> — we've credited $<%= lifetimeDiscount.toFixed(0) %> toward lifetime</div>
<% } else { %>
<div class="text-2xl font-bold text-gray-100">$<%= lifetimeBase %></div>
<% } %>
<div class="text-xs text-gray-500 mt-2">One-time, 200 monitors forever</div> <div class="text-xs text-gray-500 mt-2">One-time, 200 monitors forever</div>
</label> </label>
</div> </div>