0.13.17 : make the Stripe webhook say when it credits nothing

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 00:32:19 +02:00
parent 883b827b0d
commit 3da128278d
2 changed files with 29 additions and 3 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "harmony-back", "name": "harmony-back",
"version": "0.13.16", "version": "0.13.17",
"private": true, "private": true,
"description": "A Strapi application", "description": "A Strapi application",
"scripts": { "scripts": {
+28 -2
View File
@@ -42,15 +42,37 @@ export default factories.createCoreController(
premium: 5, 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 { 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 // Création de la commande
await strapi.documents("api::order.order").create({ await strapi.documents("api::order.order").create({
data: { data: {
stripeId: session.id, stripeId: session.id,
amount: session.amount_total ? session.amount_total / 100 : 0, amount: session.amount_total ? session.amount_total / 100 : 0,
planType: planType, 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 // 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 // INDISPENSABLE : Répondre 200 OK à Stripe pour TOUS les événements