Ce qui est branché · 3 min
Paiements
Stripe Checkout, un webhook idempotent, et pourquoi le prix ne vient jamais du navigateur.
L'argent bouge à deux endroits et nulle part ailleurs : une route qui ouvre une session Checkout, et un webhook qui décide qu'une vente a eu lieu.
Ouvrir le paiement
src/app/api/checkout/route.ts prend un identifiant d'offre. C'est tout ce
qu'il prend.
const { data: plan } = await admin
.from("plans").select("*").eq("id", planId).single();
const session = await stripe().checkout.sessions.create({
mode: "payment",
line_items: [{ quantity: 1, price_data: {
currency: plan.currency,
unit_amount: plan.price_cents,
tax_behavior: "exclusive",
product_data: { name: plan.name, tax_code: "txcd_10000000" },
}}],
automatic_tax: { enabled: true },
metadata: { plan_id: plan.id, user_id: user?.id ?? "" },
success_url: `${origin}/app?welcome=1`,
cancel_url: `${origin}/#pricing`,
});Le prix est lu dans la base. Il n'est jamais pris dans le corps de la requête,
parce qu'un montant envoyé est une remise envoyée. Si votre route de paiement
accepte amount, quelqu'un finira par lui envoyer un.
Le webhook fait seul autorité
checkout.session.completed est l'endroit où une commande devient réelle. Trois
règles la rendent sûre à rejouer, ce qui compte parce que Stripe réessaie et
livre parfois le même événement plusieurs fois :
Vérifier la signature en premier
Un POST non signé vers cette URL, c'est quelqu'un qui se fait passer pour Stripe. Refusez-le avec un 400 avant de lire le moindre champ.
Indexer la ligne sur l'identifiant de session
Un upsert sur stripe_session_id, qui est unique. Un rejeu met à jour la
ligne déjà écrite au lieu de créer une seconde commande payée.
Vérifier avant de délivrer
Cherchez une licence ou un droit déjà associé à cette commande et sortez tôt s'il existe. Sinon un réessai envoie un second e-mail de bienvenue, et c'est ainsi que vos clients découvrent que votre webhook est cassé.
Les remboursements
Activez charge.refunded sur l'endpoint et défaites tout ce que la vente avait
créé : passez la commande en remboursée, retirez le droit d'accès. Si vos
conditions disent qu'un remboursement annule quelque chose, quelque chose doit
réellement l'annuler.
La TVA
Pour un produit numérique vendu depuis l'UE, activez Stripe Tax et ajoutez au minimum une immatriculation pour votre propre pays. Deux réglages décident de ce que voit l'acheteur :
tax_code: "txcd_10000000"marque la ligne comme service électronique général. Sans lui, Stripe ne peut pas choisir de taux et ne facture rien.tax_behavior: "exclusive"signifie que votre montant est hors taxes et que la taxe s'ajoute.inclusivesignifie qu'il la contient déjà.
Tester
Carte 4242 4242 4242 4242, n'importe quelle date future, n'importe quel CVC.
Pour exercer le webhook en local, redirigez les événements plutôt que de
deviner la charge utile :
stripe listen --forward-to localhost:3000/api/webhooks/stripeLa commande affiche un whsec_... pour la session. Mettez-le dans .env le
temps de vos tests.
Une erreur ou un manque sur cette page ? Dites-le-nous.