139 lines
5.5 KiB
JavaScript
139 lines
5.5 KiB
JavaScript
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": images[0].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.FASTIFY_PORT ?? process.env.PORT ?? 3000, host: '0.0.0.0' })
|
|
} catch (err) {
|
|
app.log.error(err)
|
|
process.exit(1)
|
|
}
|