0.13.3 : add stripe and subscription plan
Some checks failed
Build release Docker image / Build Docker Images (push) Failing after 4m18s

This commit is contained in:
2026-03-05 18:19:34 +01:00
parent ce37fafddf
commit bc63c56da6
10 changed files with 9608 additions and 5 deletions

View File

@@ -0,0 +1,32 @@
{
"kind": "collectionType",
"collectionName": "orders",
"info": {
"singularName": "order",
"pluralName": "orders",
"displayName": "Order",
"description": ""
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {},
"attributes": {
"stripeId": {
"type": "string",
"unique": true
},
"amount": {
"type": "decimal"
},
"planType": {
"type": "string"
},
"user": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.user",
"inversedBy": "orders"
}
}
}

View File

@@ -0,0 +1,75 @@
import { factories } from "@strapi/strapi";
import Stripe from "stripe";
export default factories.createCoreController(
"api::order.order" as any,
({ strapi }) => ({
async handleWebhook(ctx) {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const sig = ctx.request.headers["stripe-signature"];
const unparsedBody = ctx.request.body[Symbol.for("unparsedBody")];
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
unparsedBody,
sig as string,
process.env.STRIPE_WEBHOOK_SECRET!,
);
} catch (err: any) {
strapi.log.error(`❌ Erreur de signature Webhook: ${err.message}`);
return ctx.badRequest(`Webhook Error: ${err.message}`);
}
// IMPORTANT : On logue le type d'événement reçu pour débugger
strapi.log.info(`📩 Événement reçu : ${event.type}`);
// On ne traite QUE le checkout.session.completed
if (event.type === "checkout.session.completed") {
const session = event.data.object as Stripe.Checkout.Session;
const userId = session.metadata?.userId;
const planType = session.metadata?.planType as
| "free"
| "pro"
| "premium";
if (userId) {
try {
// 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,
},
});
// Mise à jour de l'utilisateur
await strapi.documents("plugin::users-permissions.user").update({
documentId: userId,
data: {
isPremium: true,
subscriptionPlan: planType,
stripeCustomerId: session.customer as string,
subscriptionId: session.subscription as string,
trialStartedAt: null,
},
});
strapi.log.info(
`✅ Droits mis à jour pour l'utilisateur ${userId}`,
);
} catch (dbError) {
strapi.log.error(`❌ Erreur base de données : ${dbError.message}`);
// On répond quand même 200 à Stripe pour éviter les retries infinis
}
}
}
// INDISPENSABLE : Répondre 200 OK à Stripe pour TOUS les événements
// Cela évite que Stripe ne renvoie la requête 10 fois
ctx.send({ received: true });
},
}),
);

View File

@@ -0,0 +1,24 @@
export default {
routes: [
// La route pour le Webhook Stripe
{
method: "POST",
path: "/stripe/webhook",
handler: "api::order.order.handleWebhook",
config: {
auth: false,
},
},
// Les routes automatiques (find, findOne, create, etc.)
{
method: "GET",
path: "/orders",
handler: "api::order.order.find",
},
{
method: "GET",
path: "/orders/:id",
handler: "api::order.order.findOne",
},
],
};

View File

@@ -0,0 +1,7 @@
/**
* order service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::order.order');

View File

@@ -242,6 +242,28 @@
},
"education": {
"type": "string"
},
"subscriptionPlan": {
"type": "enumeration",
"enum": [
"free",
"pro",
"premium"
],
"default": "free"
},
"maxChoirs": {
"type": "integer",
"default": 0
},
"trialStartedAt": {
"type": "date"
},
"orders": {
"type": "relation",
"relation": "oneToMany",
"target": "api::order.order",
"mappedBy": "user"
}
}
}