From e2b4ce44475c7e51b2f11c49c968cfeef6c99004 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 19 Aug 2026 13:56:44 -0700 Subject: [PATCH] pricing: wire checkout to license server (one-time + subscription) Replace hardcoded Stripe Payment Links with client-side fetch to https://licenses.dashcaddy.net/api/checkout/{one-time,subscription}. Add email capture input and two-button checkout (Buy once vs. Subscribe & auto-renew). Plan codes: premium_30d/90d/180d/365d. Update hero copy and FAQ to reflect both payment modes. --- src/app/pricing/page.tsx | 153 +++++++++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 38 deletions(-) diff --git a/src/app/pricing/page.tsx b/src/app/pricing/page.tsx index c023b18..55f36b6 100644 --- a/src/app/pricing/page.tsx +++ b/src/app/pricing/page.tsx @@ -5,38 +5,78 @@ import Link from 'next/link'; import Navbar from '@/components/Navbar'; import Footer from '@/components/Footer'; -// ─── Stripe Payment Links ─────────────────────────────────────────── -// These are pre-generated checkout URLs from the Stripe Dashboard. -// Sami needs to provide his Stripe secret key so we can create these -// programmatically, or create them manually in the Stripe Dashboard. -// The product catalog (from src/billing/catalog.js): -// pro-30d → $20, 30-day license (one-time payment) -// pro-90d → $50, 90-day license (one-time payment) -// pro-180d → $70, 180-day license (one-time payment) -// pro-365d → $99, 365-day license (one-time payment) +// ─── License server checkout ──────────────────────────────────────── +// The marketing site is a Next.js static export (no server runtime), so +// checkout is driven client-side by POSTing { planCode, customerEmail } +// to the license server at https://licenses.dashcaddy.net. The server +// creates a Stripe Checkout session (one-time OR subscription) and +// returns { url, sessionId }; we redirect the browser to that URL. +// +// Plan codes (see /root/dashcaddy-license-server/src/plans.js): +// premium_30d → $20, 30-day license, one-time or 1-month sub +// premium_90d → $50, 90-day license, one-time or 3-month sub +// premium_180d → $70, 180-day license, one-time or 6-month sub +// premium_365d → $99, 365-day license, one-time or 12-month sub // ───────────────────────────────────────────────────────────────────── -const STRIPE_LINKS: Record = { - '30d': 'https://buy.stripe.com/7sY9AVgJm35P6Uo5j904800', - '90d': 'https://buy.stripe.com/bJe6oJ0Ko7m5emQ4f504801', - '180d': 'https://buy.stripe.com/4gMeVfct635P2E826X04802', - '365d': 'https://buy.stripe.com/8x228tct65dXdiM9zp04803', -}; +const LICENSE_SERVER = 'https://licenses.dashcaddy.net'; + +type CheckoutMode = 'one-time' | 'subscription'; +type CheckoutError = { error: string; detail?: string }; + +async function startCheckout(mode: CheckoutMode, planCode: string, customerEmail: string): Promise { + const endpoint = mode === 'subscription' ? '/api/checkout/subscription' : '/api/checkout/one-time'; + const res = await fetch(`${LICENSE_SERVER}${endpoint}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ planCode, customerEmail }), + }); + const data = await res.json().catch(() => ({} as CheckoutError)); + if (!res.ok || !data || typeof (data as { url?: string }).url !== 'string') { + const message = (data as CheckoutError).error || `Checkout failed (${res.status})`; + throw new Error(message); + } + return (data as { url: string }).url; +} + +function isValidEmail(email: string): boolean { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); +} export default function PricingPage() { const [selectedPlan, setSelectedPlan] = useState<'30d' | '90d' | '180d' | '365d'>('365d'); + const [email, setEmail] = useState(''); + const [busy, setBusy] = useState(null); + const [error, setError] = useState(null); const planOptions = [ - { key: '30d', label: '30 Days', price: 20, productId: 'pro-30d', perDay: '$0.67/day' }, - { key: '90d', label: '90 Days', price: 50, productId: 'pro-90d', perDay: '$0.56/day' }, - { key: '180d', label: '180 Days', price: 70, productId: 'pro-180d', perDay: '$0.39/day' }, - { key: '365d', label: '365 Days', price: 99, productId: 'pro-365d', perDay: '$0.27/day' }, + { key: '30d', planCode: 'premium_30d', label: '30 Days', price: 20, perDay: '$0.67/day' }, + { key: '90d', planCode: 'premium_90d', label: '90 Days', price: 50, perDay: '$0.56/day' }, + { key: '180d', planCode: 'premium_180d', label: '180 Days', price: 70, perDay: '$0.39/day' }, + { key: '365d', planCode: 'premium_365d', label: '365 Days', price: 99, perDay: '$0.27/day' }, ] as const; const selected = planOptions.find(p => p.key === selectedPlan)!; + const emailOk = isValidEmail(email); + + async function handleCheckout(mode: CheckoutMode) { + setError(null); + if (!emailOk) { + setError('Please enter a valid email address — that is where your license key will be delivered.'); + return; + } + setBusy(mode); + try { + const url = await startCheckout(mode, selected.planCode, email.trim()); + window.location.href = url; + } catch (e) { + setError(e instanceof Error ? e.message : 'Checkout failed'); + setBusy(null); + } + } const coreFeatures = [ 'Dashboard & real-time service monitoring', - '93 pre-configured app templates', + '77 pre-configured app templates', 'Automatic SSL via Caddy internal CA', 'DNS automation via Technitium DNS', 'Reverse proxy management + Caddyfile-as-Code', @@ -64,21 +104,25 @@ export default function PricingPage() { 'Priority support', 'One active machine per license', '7-day grace period on expiry', - 'One-time payment — no recurring billing', + 'Fixed-duration license — runs for the duration you buy, then expires', ]; const faqs = [ { question: 'How does the license work?', - answer: 'You pay once and receive a license code valid for the selected duration (30, 90, 180, or 365 days). Paste it into your DashCaddy dashboard under Admin → License to unlock all premium features. No recurring billing — when it expires, you simply purchase again if you want to continue.', + answer: 'Choose a duration (30, 90, 180, or 365 days) and a payment mode — one-time purchase (license runs for that duration, then expires) or subscription (auto-renews at the chosen interval, time is added to your existing license each renewal, cancel anytime). Enter your email at checkout, pay via Stripe, and your license key is delivered to that email. Paste the key into your DashCaddy dashboard under Admin → License to unlock all premium features for that duration.', }, { question: 'What happens when my license expires?', - answer: 'Premium features gracefully deactivate after a 7-day grace period. Your services keep running — only the premium-gated features (SSO, Recipes, Swarm, Fleet Management) become unavailable. Purchase a new license at any time to re-enable them.', + answer: 'One-time licenses: premium features gracefully deactivate after a 7-day grace period. Your services keep running — only the premium-gated features (SSO, Recipes, Swarm, Fleet Management) become unavailable. Purchase a new license at any time to re-enable them. Subscription licenses: renew automatically at the chosen interval — time is added to your existing license, no re-pasting required. Cancel from your Stripe customer portal at any time; the license stays active until the end of the current billing period.', + }, + { + question: 'One-time or subscription — which should I pick?', + answer: 'Pick one-time if you want to pay once and not be billed again. Pick subscription if you want uninterrupted premium access with auto-renewal — at each renewal the next interval of time is added to your existing license, so you paste your key once and never re-paste on renewal. Both modes use the same $20/$50/$70/$99 price points.', }, { question: 'Can I use DashCaddy without Premium?', - answer: 'Absolutely. The core platform is fully functional without a license — including AI commands, the Security Center, service discovery, monitoring, backup, and all 93 app templates. Premium unlocks SSO, Recipes, Swarm orchestration, and Fleet Management for advanced multi-node setups.', + answer: 'Absolutely. The core platform is fully functional without a license — including AI commands, the Security Center, service discovery, monitoring, backup, and all 77 app templates. Premium unlocks SSO, Recipes, Swarm orchestration, and Fleet Management for advanced multi-node setups.', }, { question: 'How many machines can I activate?', @@ -110,7 +154,7 @@ export default function PricingPage() { Simple, Transparent Pricing

