commit
1b522c0df4
15
.env.exemple
15
.env.exemple
@ -11,7 +11,20 @@ SMS_FACTOR_TOKEN=
|
||||
MAILCHIMP_API_KEY=
|
||||
MAILCHIMP_KEY=
|
||||
MAILCHIMP_LIST_ID=
|
||||
APP_HOST=
|
||||
|
||||
|
||||
#Configuration Stripe
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID=
|
||||
STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID=
|
||||
STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID=
|
||||
STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID=
|
||||
|
||||
#Cartes de test Stripe
|
||||
SUCCES= 4242 4242 4242 4242 #Paiement réussi
|
||||
DECLINED= 4000 0025 0000 3155 #Paiement refusé
|
||||
|
||||
# Configuration serveur
|
||||
APP_HOST=
|
||||
PORT=
|
||||
|
@ -13,7 +13,8 @@
|
||||
"dotenv": "^17.2.0",
|
||||
"express": "^4.18.2",
|
||||
"node-fetch": "^2.6.7",
|
||||
"ovh": "^2.0.3"
|
||||
"ovh": "^2.0.3",
|
||||
"stripe": "^18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
|
234
src/server.js
234
src/server.js
@ -3,6 +3,7 @@ const cors = require('cors');
|
||||
const fetch = require('node-fetch');
|
||||
const ovh = require('ovh');
|
||||
const mailchimp = require('@mailchimp/mailchimp_transactional');
|
||||
const Stripe = require('stripe');
|
||||
require('dotenv').config();
|
||||
|
||||
// Initialisation de l'application Express
|
||||
@ -619,6 +620,239 @@ setInterval(() => {
|
||||
|
||||
//------------------------------------ End of Email Section ------------------------------------
|
||||
|
||||
//------------------------------------ Stripe Section ------------------------------------------
|
||||
|
||||
const configStripe = {
|
||||
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
|
||||
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
|
||||
APP_HOST: process.env.APP_HOST || 'http://localhost:3000',
|
||||
};
|
||||
|
||||
// Stripe service
|
||||
class StripeService {
|
||||
constructor() {
|
||||
this.client = new Stripe(configStripe.STRIPE_SECRET_KEY);
|
||||
this.prices = {
|
||||
STANDARD: {
|
||||
monthly: process.env.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID,
|
||||
yearly: process.env.STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID
|
||||
},
|
||||
UNLIMITED: {
|
||||
monthly: process.env.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID,
|
||||
yearly: process.env.STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Only for test
|
||||
async createTestSubscription() {
|
||||
try {
|
||||
const customer = await this.client.customers.create({
|
||||
email: 'test@example.com',
|
||||
description: 'Client test',
|
||||
source: 'tok_visa'
|
||||
});
|
||||
|
||||
const priceId = this.prices.STANDARD.monthly;
|
||||
const price = await this.client.prices.retrieve(priceId);
|
||||
|
||||
const subscription = await this.client.subscriptions.create({
|
||||
customer: customer.id,
|
||||
items: [{ price: price.id }],
|
||||
payment_behavior: 'default_incomplete',
|
||||
expand: ['latest_invoice.payment_intent']
|
||||
});
|
||||
|
||||
return {
|
||||
subscriptionId: subscription.id,
|
||||
customerId: customer.id,
|
||||
status: subscription.status,
|
||||
priceId: price.id
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createCheckoutSession(subscription, frequency) {
|
||||
const priceId = this.getPriceId(subscription.type, frequency);
|
||||
|
||||
return await this.client.checkout.sessions.create({
|
||||
mode: 'subscription',
|
||||
payment_method_types: ['card', 'sepa_debit'],
|
||||
billing_address_collection: 'auto',
|
||||
line_items: [{
|
||||
price: priceId,
|
||||
quantity: subscription.type === 'STANDARD' ? subscription.seats : 1,
|
||||
}],
|
||||
success_url: `${configStripe.APP_HOST}/subscription/success`, // Success page (frontend)
|
||||
cancel_url: `${configStripe.APP_HOST}/subscription/error`, // Error page (frontend)
|
||||
metadata: {
|
||||
subscription: JSON.stringify(subscription),
|
||||
},
|
||||
allow_promotion_codes: true,
|
||||
automatic_tax: { enabled: true }
|
||||
});
|
||||
}
|
||||
|
||||
getPriceId(type, frequency) {
|
||||
return this.prices[type][frequency];
|
||||
}
|
||||
|
||||
async getSubscription(subscriptionId) {
|
||||
return await this.client.subscriptions.retrieve(subscriptionId);
|
||||
}
|
||||
|
||||
async createPortalSession(subscriptionId) {
|
||||
const subscription = await this.getSubscription(subscriptionId);
|
||||
return await this.client.billingPortal.sessions.create({
|
||||
customer: subscription.customer,
|
||||
return_url: `${configStripe.APP_HOST}/subscription/manage`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const stripeService = new StripeService();
|
||||
|
||||
// Validation middleware
|
||||
const validateSubscription = (req, res, next) => {
|
||||
const { type, seats, frequency } = req.body;
|
||||
|
||||
if (!type || !['STANDARD', 'UNLIMITED'].includes(type)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Type d\'abonnement invalide'
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'STANDARD' && (!seats || seats < 1)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Nombre de sièges invalide'
|
||||
});
|
||||
}
|
||||
|
||||
if (!frequency || !['monthly', 'yearly'].includes(frequency)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Fréquence invalide'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Routes
|
||||
|
||||
// Only for test
|
||||
app.post('/api/test/create-subscription', async (req, res) => {
|
||||
try {
|
||||
const result = await stripeService.createTestSubscription();
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Erreur lors de la création de l\'abonnement de test',
|
||||
error: {
|
||||
message: error.message,
|
||||
type: error.type,
|
||||
code: error.code
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/subscriptions/checkout', validateSubscription, async (req, res) => {
|
||||
try {
|
||||
const session = await stripeService.createCheckoutSession(req.body, req.body.frequency);
|
||||
res.json({ success: true, sessionId: session.id });
|
||||
} catch (error) {
|
||||
console.error('Erreur création checkout:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Erreur lors de la création de la session de paiement'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/subscriptions/:id', async (req, res) => {
|
||||
try {
|
||||
const subscription = await stripeService.getSubscription(req.params.id);
|
||||
res.json({ success: true, subscription });
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Erreur lors de la récupération de l\'abonnement'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/subscriptions/:id/portal', async (req, res) => {
|
||||
try {
|
||||
const session = await stripeService.createPortalSession(req.params.id);
|
||||
res.json({ success: true, url: session.url });
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Erreur lors de la création de la session du portail'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Webhook Stripe
|
||||
app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
|
||||
const sig = req.headers['stripe-signature'];
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = Stripe.webhooks.constructEvent(req.body, sig, configStripe.STRIPE_WEBHOOK_SECRET);
|
||||
} catch (err) {
|
||||
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed':
|
||||
const session = event.data.object;
|
||||
if (session.status === 'complete') {
|
||||
const subscription = JSON.parse(session.metadata.subscription);
|
||||
// Stock subscription (create process)
|
||||
console.log('Nouvel abonnement:', subscription);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invoice.payment_succeeded':
|
||||
const invoice = event.data.object;
|
||||
if (['subscription_update', 'subscription_cycle'].includes(invoice.billing_reason)) {
|
||||
const subscription = await stripeService.getSubscription(invoice.subscription);
|
||||
// Update subscription (update process)
|
||||
console.log('Mise à jour abonnement:', subscription);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'customer.subscription.deleted':
|
||||
const deletedSubscription = event.data.object;
|
||||
// Delete subscription (update process to delete)
|
||||
console.log('Suppression abonnement:', deletedSubscription.id);
|
||||
break;
|
||||
}
|
||||
|
||||
res.json({ received: true });
|
||||
} catch (error) {
|
||||
console.error('Erreur webhook:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Erreur lors du traitement du webhook'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------ End of Stripe Section -----------------------------------
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user