From 3da128278d1c3a932e2d0e0f1a66083a424d9545 Mon Sep 17 00:00:00 2001 From: julien vdb Date: Mon, 27 Jul 2026 00:32:19 +0200 Subject: [PATCH] 0.13.17 : make the Stripe webhook say when it credits nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un abonnement premium souscrit en test aboutissait chez Stripe sans qu'aucune commande ni aucun droit n'apparaisse en base — et sans la moindre trace côté serveur. - `metadata.userId` absent faisait sortir le handler en silence avec un 200 : c'est désormais journalisé en erreur, avec l'id de session Stripe - l'utilisateur est résolu une fois par `documentId` (ce que le front envoie) : la relation `user` d'une commande attend un id numérique, elle recevait un documentId - un utilisateur introuvable est journalisé au lieu d'échouer plus loin - les types d'événements ignorés sont tracés, pour distinguer « Stripe n'appelle pas » de « Stripe appelle sans checkout.session.completed » Co-Authored-By: Claude Opus 5 --- package.json | 2 +- src/api/order/controllers/order.ts | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0e424ff..b5c7cc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harmony-back", - "version": "0.13.16", + "version": "0.13.17", "private": true, "description": "A Strapi application", "scripts": { diff --git a/src/api/order/controllers/order.ts b/src/api/order/controllers/order.ts index 6432e35..7c2917f 100644 --- a/src/api/order/controllers/order.ts +++ b/src/api/order/controllers/order.ts @@ -42,15 +42,37 @@ export default factories.createCoreController( premium: 5, }; - if (userId) { + if (!userId) { + // Sans `userId` dans les métadonnées, l'ancien code sortait en + // silence avec un 200 : le paiement aboutissait chez Stripe sans + // qu'aucune commande ni aucun droit n'apparaisse, et sans trace. + strapi.log.error( + `❌ Webhook ${event.type} sans metadata.userId (session ${session.id}) : aucun droit crédité`, + ); + } else { try { + // Les métadonnées portent un `documentId` (cf. le front, + // UserSubscriptionTab). La relation `user` d'une commande attend + // un id numérique : on résout l'utilisateur une fois pour les deux. + const user = await strapi.db + .query("plugin::users-permissions.user") + .findOne({ where: { documentId: userId } }); + + if (!user) { + strapi.log.error( + `❌ Webhook : aucun utilisateur pour documentId ${userId} (session ${session.id})`, + ); + ctx.send({ received: true }); + return; + } + // Création de la commande await strapi.documents("api::order.order").create({ data: { stripeId: session.id, amount: session.amount_total ? session.amount_total / 100 : 0, planType: planType, - user: userId, + user: user.id, }, }); @@ -74,6 +96,10 @@ export default factories.createCoreController( // On répond quand même 200 à Stripe pour éviter les retries infinis } } + } else { + // Permet de distinguer « Stripe n'appelle pas le webhook » de + // « Stripe appelle mais n'envoie pas checkout.session.completed ». + strapi.log.info(`ℹ️ Événement ${event.type} ignoré`); } // INDISPENSABLE : Répondre 200 OK à Stripe pour TOUS les événements