240 lines
7.7 KiB
JavaScript
240 lines
7.7 KiB
JavaScript
const express = require('express');
|
|
const Stripe = require('stripe');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
const config = {
|
|
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',
|
|
PORT: process.env.PORT || 8080
|
|
};
|
|
|
|
// Stripe service
|
|
class StripeService {
|
|
constructor() {
|
|
this.client = new Stripe(config.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: `${config.APP_HOST}/subscription/success`, // Success page (frontend)
|
|
cancel_url: `${config.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: `${config.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, config.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'
|
|
});
|
|
}
|
|
});
|
|
|
|
app.listen(config.PORT, () => {
|
|
console.log(`Serveur démarré sur le port ${config.PORT}`);
|
|
}); |