import { Elysia, t } from 'elysia' import { cors } from '@elysiajs/cors' import Stripe from 'stripe' import { readFileSync, existsSync, statSync } from 'fs' import { join, extname } from 'path' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '', { apiVersion: '2025-01-27.acacia', }) const DOMAIN = process.env.DOMAIN ?? 'http://localhost:3000' const PUBLIC_DIR = join(import.meta.dir, 'public') const PRODUCTS = { lumiere_orbitale: { name: 'LUMIÈRE_ORBITALE — REBOUR', description: 'Lampe de table unique. Béton texturé coulé à la main + dôme céramique laqué. Collection 001.', amount: 180000, currency: 'eur', }, } // Map extensions → MIME types const MIME: Record = { '.html': 'text/html; charset=utf-8', '.css': 'text/css', '.js': 'application/javascript', '.json': 'application/json', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.woff2':'font/woff2', '.txt': 'text/plain', '.xml': 'application/xml', } const HTML_HEADERS = { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'Referrer-Policy': 'strict-origin-when-cross-origin', } // Sert un fichier statique depuis public/ function serveStatic(relativePath: string): Response { const filePath = join(PUBLIC_DIR, relativePath) if (!existsSync(filePath) || statSync(filePath).isDirectory()) { return new Response('Not Found', { status: 404 }) } const ext = extname(filePath).toLowerCase() const mime = MIME[ext] ?? 'application/octet-stream' const isAsset = ['.jpg', '.jpeg', '.png', '.webp', '.svg', '.ico', '.woff2', '.css', '.js'].includes(ext) return new Response(Bun.file(filePath), { headers: { 'Content-Type': mime, 'Cache-Control': isAsset ? 'public, max-age=31536000, immutable' : 'public, max-age=3600', }, }) } const app = new Elysia() .use(cors({ origin: '*', methods: ['GET', 'POST'] })) // ── Pages HTML ───────────────────────────────────────────────────────────── .get('/', () => new Response(readFileSync(join(PUBLIC_DIR, 'index.html'), 'utf-8'), { headers: HTML_HEADERS }) ) .get('/success', () => new Response(readFileSync(join(PUBLIC_DIR, 'success.html'), 'utf-8'), { headers: HTML_HEADERS }) ) // ── SEO : robots + sitemap ───────────────────────────────────────────────── .get('/robots.txt', () => new Response(`User-agent: *\nAllow: /\nSitemap: ${DOMAIN}/sitemap.xml\n`, { headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'public, max-age=86400' }, }) ) .get('/sitemap.xml', () => { const today = new Date().toISOString().split('T')[0] return new Response( `\n\n ${DOMAIN}/${today}weekly1.0\n`, { headers: { 'Content-Type': 'application/xml', 'Cache-Control': 'public, max-age=86400' } } ) }) // ── Fichiers statiques : /assets/*, /style.css, /main.js, etc. ──────────── .get('/assets/*', ({ params }) => serveStatic(`assets/${(params as any)['*']}`)) .get('/style.css', () => serveStatic('style.css')) .get('/main.js', () => serveStatic('main.js')) // ── API Stripe : créer session checkout ─────────────────────────────────── .post( '/api/checkout', async ({ body }) => { const product = PRODUCTS[body.product as keyof typeof PRODUCTS] if (!product) return new Response('Produit inconnu', { status: 404 }) const session = await stripe.checkout.sessions.create({ mode: 'payment', payment_method_types: ['card'], line_items: [{ price_data: { currency: product.currency, unit_amount: product.amount, product_data: { name: product.name, description: product.description }, }, quantity: 1, }], success_url: `${DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${DOMAIN}/#collection`, locale: 'fr', customer_email: body.email ?? undefined, custom_text: { submit: { message: 'Pièce unique — fabriquée à Paris. Délai : 6 à 8 semaines.' }, }, }) return { url: session.url } }, { body: t.Object({ product: t.String(), email: t.Optional(t.String()), }), } ) // ── API : vérifier session après paiement ───────────────────────────────── .get( '/api/session/:id', async ({ params }) => { const session = await stripe.checkout.sessions.retrieve(params.id) return { status: session.payment_status, amount: session.amount_total, currency: session.currency, customer_email: session.customer_details?.email ?? null, } }, { params: t.Object({ id: t.String() }) } ) // ── Webhook Stripe ───────────────────────────────────────────────────────── .post('/api/webhook', async ({ request, headers }) => { const sig = headers['stripe-signature'] const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET if (!sig || !webhookSecret) return new Response('Missing signature', { status: 400 }) let event: Stripe.Event try { event = stripe.webhooks.constructEvent( Buffer.from(await request.arrayBuffer()), sig, webhookSecret ) } catch { return new Response('Webhook Error', { status: 400 }) } if (event.type === 'checkout.session.completed') { const session = event.data.object as Stripe.Checkout.Session if (session.payment_status === 'paid') { console.log(`✓ Paiement — ${session.id} — ${session.customer_details?.email}`) } } return { received: true } }) .listen(3000) console.log(` ┌──────────────────────────────────────┐ │ REBOUR — http://localhost:3000 │ │ NODE_ENV: ${process.env.NODE_ENV ?? 'development'} └──────────────────────────────────────┘ `)