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}`); });