Introduction
L'automatisation de la facturation est essentielle pour tout SaaS ou service en ligne en 2026. Manuellement gérer les abonnements, générer des PDF et envoyer des rappels expose à des erreurs coûteuses et freine la scalabilité. Avec Stripe Billing, vous centralisez les paiements récurrents, tandis que des webhooks déclenchent des actions automatisées comme la création de factures PDF et l'envoi d'emails.
Ce tutoriel intermédiaire vous guide pas à pas pour implémenter un système complet en Node.js/TypeScript : réception de webhooks Stripe (ex. invoice.paid), génération de PDF personnalisés avec pdfkit, et envoi via nodemailer. Imaginez : un client paie, et en 5 secondes, il reçoit sa facture par email.
Pourquoi c'est crucial ? Réduisez le churn de 20-30% grâce à une expérience fluide, économisez 10h/semaine, et respectez RGPD/DSGVO avec des logs traçables. Prêt à transformer votre billing en machine autonome ? (128 mots)
Prérequis
- Node.js 20+ installé
- Compte Stripe (test mode activé, API keys récupérées)
- Connaissances de base en TypeScript et Express
- Serveur SMTP (Gmail App Password ou SendGrid)
- Outils : npm/yarn, ngrok pour tester webhooks localement
Initialisation du projet
mkdir billing-automation && cd billing-automation
npm init -y
npm install express stripe pdfkit nodemailer @types/node @types/express dotenv stripe-node-webhook
npm install -D typescript ts-node @types/pdfkit @types/nodemailer
npx tsc --init --target es2022 --module commonjs --outDir ./dist --rootDir ./src --strictCe script crée un projet Node.js, installe les dépendances essentielles (Stripe SDK, pdfkit pour PDF, nodemailer pour emails) et configure TypeScript. Les types évitent les erreurs runtime. Exécutez-le pour un setup prêt en 1 minute ; stripe-node-webhook simplifie la validation des signatures.
Configuration des variables d'environnement
Créez un fichier .env à la racine :
``plaintext`
STRIPE_SECRET_KEY=sk_test_votre_cle_secrete
STRIPE_WEBHOOK_SECRET=whsec_votre_secret_webhook
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=votre.email@gmail.com
SMTP_PASS=votre_app_password
Analogie : Comme un coffre-fort, ces vars protègent vos clés API. Jamais en dur dans le code ! Ajoutez .env à .gitignore. Dans Stripe Dashboard > Développeurs > Webhooks, créez un endpoint https://votre-domaine.com/webhook et sélectionnez invoice.paid, invoice.payment_failed`.
Structure du serveur principal
import express from 'express';
import dotenv from 'dotenv';
import { Stripe } from 'stripe-node-webhook';
dotenv.config();
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.use(express.json());
const stripe = new Stripe({
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
apiKey: process.env.STRIPE_SECRET_KEY!
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Serveur billing sur port ${PORT}`);
});
export { app, stripe };Ce serveur Express base gère les body parsers pour webhooks (raw JSON requis par Stripe) et initialise le validateur webhook. Il écoute sur 3000 pour tests locaux. Piège : Oublier express.raw() casse la validation signature. Lancez avec npx ts-node src/server.ts.
Endpoint webhook avec validation
import { app, stripe } from './server';
import { generateInvoicePDF, sendInvoiceEmail } from './invoice';
app.post('/webhook', async (req, res) => {
try {
const event = await stripe.parseWebhookRequest(req);
if (event.type === 'invoice.paid') {
const invoice = event.data.object as any;
await generateInvoicePDF(invoice);
await sendInvoiceEmail(invoice.customer_email, invoice.hosted_invoice_url);
} else if (event.type === 'invoice.payment_failed') {
console.log('Paiement échoué:', event.data.object);
}
res.status(200).json({ received: true });
} catch (err) {
console.error('Webhook error:', err);
res.status(400).send(`Webhook Error: ${err}`);
}
});Cet endpoint valide automatiquement la signature Stripe via stripe-node-webhook, traite invoice.paid en générant PDF/email. Pour payment_failed, loggez pour relances. Sécurité : Toujours répondre 200 sinon Stripe retry infiniment. Intégrez-le après server.ts.
Génération de facture PDF personnalisée
Les PDF doivent être pro : logo, détails ligne, TVA calculée. Utilisez pdfkit pour du SVG-like. Stockez-les sur /invoices/{id}.pdf ou S3. Astuce : Testez avec ngrok (ngrok http 3000) pour exposer localement à Stripe.
Fonction génération PDF
import PDFDocument from 'pdfkit';
import fs from 'fs';
export async function generateInvoicePDF(invoice: any): Promise<string> {
return new Promise((resolve, reject) => {
const doc = new PDFDocument();
const filename = `./invoices/${invoice.id}.pdf`;
doc.pipe(fs.createWriteStream(filename));
doc.fontSize(25).text('Facture', 50, 50);
doc.fontSize(12).text(`ID: ${invoice.id}`, 50, 100);
doc.text(`Montant: ${invoice.amount_paid / 100}€`, 50, 120);
doc.text(`Client: ${invoice.customer_email}`, 50, 140);
doc.text(`Date: ${new Date(invoice.created * 1000).toLocaleDateString('fr-FR')}`, 50, 160);
doc.end();
doc.on('end', () => resolve(filename));
doc.on('error', reject);
});
}Cette fonction crée un PDF basique mais extensible (ajoutez logo via doc.image()). Promesse pour async. Piège : fs synchrone bloque ; utilisez async. Créez dossier invoices/ avant. Scalable vers AWS S3 avec multer-s3.
Fonction envoi email avec pièce jointe
import nodemailer from 'nodemailer';
export async function sendInvoiceEmail(email: string, invoiceUrl: string): Promise<void> {
const transporter = nodemailer.createTransporter({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
// Attachez PDF généré
await transporter.sendMail({
from: process.env.SMTP_USER,
to: email,
subject: 'Votre facture est disponible',
html: `<p>Facture attachée. <a href="${invoiceUrl}">Voir en ligne</a></p>`,
attachments: [{ path: `./invoices/${invoiceUrl.split('/').pop()!}.pdf` }]
});
}Nodemailer envoie HTML + PJ PDF. Auth Gmail : Activez 2FA + App Password. Piège : Ports TLS (587) vs SSL (465). Logs transporter.sendMail pour debug. Pour prod, migrez vers SendGrid/Resend pour deliverability >99%.
Test et mise en production
- Local :
npm run dev, ngrok, test webhook Stripe CLI (stripe listen --forward-to localhost:3000/webhook). - Prod : Vercel/Render, set env vars, HTTPS obligatoire.
- Vérifiez logs Stripe Dashboard pour retries.
Bonnes pratiques
- Idempotence : Vérifiez
invoice.iden DB pour éviter doublons (utilisez Prisma/PlanetScale). - Sécurité : Validez toujours signatures ; rate-limit
/webhookavecexpress-rate-limit. - Scalabilité : Queue (BullMQ/Redis) pour PDF/email async ; monitorez avec Sentry.
- RGPD : Stockez PDF <90 jours, logs anonymisés.
- Tests : Stripe test clocks pour simuler temps (ex. fin période).
Erreurs courantes à éviter
- Signature invalide : Oubli
express.raw()ou mauvais secret → 400 errors infinis. - Emails en spam : Pas de DKIM/SPF → Utilisez transactional email provider.
- PDF corrompu : Erreur async fs → Await toutes Promises.
- Pas de retry : Ignorez
payment_failed→ Implémentez relances auto via Stripe.
Pour aller plus loin
- Docs Stripe Billing : stripe.com/billing
- Avancé : Intégrez taxes EU avec Stripe Tax, multi-devises.
- Ressources : Tutoriel webhooks avancés