- The core platform is completely free — including AI commands, Security Center, and all 93 app templates. Premium unlocks advanced orchestration with a one-time payment. + The core platform is completely free — including AI commands, Security Center, and all 77 app templates. Premium unlocks SSO, Recipes, Swarm, and Fleet Management. Buy once for a fixed term, or subscribe and never worry about renewal — your license is extended automatically.

@@ -166,7 +210,7 @@ export default function PricingPage() {

Premium

-

Advanced orchestration, SSO, and fleet management. One-time payment — no subscription.

+

Advanced orchestration, SSO, and fleet management. Fixed-duration license — runs for the term you choose, then expires.

{/* Plan selector */}
@@ -189,22 +233,55 @@ export default function PricingPage() {
${selected.price} - one-time + {selected.label.toLowerCase()} license
-

{selected.perDay} · {selected.productId}

+

{selected.perDay}

- {/* Subscribe button — Stripe Payment Link */} - - Buy Premium License - + {/* Email capture — license key is emailed here */} +
+ + { setEmail(e.target.value); if (error) setError(null); }} + disabled={busy !== null} + className="w-full rounded-lg border border-surface-700 bg-surface-900/60 px-4 py-3 text-sm text-surface-50 placeholder:text-surface-500 focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-500/30 disabled:opacity-60" + /> +
+ + {/* Two-button checkout: one-time OR subscription */} +
+ + +
+ {error && ( +

+ {error} +

+ )}

- 🔒 Secure checkout via Stripe · One-time payment + 🔒 Secure checkout via Stripe · Cancel subscription anytime