Facturation & Abonnements
Gestion des abonnements, factures et portail client via Stripe Billing. Integration complete pour la monetisation de la plateforme YaniPay.
Overview
Le module de facturation YaniPay s'appuie sur Stripe Billingpour gerer l'ensemble du cycle de vie des abonnements : creation de sessions de paiement, gestion des plans, facturation automatique et portail client self-service.
Plans tarifaires disponibles
MONTHLY et ANNUAL. Le paiement en YANI Tokenest supporté avec réduction configurable (yaniTokenDiscount sur SubscriptionDTO).5 API Endpoints
Checkout, subscriptions, invoices, portal et usage metrics
PCI Compliant
Stripe gere les donnees de carte, zero PCI scope cote serveur
Webhooks Temps Reel
Notifications instantanees pour chaque evenement de facturation
Fonctionnalites
Le module de facturation couvre les quatre piliers de la gestion d'abonnements SaaS :
Checkout Sessions
Creez des sessions de paiement securisees via Stripe Checkout. Redirection vers une page hebergee par Stripe avec gestion automatique des methodes de paiement.
Subscriptions
Gerez les abonnements mensuels et annuels avec support des upgrades, downgrades, periodes d'essai et annulations avec prorata.
Invoices
Listez et telechargez les factures au format PDF. Historique complet des paiements avec statuts et details de facturation.
Customer Portal
Portail self-service Stripe pour la gestion autonome des abonnements, methodes de paiement et telechargement de factures.
API Endpoints
Tous les endpoints de facturation requierent une authentification via session NextAuth ou JWT. Les requetes sont proxifiees vers Stripe via le backend YaniPay.
| Methode | Endpoint | Description |
|---|---|---|
| POST | /api/billing/checkout | Creer une session Stripe Checkout |
GETPOSTPATCH | /api/billing/subscription | Gerer les abonnements (consulter, creer, modifier) |
| GET | /api/billing/invoices | Lister les factures avec liens de telechargement PDF |
| POST | /api/billing/portal | Generer un lien vers le portail client Stripe |
| GET | /api/billing/usage | Metriques d'utilisation et consommation |
Checkout Flow
Le flux de paiement utilise Stripe Checkoutpour une experience de paiement securisee et optimisee. L'utilisateur est redirige vers une page hebergee par Stripe puis retourne sur YaniPay apres le paiement.
1. Creer une session Checkout
// Create checkout session
const response = await fetch('/api/billing/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tier: 'PROFESSIONAL', // 'SMART' | 'PROFESSIONAL' | 'ENTERPRISE'
cycle: 'MONTHLY', // 'MONTHLY' | 'ANNUAL' (optionnel, defaut MONTHLY)
successUrl: '/dashboard/billing?success=true',
cancelUrl: '/dashboard/billing?canceled=true',
}),
});
const { url } = await response.json();
window.location.href = url; // Redirect to Stripe Checkout2. Parametres de la requete
| Parametre | Type | Requis | Description |
|---|---|---|---|
| tier | SubscriptionTier | Plan cible : 'SMART' | 'PROFESSIONAL' | 'ENTERPRISE' | |
| cycle | BillingCycle? | optionnel | 'MONTHLY' ou 'ANNUAL' (defaut : MONTHLY) |
| successUrl | string? | optionnel | URL de redirection apres paiement reussi |
| cancelUrl | string? | optionnel | URL de redirection si annulation |
3. Reponse
{
"sessionId": "cs_live_a1b2c3d4...",
"url": "https://checkout.stripe.com/c/pay/cs_live_...",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_...",
"plan": {
"tier": "PROFESSIONAL",
"name": "YaniPay Professional",
"price": 4900,
"cycle": "MONTHLY"
},
"expiresAt": "2026-04-09T13:00:00Z"
}Gestion des Abonnements
L'endpoint /api/billing/subscriptionsupporte trois methodes HTTP pour couvrir l'ensemble du cycle de vie d'un abonnement.
Consulter l'abonnement actif
const response = await fetch('/api/billing/subscription', {
credentials: 'include',
});
const subscription = await response.json();
// Returns current subscription details{
"subscription": {
"id": "sub_1N2x3y4z",
"stripeSubscriptionId": "sub_stripe_abc",
"stripeCustomerId": "cus_stripe_xyz",
"tier": "PROFESSIONAL",
"status": "ACTIVE",
"billingCycle": "MONTHLY",
"currentPeriodStart": "2026-04-01T00:00:00Z",
"currentPeriodEnd": "2026-05-01T00:00:00Z",
"cancelAtPeriodEnd": false,
"trialEnd": null,
"yaniTokenPayment": false,
"yaniTokenDiscount": null
},
"tier": "PROFESSIONAL",
"status": "ACTIVE",
"plan": {
"id": "professional",
"name": "Professional",
"description": "Pour les professionnels exigeants",
"features": ["API illimitee", "Support prioritaire", "Analytics avancees"],
"limits": {
"transactions": 10000,
"apiCalls": 100000,
"storageGB": 50,
"teamMembers": 10,
"aiRequests": 5000
},
"price": { "monthly": 4900, "annual": 49000 },
"cashbackPercent": 2.5
},
"usage": {
"transactions": 1842,
"apiCalls": 48320,
"storageMB": 12288,
"teamMembers": 3,
"aiRequests": 412
},
"limits": {
"transactions": 10000,
"apiCalls": 100000,
"storageGB": 50,
"teamMembers": 10,
"aiRequests": 5000
},
"recentInvoices": [
{
"id": "in_abc123",
"number": "YNP-2026-0042",
"status": "PAID",
"amount": 4900,
"currency": "eur",
"paidAt": "2026-04-01T08:00:00Z",
"createdAt": "2026-04-01T00:00:00Z"
}
]
}Modifier un abonnement (upgrade/downgrade)
const response = await fetch('/api/billing/subscription', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
tier: 'ENTERPRISE', // SubscriptionTier cible
cycle: 'ANNUAL', // BillingCycle (optionnel)
// cancelAtPeriodEnd: true // Pour programmer une annulation
// resume: true // Pour reactivater apres pause
}),
});
const { subscription, message } = await response.json();
console.log(`Plan mis a jour : ${message}`);Prorata automatique
Factures
Recuperez l'historique complet des factures avec liens de telechargement PDF.
const response = await fetch('/api/billing/invoices?limit=10', {
credentials: 'include',
});
const { invoices } = await response.json();
invoices.forEach((invoice) => {
console.log(`#${invoice.number} - ${invoice.amountPaid / 100} EUR - ${invoice.status}`);
console.log(`PDF: ${invoice.invoicePdf}`);
});1 { 2 "invoices": [ 3 { 4 "id": "in_1N2x3y4z", 5 "number": "YNP-2026-0042", 6 "status": "paid", 7 "amountPaid": 2900, 8 "amountDue": 2900, 9 "currency": "eur", 10 "periodStart": "2026-01-01T00:00:00Z", 11 "periodEnd": "2026-02-01T00:00:00Z", 12 "invoicePdf": "https://pay.stripe.com/invoice/...", 13 "hostedInvoiceUrl": "https://invoice.stripe.com/i/...", 14 "created": "2026-01-01T00:00:00Z" 15 } 16 ], 17 "hasMore": true, 18 "totalCount": 12 19 }
Portail Client
Generez un lien vers le portail client Stripe ou les utilisateurs peuvent gerer leur abonnement de maniere autonome : changer de plan, mettre a jour la methode de paiement, telecharger les factures ou annuler leur abonnement.
const response = await fetch('/api/billing/portal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
returnUrl: '/dashboard/billing',
}),
});
const { url } = await response.json();
window.location.href = url; // Redirect to Stripe portal{
"url": "https://billing.stripe.com/p/session/...",
"returnUrl": "/dashboard/billing"
}Portail Self-Service
Webhook Handling
Les webhooks Stripe permettent de recevoir des notifications en temps reel pour chaque evenement de facturation. Le endpoint POST /api/webhooks/stripe verifie la signature HMAC et traite les evenements.
1 import Stripe from 'stripe'; 2 import { headers } from 'next/headers'; 3 4 const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); 5 const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!; 6 7 export async function POST(request: Request) { 8 const body = await request.text(); 9 const headersList = await headers(); 10 const signature = headersList.get('stripe-signature')!; 11 12 let event: Stripe.Event; 13 14 try { 15 event = stripe.webhooks.constructEvent(body, signature, webhookSecret); 16 } catch (err) { 17 return new Response('Webhook signature verification failed', { status: 400 }); 18 } 19 20 switch (event.type) { 21 case 'checkout.session.completed': 22 // Activer l'abonnement 23 await handleCheckoutCompleted(event.data.object); 24 break; 25 26 case 'invoice.paid': 27 // Confirmer le paiement 28 await handleInvoicePaid(event.data.object); 29 break; 30 31 case 'invoice.payment_failed': 32 // Notifier l'utilisateur 33 await handlePaymentFailed(event.data.object); 34 break; 35 36 case 'customer.subscription.updated': 37 // Mettre a jour le plan 38 await handleSubscriptionUpdated(event.data.object); 39 break; 40 41 case 'customer.subscription.deleted': 42 // Revoquer les acces 43 await handleSubscriptionCanceled(event.data.object); 44 break; 45 } 46 47 return new Response('OK', { status: 200 }); 48 }
Evenements traites
| Evenement | Action |
|---|---|
| checkout.session.completed | Activation de l'abonnement, mise a jour du role utilisateur |
| invoice.paid | Confirmation du paiement, extension de la periode |
| invoice.payment_failed | Notification email, grace period de 7 jours |
| customer.subscription.updated | Mise a jour du plan, ajustement des permissions |
| customer.subscription.deleted | Revocation des acces premium, downgrade au plan gratuit |
Securite des Webhooks
stripe.webhooks.constructEvent() avant de traiter un evenement. Ne faites jamais confiance au payload sans verification de signature. La variable STRIPE_WEBHOOK_SECRET doit etre configuree dans .env.local.Usage Metrics
Consultez les metriques de consommation pour le suivi de l'utilisation et la facturation a l'usage.
const response = await fetch('/api/billing/usage?period=current', {
credentials: 'include',
});
const usage = await response.json();{
"tier": "PROFESSIONAL",
"status": "ACTIVE",
"period": {
"type": "current",
"start": "2026-04-01T00:00:00Z",
"end": "2026-05-01T00:00:00Z"
},
"usage": {
"apiCalls": {
"current": 48320,
"limit": 100000,
"percentage": 48.3,
"exceeded": false
},
"transactions": {
"current": 1842,
"limit": 10000,
"percentage": 18.4,
"exceeded": false
},
"aiRequests": {
"current": 412,
"limit": 5000,
"percentage": 8.2,
"exceeded": false
}
},
"trends": {
"apiCalls": { "change": 12.3, "direction": "up" },
"transactions": { "change": -2.1, "direction": "down" }
},
"plan": {
"name": "Professional",
"limits": {
"transactions": 10000,
"apiCalls": 100000,
"storageGB": 50,
"teamMembers": 10,
"aiRequests": 5000
}
},
"recentActivity": []
}Gestion des Erreurs
Les endpoints de facturation retournent des codes HTTP standard avec des messages d'erreur structures.
Bad Request
Parametres invalides (priceId manquant, URL mal formee)
Unauthorized
Session absente ou expiree, authentification requise
Payment Required
Methode de paiement invalide ou fonds insuffisants
Not Found
Abonnement ou client Stripe introuvable
Too Many Requests
Rate limit Stripe depasse (100 req/min)
Internal Server Error
Erreur serveur ou indisponibilite Stripe
{
"error": "Invalid price ID",
"code": "INVALID_PRICE",
"message": "The price ID 'price_invalid' does not exist in Stripe",
"details": {
"priceId": "price_invalid",
"suggestion": "Use a valid Stripe price ID from the dashboard"
}
}