0.13.3 : add stripe and subscription plan
Some checks failed
Build release Docker image / Build Docker Images (push) Failing after 4m18s
Some checks failed
Build release Docker image / Build Docker Images (push) Failing after 4m18s
This commit is contained in:
32
src/api/order/content-types/order/schema.json
Normal file
32
src/api/order/content-types/order/schema.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/api/order/controllers/order.ts
Normal file
75
src/api/order/controllers/order.ts
Normal 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 });
|
||||
},
|
||||
}),
|
||||
);
|
||||
24
src/api/order/routes/order.ts
Normal file
24
src/api/order/routes/order.ts
Normal 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",
|
||||
},
|
||||
],
|
||||
};
|
||||
7
src/api/order/services/order.ts
Normal file
7
src/api/order/services/order.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* order service
|
||||
*/
|
||||
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreService('api::order.order');
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user