// payments.jsx — central payment configuration + checkout adapter. // // ┌───────────────────────────────────────────────────────────────────────┐ // │ POUR PASSER AUX VRAIS PAIEMENTS STRIPE : tout se règle ICI, en un seul │ // │ endroit. Tant que MODE vaut "demo", le site fonctionne comme avant │ // │ (aucun paiement réel, carte fictive). │ // └───────────────────────────────────────────────────────────────────────┘ // // ── ÉTAPES quand ton compte Stripe est prêt ────────────────────────────── // // OPTION 1 — la plus simple (aucun serveur) : Stripe Payment Link // 1. Dans le dashboard Stripe → "Liens de paiement" → créer un lien. // 2. Colle l'URL dans PAYMENT_LINK_URL ci-dessous. // 3. Mets MODE = "stripe_link". // ⚠️ Un Payment Link a un montant fixe : pratique pour un seul produit, // moins pour un panier à montant variable. // // OPTION 2 — recommandée pour le panier : Stripe Checkout (mini serveur) // 1. Crée une petite fonction serveur (Vercel / Netlify / autre) qui // reçoit le panier et crée une "Checkout Session" Stripe, puis renvoie // { "url": "https://checkout.stripe.com/..." }. // 2. Colle l'URL de cette fonction dans CHECKOUT_ENDPOINT. // 3. Mets MODE = "stripe_checkout". // (Je peux te fournir le code de cette fonction le moment venu.) // // La clé SECRÈTE Stripe (sk_...) ne doit JAMAIS être mise ici ni dans le // navigateur — elle vit uniquement côté serveur (Option 2). const PAYMENTS = { MODE: "stripe_checkout", // "demo" | "stripe_link" | "stripe_checkout" // Option 1 : PAYMENT_LINK_URL: "", // ex. "https://buy.stripe.com/xxxxxxxx" // Option 2 : CHECKOUT_ENDPOINT: "https://serveur-stripe.vercel.app/api/checkout", // Clé PUBLIQUE Stripe (commence par pk_) — facultative pour les 2 options // ci-dessus, utile seulement si tu intègres Stripe.js plus tard. STRIPE_PUBLISHABLE_KEY: "", CURRENCY: "eur", }; // Returns the EFFECTIVE mode: falls back to "demo" if a real mode is selected // but not configured yet — so the site never breaks half-way. function paymentsMode() { if (PAYMENTS.MODE === "stripe_link" && PAYMENTS.PAYMENT_LINK_URL) return "stripe_link"; if (PAYMENTS.MODE === "stripe_checkout" && PAYMENTS.CHECKOUT_ENDPOINT) return "stripe_checkout"; return "demo"; } function paymentsIsDemo() { return paymentsMode() === "demo"; } // Build Stripe-friendly line items from the cart (used by the Checkout endpoint). function paymentsLineItems(items, lang) { return (items || []).map((it) => { const p = window.PRODUCTS.find((x) => x.id === it.productId) || window.PRODUCTS[0]; return { name: lang === "en" ? p.name_en : p.name_fr, productId: it.productId, unit_amount: Math.round(it.price * 100), // cents quantity: it.qty, }; }); } // Human-readable manufacturing details for each cart item — sent to the // checkout endpoint so the payment carries everything needed to produce // the keychains (attached as Stripe metadata server-side). function paymentsOrderDetails(items, lang) { return (items || []).map((it) => { const p = window.PRODUCTS.find((x) => x.id === it.productId) || window.PRODUCTS[0]; return { produit: lang === "en" ? p.name_en : p.name_fr, quantite: it.qty || 1, porte_cles: (it.units || []).map((u, i) => { const plateF = window.findFilament(u.plate); const codeId = window.codeForUnit ? window.codeForUnit(u) : u.code; const codeF = window.findFilament(codeId); return { numero: i + 1, type_contenu: u.contentType || "contact", contenu_qr: window.buildQRPayload(u), couleur_plaque: plateF ? plateF.fr : u.plate, couleur_code: codeF ? codeF.fr : codeId, }; }), }; }); } // Single entry point used by the payment screen. // Demo → resolves { redirected:false } so the screen runs its simulated flow. // Stripe → redirects the browser to the hosted payment page. async function startCheckout({ items, customer, total, lang }) { const mode = paymentsMode(); if (mode === "stripe_link") { window.location.href = PAYMENTS.PAYMENT_LINK_URL; return { redirected: true }; } if (mode === "stripe_checkout") { const res = await fetch(PAYMENTS.CHECKOUT_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: paymentsLineItems(items, lang), details: paymentsOrderDetails(items, lang), amount_total: Math.round((total || 0) * 100), currency: PAYMENTS.CURRENCY, customer: customer || null, locale: lang || "fr", }), }); if (!res.ok) throw new Error("checkout_failed_" + res.status); const data = await res.json(); if (data && data.url) { window.location.href = data.url; return { redirected: true }; } throw new Error("checkout_no_url"); } return { redirected: false }; // demo } Object.assign(window, { PAYMENTS, paymentsMode, paymentsIsDemo, paymentsLineItems, paymentsOrderDetails, startCheckout, });