Next.js SaaS
Créer des abonnements Stripe dans Next.js : Checkout, webhooks et état de facturation
Créez la facturation d’abonnements Stripe dans Next.js avec sessions Checkout créées sur le serveur, webhooks vérifiés, droits locaux et traitement idempotent.
Le retour de Checkout relève de l’expérience utilisateur. Les webhooks vérifiés permettent de rapprocher fiablement l’état de facturation asynchrone.
Gérer un modèle local de facturation
Stockez les identifiants client et abonnement du prestataire sur le compte propriétaire, puis normalisez l’offre, le statut, la période actuelle, l’état d’annulation et les droits nécessaires. L’objet du prestataire ne remplace pas un modèle d’accès propre au produit.
Décidez comment trialing, active, past_due, canceled et incomplete affectent l’application. Gardez l’historique et les décisions d’accès explicables. Un libellé tarifaire doit correspondre à un identifiant de prix configuré sur le serveur, pas à un prix arbitraire envoyé par le navigateur.
Créer les sessions Checkout sur le serveur
Authentifiez le compte, validez l’offre demandée avec une liste autorisée, créez ou réutilisez son Stripe Customer, puis créez une session Checkout en mode abonnement. Ajoutez l’identifiant stable du compte aux métadonnées pour rapprocher les événements ultérieurs sans faire confiance à un e-mail.
Renvoyez l’URL du prestataire ou redirigez vers elle. N’exposez jamais la clé secrète au navigateur. Utilisez une clé d’idempotence si une requête applicative répétée ne doit pas créer d’opérations en double chez le prestataire.
"use server";
export async function startCheckout(plan: PlanId) {
const account = await requireBillingAdmin();
const price = PRICE_IDS[plan];
if (!price) return { error: "Unknown plan" };
const session = await stripe.checkout.sessions.create({
mode: "subscription",
customer: account.stripeCustomerId,
line_items: [{ price, quantity: 1 }],
success_url: SITE_URL + "/settings/billing?checkout=complete",
cancel_url: SITE_URL + "/pricing",
metadata: { accountId: account.id },
});
redirect(session.url);
}Traiter la page de retour comme en attente
Le client peut atteindre l’URL de succès avant le traitement de tous les événements asynchrones, et copier cette URL ne prouve pas le paiement. Affichez un état de confirmation, puis lisez l’abonnement local et actualisez-le après le webhook.
N’accordez pas d’accès permanent depuis un paramètre ou une réponse du prestataire côté client. Les droits doivent suivre un état serveur vérifié. Proposez une nouvelle tentative claire ou un recours à l’assistance si la facturation reste incomplète.
Vérifier la requête webhook brute
Un Route Handler Next.js peut lire le corps intact avec request.text et la signature dans request.headers. Transmettez le corps brut, la valeur Stripe-Signature et le secret de l’endpoint à la bibliothèque officielle. Analyser le JSON d’abord modifie le corps utilisé pour la vérification et peut faire échouer les signatures.
Rejetez les signatures invalides avant toute opération. Séparez les secrets des endpoints de test et de production. Renvoyez rapidement un succès après l’enregistrement du travail accepté ; les e-mails longs ou synchronisations doivent devenir des tâches.
export async function POST(request: Request) {
const payload = await request.text();
const signature = request.headers.get("stripe-signature");
const event = stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET,
);
await recordBillingEvent(event);
return new Response(null, { status: 200 });
}Traiter les événements de façon idempotente
Les webhooks peuvent se répéter et leur ordre n’est pas garanti. Insérez l’identifiant d’événement du prestataire avec une contrainte d’unicité avant les effets de bord. S’il existe déjà, acquittez-le sans envoyer un nouvel e-mail ni appliquer deux fois le changement.
Pour les mises à jour et suppressions d’abonnement, récupérez ou déduisez l’état actuel faisant foi du prestataire si l’ordre des événements peut rendre l’enregistrement local obsolète. Enregistrez l’événement et mettez l’état de facturation à jour dans une transaction si possible.
- —Enregistrez l'identifiant et le type de l'événement.
- —Associez-le au compte local.
- —Appliquez une seule fois l’état d’abonnement normalisé.
- —Mettez les tâches de suivi lentes en file après les changements durables d’état.
Gérer le cycle de vie complet de l'abonnement
Prenez en charge la création de clients et d’abonnements, changements d’offre, renouvellements, échecs de paiement, annulations planifiées ou immédiates et suppressions. Utilisez le portail client s’il convient au produit, plutôt que reconstruire sans raison la gestion des moyens de paiement et factures.
L’autorisation reste nécessaire : seul le rôle approprié peut lancer le paiement ou ouvrir la gestion de facturation. Notez qui a demandé le changement d’offre et sa date d’effet pour que l’assistance puisse expliquer l’état du compte.
Tester les nouvelles tentatives et les échecs
Utilisez la CLI Stripe ou une destination d’événement de test pour transmettre les événements signés. Répétez un événement, livrez une ancienne mise à jour après une récente, utilisez un mauvais secret, interrompez la base et provoquez un paiement échoué. Confirmez que les accès et l’état local restent explicables.
Testez la compilation de production avec les identifiants du mode test avant d’activer l’endpoint réel. Séparez les identifiants réels et de test et empêchez un aperçu de s’enregistrer accidentellement comme destination de production.
Héberger la facturation sur une version HTTPS stable
Stripe exige en production un endpoint webhook HTTPS accessible publiquement. Adios fournit l’environnement Next.js persistant, la route HTTPS générée, les domaines personnalisés, le TLS géré, les références aux secrets, les contrôles de santé et les journaux nécessaires à son exploitation avec l’interface SaaS.
Déployez et vérifiez Checkout en mode test, examinez les erreurs de signature sans journaliser les secrets reçus, puis promouvez la version opérationnelle. Enregistrez l’URL stable du webhook de production, pas un aperçu jetable. Une future candidate échouant à la compilation ou au contrôle de santé n’a pas à remplacer l’endpoint de facturation fonctionnel.
env:
STRIPE_SECRET_KEY: secret://STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET: secret://STRIPE_WEBHOOK_SECRET
DATABASE_URL: secret://DATABASE_URL
runtime:
name: node@24
port: 3000
health_path: /api/health