J'ai récemment déployé un système d'abonnement Stripe complet sur SunderDev lui-même. Voici les décisions d'architecture qui ont évité les pièges habituels.
Le SDK officiel
composer require stripe/stripe-phpEn 2026 la version 15+ est stable. Toujours préférer le SDK officiel à une implémentation maison : les événements webhook évoluent, les idempotency keys sont gérées, les retries aussi.
Architecture recommandée
- Checkout hébergé par Stripe : PCI compliance offerte, 0 token de carte qui transite chez vous.
- Webhooks comme source de vérité : jamais faire confiance au redirect client.
- Customer Portal : laissez Stripe gérer les upgrades/downgrades/annulations.
- Idempotency keys sur toutes les créations.
Le flow complet
1. Création de la session Checkout
$session = \Stripe\Checkout\Session::create([
'mode' => 'subscription',
'customer_email' => $user->email,
'line_items' => [['price' => $priceId, 'quantity' => 1]],
'success_url' => $baseUrl . '/abonnement-success?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $baseUrl . '/abonnement-cancel',
'metadata' => ['user_id' => $user->id],
'subscription_data' => ['metadata' => ['user_id' => $user->id]],
]);
header('Location: ' . $session->url);2. Vérification signature webhook
$payload = file_get_contents('php://input');
$sig = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
try {
$event = \Stripe\Webhook::constructEvent($payload, $sig, $webhookSecret);
} catch (\UnexpectedValueException | \Stripe\Exception\SignatureVerificationException $e) {
http_response_code(400);
exit('Invalid webhook');
}Ne jamais, jamais traiter un webhook sans vérification signature. C'est la porte ouverte à la fraude.
3. Événements à gérer
checkout.session.completed: activation initiale.customer.subscription.updated: changement de plan.customer.subscription.deleted: annulation effective.invoice.payment_succeeded: reconnaissance de revenu.invoice.payment_failed: relance client + dégradation.
4. Idempotence
Stockez le event->id dans une table dédiée avant traitement. Stripe retry les webhooks jusqu'à succès — sans idempotence, vous allez doubler vos actions.
if ($db->processedEventExists($event->id)) {
http_response_code(200);
exit('OK — already processed');
}
$db->markProcessed($event->id);Les pièges à éviter
- Ne pas fier à
success_urlpour activer un compte. Un utilisateur peut fermer l'onglet — le webhook est votre seule source fiable. - Timezones :
current_period_endest en timestamp Unix UTC. Toujours convertir explicitement. - Prorata : les changements de plan mid-cycle créent des
invoice.updatedavec des lignes de crédit. Testez-les. - Tax : si vous vendez en Europe, activez Stripe Tax sinon vous vous faites rattraper.
Customer Portal
Une seule API, une app entière "gérer mon abonnement" clé en main :
$portal = \Stripe\BillingPortal\Session::create([
'customer' => $customerId,
'return_url' => $baseUrl . '/mon-compte',
]);
header('Location: ' . $portal->url);Upgrades, downgrades, mise à jour CB, factures téléchargeables, annulations — tout est géré par Stripe. Ne réinventez pas la roue.
Tests en local
Stripe CLI permet de forwarder les webhooks vers votre localhost :
stripe listen --forward-to localhost:8000/webhook.phpPuis stripe trigger invoice.payment_failed pour simuler chaque cas.
Conclusion
L'intégration Stripe en elle-même est propre. La difficulté est dans la logique métier autour : provisioning, dégradation en cas d'impayé, notifications, relances. Prévoyez du temps pour ça, pas pour l'API en elle-même.
Docs officielles : Subscriptions overview et Webhooks guide.
Vous voulez mettre en place Stripe proprement sur votre SaaS ? Je l'ai fait plusieurs fois, je peux vous faire gagner 2 semaines.