Si tu es ici, c’est que tu sens le truc venir : ton SaaS tourne, l’argent rentre, et au milieu du moteur il y a Laravel Spark. Sauf qu’en 2026 l’écosystème a bougé, Laravel avance vite, et Spark donne ce sentiment désagréable d’être « en retard d’un train ». Ce n’est pas juste une discussion de puristes. C’est une vraie dette produit : le jour où tu dois upgrader Laravel, toucher à la facturation, ou répondre à un incident Stripe, tu découvres que ton cœur business dépend d’un starter kit.
L’objectif de cet article est simple : te donner un plan de migration réaliste pour une app existante. Pas une refonte totale. Pas « on va tout réécrire ». Un plan qui te permet de reprendre la main, progressivement, sans te retrouver à recoder la factu en panique un dimanche soir.
Laravel Spark est-il mort ? La vraie question, c’est plutôt « est-ce qu’il te met en risque ? »
Je vais être franc : je ne sais pas si Spark est « mort » au sens strict, et ce n’est même pas le point le plus important. Ce qui compte, c’est l’incertitude : quand un package central n’a plus un rythme de maintenance aligné avec ton framework, tu finis par arbitrer en permanence entre « je reste sur une vieille stack » et « je prends le risque de casser la partie qui facture ».
Dans les équipes, ça se traduit par des symptômes très concrets : tu repousses un upgrade Laravel parce que Spark n’est pas compatible, tu évites de toucher au checkout par peur de la régression, et tu passes ton temps à bricoler autour (scripts, patches, contournements) au lieu d’avoir une architecture claire. Donc oui, la bonne question est : combien ça te coûte de rester, et combien ça te coûterait de sortir… en choisissant le bon chemin.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
Audit Spark : ce que tu dois inventorier avant de toucher à quoi que ce soit
Le piège classique, c’est de réduire Spark à « abonnement + paiement ». En vrai, dans une app en prod, Spark a souvent colonisé plus large : l’auth, les teams, la gestion des cartes, le portail client, la facturation PDF, les mails, les webhooks, parfois même des bouts d’UI et de routing. Si tu démarres une migration sans inventaire, tu vas oublier un truc et le découvrir au pire moment, côté support ou côté compta.
Quand je fais cet audit, je cherche quatre familles de dépendances.
D’abord la surface “billing provider” : ce qui parle réellement à Stripe (ou autre). Où sont créés les customers ? Où sont créés les subscriptions ? Qui gère les upgrades/downgrades ? Qui gère les coupons ? Rien que répondre à ces questions te dit déjà si tu es face à un Spark « juste pratique » ou un Spark « complètement central ».
Ensuite, la donnée locale. Quelles tables Spark a ajoutées ? Qu’est-ce que ton app lit réellement dans ces tables ? Est-ce que ton code dépend d’un statut d’abonnement stocké localement ? Est-ce que tu as des « entitlements » dérivés (features activées, quotas, limites) qui sont calculés à partir du plan ? C’est souvent là que tu as le plus gros risque de régression, parce que le produit s’est mis à dépendre d’un modèle implicite.
Troisième point : les webhooks et la résilience. On ne s’en rend pas compte tant que ça marche, mais toute la vérité de la factu moderne est dans les événements. Si Spark fait une partie du job (et que tu as complété avec du code maison), tu veux comprendre exactement qui « fait foi » entre ton DB et Stripe. Si tu n’as pas de réponse nette, ton chantier de migration doit commencer par là.
Enfin, l’expérience client : portail, emails, TVA, factures, mentions légales, pièces comptables. Ce n’est pas glamour, mais c’est ce qui déclenche des tickets. Et une migration de billing qui “techniquement marche” peut quand même faire exploser le support parce que les emails ont changé, que les factures n’ont plus la même numérotation, ou que les liens de gestion de carte ne sont plus au même endroit.
Le move qui te sauve : découpler ton « contrat billing » de l’outil (Spark, Cashier, maison)
Si tu veux sortir proprement, tu dois arrêter de laisser l’implémentation piloter ton modèle. Spark (comme beaucoup de starters) te donne un modèle de données et une manière de penser. C’est pratique au début, mais au fil du temps, ton produit colle à cette forme.
Ce que tu veux, c’est définir ton contrat billing côté domaine. Pas compliqué, pas une usine à gaz. Juste les concepts dont ton SaaS a besoin pour fonctionner sans Stripe dans la tête à chaque requête : qui est abonné à quoi, dans quel état, jusqu’à quand, avec quels droits, et quelle est la source de vérité (provider vs DB).
Ça ressemble souvent à un modèle interne minimal : un plans (tes offres), un subscriptions (l’abonnement côté app, avec un état), éventuellement des entitlements si tu veux découpler features et plans, et surtout une notion de provider (Stripe/Paddle/… et leurs identifiants). À partir de là, Spark devient « un moyen » de remplir et synchroniser ce contrat, pas « le contrat lui-même ».
Le gain est immédiat : tu peux migrer ton implémentation sans re-mapper ton produit. Tu changes le moteur, pas la voiture.
<?php
interface BillingGateway
{
public function createCustomer(array $data): string; // returns provider_customer_id
public function startSubscription(string $providerCustomerId, string $planKey): array;
public function cancelSubscription(string $providerSubscriptionId, bool $atPeriodEnd = true): void;
public function syncSubscriptionFromWebhook(array $event): void;
}
// Dans un service provider Laravel :
// app()->bind(BillingGateway::class, StripeBillingGateway::class);
Ce genre d’interface paraît « académique », mais en pratique ça te force à rendre explicite ce qui était implicite. Et tu peux migrer en implémentant un nouveau gateway, tout en laissant l’ancien vivre quelques semaines derrière un feature flag.
Alternatives crédibles à Spark en 2026 : Cashier (Stripe/Paddle), portail provider, ou minimalisme assumé
Quand tu sors de Spark, tu as trois directions réalistes. Et le bon choix dépend surtout de ton niveau d’exigence produit sur la facturation, pas de ton amour de l’open source.
La voie la plus naturelle dans un SaaS Laravel, c’est Laravel Cashier. Cashier te donne une intégration propre avec Stripe (et une variante pour Paddle). Ce n’est pas Spark. Tu n’as pas tout le « SaaS starter kit » autour. Mais pour beaucoup de produits, c’est exactement ce qu’on veut : un socle billing maintenu, et le reste chez toi.
La deuxième voie, souvent sous-estimée, c’est d’assumer que le provider fait très bien une partie du boulot. Si ton produit peut vivre avec le Customer Portal Stripe (ou l’équivalent), et si tu standardises tes flows, tu peux réduire énormément la surface de code. Moins de UI, moins de dette. Par contre, tu acceptes aussi moins de personnalisation.
La troisième voie, c’est l’implémentation maison minimaliste. Je dis bien minimaliste. Pas « on recode Stripe ». Mais « on écrit notre couche fine autour des APIs et des webhooks, et on accepte la responsabilité ». Ça peut être la meilleure option si tu as des besoins atypiques, ou si tu veux un contrôle total. Ça peut aussi être un piège si ton équipe n’a pas le temps de maintenir ça sur la durée. Le billing, c’est un métier. Quand tu le fais toi-même, tu le prends avec.
Et oui, il existe des forks ou des alternatives « Spark-like ». Mon avis : fais attention. Tu remplaces une dépendance incertaine par une autre. Si ton objectif est de réduire le risque, tu veux soit une brique très standard (Cashier + provider), soit ton propre code avec un périmètre maîtrisé.
Plan de migration progressive : le chemin qui évite le big bang
La migration qui se passe bien, c’est celle où tu n’as jamais un moment « on bascule tout ». Tu ajoutes des rails, tu mets des capteurs, tu fais cohabiter, tu coupes quand tu es sûr. En billing, c’est encore plus vrai : tu veux éviter la surprise d’un abonnement qui se retrouve « active » côté app mais « unpaid » côté Stripe.
Je commence généralement par sortir le produit de Spark avant de sortir la tech. Concrètement, tu introduis ton contrat billing interne, et tu fais en sorte que ton app ne lise plus directement des champs Spark pour décider d’un accès, d’un quota, d’un écran. Tu centralises l’autorisation de features derrière un service à toi. Même si, derrière, tu continues de remplir ce service avec les données Spark au début.
Ensuite tu mets en place une stratégie de “double-write” contrôlée sur les opérations critiques, typiquement la création d’abonnement et l’annulation. L’idée n’est pas de tout écrire deux fois pendant six mois. L’idée, c’est d’avoir une période courte où tu peux comparer, détecter un écart, et rollback. Là où les gens se ratent, c’est quand le double-write n’est pas observé. Si tu écris deux fois mais que personne ne compare, tu ne découvres les divergences qu’avec des clients énervés.
Après ça, tu fais un backfill : tu reconstruis ton modèle interne à partir de la vérité provider (Stripe/Paddle) et tu compares à la DB historique. Tu identifies les cas qui ne collent pas (abonnements annulés mais encore actifs, périodes de grâce, comptes “lifetime”, coupons historiques). Ce sont ces cas qui feront bugger ta bascule si tu les ignores.
La dernière étape, c’est la bascule du temps réel : webhooks, renouvellements, paiements échoués, retry. Tu mets le nouveau handler en place, tu le fais tourner en « shadow » si possible, tu logges tout, et tu ne coupes l’ancien que quand tu as vu passer assez d’événements variés.
Les pièges qui te font perdre du temps (ou de l’argent) : webhooks, prorata, emails, TVA
Le sujet n°1, c’est l’idempotence des webhooks. C’est toujours la même histoire : tu traites un événement, ça timeout, Stripe retry, et tu appliques deux fois la même action. Ou tu reçois des événements dans un ordre “pas logique”. Si tu n’as pas une clé d’idempotence et une table de « processed events », tu joues à la roulette russe.
<?php
public function handleStripeWebhook(array $payload): void
{
$eventId = $payload['id'] ?? null;
if (!$eventId) {
abort(400);
}
// Table: webhook_events(event_id, provider, processed_at)
$already = WebhookEvent::where('provider', 'stripe')
->where('event_id', $eventId)
->exists();
if ($already) {
return; // idempotent: safe retry
}
DB::transaction(function () use ($payload, $eventId) {
// 1) apply state changes based on the event
$this->billingGateway->syncSubscriptionFromWebhook($payload);
// 2) mark as processed
WebhookEvent::create([
'provider' => 'stripe',
'event_id' => $eventId,
'processed_at' => now(),
]);
});
}
Le deuxième piège, c’est le prorata et les changements de plan. Selon ton produit, tu peux soit choisir une politique simple (les changements prennent effet au prochain cycle), soit gérer du prorata au centime. Les deux se défendent, mais il faut choisir. Beaucoup de migrations se plantent parce que l’ancienne logique était un mix implicite Spark + Stripe settings, et que personne n’a documenté ce qui était attendu.
Troisième piège : les emails. Entre les emails provider (paiement échoué, carte expirée), les emails app (confirmation d’abonnement, facture disponible), et parfois des emails Spark, tu peux très vite spammer ou, pire, ne plus notifier. La bonne approche, c’est de décider clairement qui envoie quoi, et de tester sur un vrai scénario « carte refusée → retry → paiement ok ».
Et oui, la TVA et la facturation légale. Si tu vends en B2B/B2C en Europe, tu sais déjà que ce n’est pas un détail. Une migration peut modifier la génération des factures, leur numérotation, la gestion des adresses, des numéros de TVA intracom, et tes exports comptables. Tu n’as pas besoin d’un roman, mais tu as besoin d’un test de bout en bout avec ton process réel (compta, outils, exports, archivage).
Mon avis (tranché) : le billing, ça se sort avant d’être coincé par un upgrade Laravel
Attendre « le bon moment » est la stratégie la plus répandue. Et souvent la pire. Le bon moment n’arrive pas, ou arrive sous forme d’incident, d’un upgrade imposé, ou d’un besoin business urgent. Si ton SaaS dépend de Spark, tu n’es pas obligé de migrer demain. Par contre, tu devrais préparer la migration maintenant, en réduisant la surface de dépendance et en mettant ton contrat billing au centre.
Une fois que ton application sait vivre avec son propre modèle (et que Spark n’est plus qu’un adaptateur), tu peux respirer. Tu peux choisir Cashier, Stripe Portal, Paddle, ou du sur-mesure. Et tu peux le faire sans tout casser, parce que le produit ne dépend plus d’un starter kit.
Conclusion : vise une sortie “ennuyeuse”, pas une migration héroïque
La meilleure migration billing, c’est celle qui ne se voit pas. Pas de grand soir. Pas de threads Slack à 2h du mat. Juste une série de petits changements qui déplacent la dépendance, ajoutent de l’observabilité, et rendent la bascule réversible.
Si tu veux une suite logique, le prochain chantier à cadrer, c’est l’observabilité de ton billing : quels événements tu traces, quels écarts tu alertes, quels dashboards tu regardes chaque semaine. Quand tu vois venir les divergences avant tes clients, tu as gagné.