/** * Stripe Checkout Session API * Deploy as a Cloudflare Worker, Vercel function, or standalone server. * * Environment variables needed: * - STRIPE_SECRET_KEY: sk_live_... or sk_test_... * - FRONTEND_URL: https://git.fdsa.agency (or your domain) * * Usage: * POST /api/checkout * Body: { "price_id": "price_abc123", "mode": "payment", "customer_email": "..." } */ const stripeEndpoint = 'https://api.stripe.com/v1/checkout/sessions' async function handleRequest(request) { // CORS for frontend if (request.method === 'OPTIONS') { return new Response(null, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }, }) } if (request.method !== 'POST') { return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405 }) } const stripeKey = globalThis.STRIPE_SECRET_KEY || process?.env?.STRIPE_SECRET_KEY const frontendUrl = globalThis.FRONTEND_URL || process?.env?.FRONTEND_URL || 'https://git.fdsa.agency' if (!stripeKey) { return new Response(JSON.stringify({ error: 'Stripe not configured' }), { status: 500, headers: { 'Access-Control-Allow-Origin': '*' }, }) } try { const { price_id, mode, customer_email } = await request.json() const body = new URLSearchParams({ 'mode': mode || 'payment', 'success_url': `${frontendUrl}/checkout/success?session_id={CHECKOUT_SESSION_ID}`, 'cancel_url': `${frontendUrl}/checkout/cancel`, 'allow_promotion_codes': 'true', 'line_items[0][price]': price_id, 'line_items[0][quantity]': '1', }) if (customer_email) { body.set('customer_email', customer_email) } const stripeResp = await fetch(stripeEndpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${stripeKey}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }) const data = await stripeJson(stripeResp) if (!stripeResp.ok) { throw new Error(data.error?.message || 'Stripe error') } return new Response(JSON.stringify({ url: data.url, session_id: data.id }), { status: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }) } catch (e) { return new Response(JSON.stringify({ error: e.message }), { status: 500, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }) } } async function stripeJson(response) { const text = await response.text() try { return JSON.parse(text) } catch { // Stripe sometimes returns unexpected formats — parse query string const params = new URLSearchParams(text) const obj = {} for (const [k, v] of params) { // Handle nested keys like 'error.message' const parts = k.split('.') if (parts.length > 1) { let cursor = obj for (let i = 0; i < parts.length - 1; i++) { cursor[parts[i]] = cursor[parts[i]] || {} cursor = cursor[parts[i]] } cursor[parts[parts.length - 1]] = v } else { obj[k] = v } } return obj } } // Cloudflare Worker entry export default { fetch: handleRequest } // Node.js entry if (typeof module !== 'undefined' && require?.main === module) { const http = require('http') const server = http.createServer((req, res) => { // Minimal Node.js adapter — assumes body is JSON let body = '' req.on('data', chunk => body += chunk) req.on('end', async () => { globalThis.process = process globalThis.request = new Request(`http://localhost${req.url}`, { method: req.method, headers: req.headers, body: body || undefined, }) const response = await handleRequest(globalThis.request) res.writeHead(response.status, Object.fromEntries(response.headers)) res.end(await response.text()) }) }) const port = process.env.PORT || 8787 server.listen(port, () => console.log(`Checkout API on :${port}`)) }