import Stripe from "stripe"; /** * POST /api/stripe/checkout * * Erstellt eine Stripe-Checkout-Session für Pro oder Legend Subscription. * Frontend (Web) öffnet die zurückgegebene URL — User landet im Stripe-Hosted * Checkout, gibt Zahlungsmethode ein, wird zu success_url umgeleitet. * * iOS muss via RevenueCat/Apple-IAP — Stripe ist Web-only-Path (Apple-Guideline * 3.1.1 verbietet externe Bezahlwege für digitale Subs in iOS-Apps). * * Body: { plan: 'pro' | 'legend', billing: 'monthly' | 'yearly' } * * ENV-Vars (in Infisical): * STRIPE_PRICE_PRO_MONTHLY — Test/Live Stripe price_xxx ID * STRIPE_PRICE_PRO_YEARLY * STRIPE_PRICE_LEGEND_MONTHLY * STRIPE_PRICE_LEGEND_YEARLY * * Tier-Mapping (post Free-Drop Pivot, 2026-05): * Pro → 3,99 €/mo · 39,90 €/yr (2 Monate gratis) * Legend → 7,99 €/mo · 79,90 €/yr (Multi-Device-Hardening) * * 14-Tage-Trial wird im Stripe-Dashboard pro Price konfiguriert (Trial-Period- * Days) — der Endpoint passt sich automatisch an wenn der Price die Trial hat. */ export default defineEventHandler(async (event) => { const config = useRuntimeConfig(); if (!config.stripeSecretKey) { throw createError({ statusCode: 500, message: "Stripe nicht konfiguriert – STRIPE_SECRET_KEY fehlt", }); } const stripe = new Stripe(config.stripeSecretKey); const user = await requireUser(event); const body = await readBody(event); const plan = body?.plan as string; const billing = (body?.billing as string) || "monthly"; if (!plan || !["pro", "legend"].includes(plan)) { throw createError({ statusCode: 400, message: "Ungültiger Plan (erwartet: 'pro' oder 'legend')", }); } if (!["monthly", "yearly"].includes(billing)) { throw createError({ statusCode: 400, message: "Ungültiger Billing-Zyklus (erwartet: 'monthly' oder 'yearly')", }); } // ENV-Var-Naming-Pattern: STRIPE_PRICE__ in Großbuchstaben const envKey = `STRIPE_PRICE_${plan.toUpperCase()}_${billing.toUpperCase()}`; const priceId = process.env[envKey]; if (!priceId || !priceId.startsWith("price_")) { throw createError({ statusCode: 503, message: `Dieser Plan ist noch nicht verfügbar. (${envKey} nicht in Infisical gesetzt)`, }); } const appUrl = config.public.appUrl || "https://rebreak.org"; const session = await stripe.checkout.sessions.create({ mode: "subscription", line_items: [{ price: priceId, quantity: 1 }], success_url: `${appUrl}/app/settings?upgraded=true`, cancel_url: `${appUrl}/pricing`, client_reference_id: user.id, metadata: { user_id: user.id, plan, billing, }, }); return { url: session.url }; });