import Fastify from 'fastify' import cors from '@fastify/cors' import Stripe from 'stripe' import { createClient } from '@sanity/client' import dotenv from 'dotenv' dotenv.config() // ── Sanity client ────────────────────────────────────────────────────────── const sanity = createClient({ projectId: process.env.SANITY_PROJECT_ID, dataset: process.env.SANITY_DATASET || 'production', apiVersion: '2024-01-01', useCdn: false, // Server-side: always fresh data token: process.env.SANITY_API_TOKEN, // Optional: for authenticated reads }) // ── Stripe ───────────────────────────────────────────────────────────────── const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '') const DOMAIN = process.env.DOMAIN ?? 'http://localhost:4321' // ── Fastify ──────────────────────────────────────────────────────────────── const app = Fastify({ logger: true, trustProxy: true }) await app.register(cors, { origin: '*', methods: ['GET', 'POST'] }) // ── Webhook Stripe ───────────────────────────────────────────────────────── app.post('/api/webhook', { config: { rawBody: true }, onRequest: (request, reply, done) => { request.rawBody = '' request.req.on('data', chunk => { request.rawBody += chunk }) request.req.on('end', done) }, }, async (request, reply) => { const sig = request.headers['stripe-signature'] const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET if (!sig || !webhookSecret) return reply.code(400).send('Missing signature') let event try { event = stripe.webhooks.constructEvent(request.rawBody, sig, webhookSecret) } catch { return reply.code(400).send('Webhook Error') } if (event.type === 'checkout.session.completed') { const session = event.data.object if (session.payment_status === 'paid') { app.log.info( `✓ Paiement confirmé — ${session.id} — ${session.customer_details?.email} — ${session.metadata?.product}` ) } } return { received: true } }) // ── Health check ──────────────────────────────────────────────────────────── app.get('/api/health', async () => ({ status: 'ok' })) // ── Checkout Stripe ───────────────────────────────────────────────────────── app.post('/api/checkout', async (request, reply) => { const { product: slug, email } = request.body ?? {} if (!slug) return reply.code(400).send({ error: 'Produit manquant' }) // Fetch product from Sanity const product = await sanity.fetch( `*[_type == "product" && slug.current == $slug && isPublished == true][0]{ productDisplayName, description, price, currency, "imageUrl": image.asset->url, "slug": slug.current }`, { slug } ) if (!product || !product.price) { return reply.code(404).send({ error: 'Produit non disponible' }) } try { const session = await stripe.checkout.sessions.create({ mode: 'payment', payment_method_types: ['card', 'link'], line_items: [{ price_data: { currency: (product.currency || 'EUR').toLowerCase(), product_data: { name: product.productDisplayName, description: product.description?.substring(0, 500) || undefined, images: product.imageUrl ? [product.imageUrl] : [], }, unit_amount: product.price, }, quantity: 1, }], metadata: { product: slug }, success_url: `${DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${DOMAIN}/#collection`, locale: 'fr', customer_email: email ?? undefined, custom_text: { submit: { message: 'Pièce unique — fabriquée à Paris. Délai : 6 à 8 semaines.' }, }, }) return { url: session.url } } catch (err) { app.log.error(err) return reply.code(500).send({ error: err.message }) } }) // ── Vérification session ──────────────────────────────────────────────────── app.get('/api/session/:id', async (request) => { const session = await stripe.checkout.sessions.retrieve(request.params.id, { expand: ['payment_intent.latest_charge'], }) const charge = session.payment_intent?.latest_charge return { status: session.payment_status, amount: session.amount_total, currency: session.currency, customer_email: session.customer_details?.email ?? null, product: session.metadata?.product ?? null, receipt_url: charge?.receipt_url ?? null, } }) // ── Start ─────────────────────────────────────────────────────────────────── try { await app.listen({ port: process.env.PORT ?? 3000, host: '0.0.0.0' }) } catch (err) { app.log.error(err) process.exit(1) }