/** * Checkout service — the only place the frontend talks to Stripe / the payment * API. The UI calls these functions; it never handles card data. * * `startCheckout` follows Stripe's recommended hosted-checkout flow: * 1. Ask our backend to create a Checkout Session (amount resolved server-side). * 2. Redirect the browser to the Stripe-hosted `session.url`. * 3. Stripe handles payment, then redirects back to success_url / cancel_url. * * A Stripe.js `redirectToCheckout({ sessionId })` fallback (which uses the * publishable key) is kept for older API behavior, but `session.url` is the * current default and avoids loading Stripe.js for the redirect. */ import { loadStripe } from '@stripe/stripe-js'; import { stripeConfig } from '../config/stripeConfig'; let stripePromise; const getStripe = () => { if (!stripePromise) stripePromise = loadStripe(stripeConfig.publishableKey); return stripePromise; }; /** * Create a session for an invoice and redirect to Stripe's hosted page. * Throws (with a friendly message) if the backend isn't configured/reachable. */ export async function startCheckout({ invoiceId, customerEmail, fallback } = {}) { if (!stripeConfig.isConfigured) { const err = new Error( 'Stripe is not configured yet. Add your publishable key to enable payments.' ); err.code = 'stripe_not_configured'; throw err; } const res = await fetch(`${stripeConfig.apiBase}/create-checkout-session`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, // `fallback` carries the invoice's amount/title for demo invoices the // owner created in localStorage (the server's static map won't have them). body: JSON.stringify({ invoiceId, customerEmail, fallback }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.error || 'Could not start checkout. Please try again.'); } // Preferred: redirect straight to the Stripe-hosted Checkout URL. if (data.url) { window.location.assign(data.url); return; } // Fallback: Stripe.js redirect using the session id + publishable key. const stripe = await getStripe(); if (!stripe) throw new Error('Unable to initialize Stripe.'); const { error } = await stripe.redirectToCheckout({ sessionId: data.id }); if (error) throw new Error(error.message); } /** * Fetch the verified status of a completed Checkout Session (used by the * success return page). Returns the trimmed status object from the backend. */ export async function getSessionStatus(sessionId) { const res = await fetch( `${stripeConfig.apiBase}/session-status?session_id=${encodeURIComponent(sessionId)}` ); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.error || 'Could not verify payment status.'); } return data; }