142 lines
4.1 KiB
JavaScript
142 lines
4.1 KiB
JavaScript
/**
|
|
* 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}`))
|
|
}
|