Stripe est la solution idéale pour la facturation de votre SaaS. Voici comment l'intégrer.
Prérequis
- Compte Stripe
- Projet Next.js
- Base de données
- NeuraAPI (optionnel)
Étape 1 : Setup Stripe
Installez le package Stripe et configurez vos clés API.
npm install stripe @stripe/stripe-jsCréez vos produits et prix dans le dashboard Stripe. .env.local:
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
NEXT_PUBLIC_APP_URL=http://localhost:3000Étape 2 : Produits et prix
Configurez les plans d'abonnement avec les prix.
- Starter — 19€/mois, 1 000 Crédits
- Pro — 49€/mois, 5 000 Crédits
- Entreprise — 99€/mois, 20 000 Crédits
Définissez les fonctionnalités de chaque plan. price_id Configurez les cycles de facturation.
Étape 3 : Webhooks
Les webhooks synchronisent les données entre Stripe et votre base.
// app/api/stripe/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export const PLANS = {
starter: {
name: 'Starter',
priceId: 'price_starter_monthly',
credits: 1000,
},
pro: {
name: 'Pro',
priceId: 'price_pro_monthly',
credits: 5000,
},
enterprise: {
name: 'Entreprise',
priceId: 'price_enterprise_monthly',
credits: 20000,
},
} as const
export async function POST(req: NextRequest) {
const session = await auth()
if (!session?.user) {
return NextResponse.json(
{ error: 'Non autorisé' },
{ status: 401 }
)
}
const { plan } = await req.json()
const selectedPlan = PLANS[plan as keyof typeof PLANS]
if (!selectedPlan) {
return NextResponse.json(
{ error: 'Plan invalide' },
{ status: 400 }
)
}
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
customer_email: session.user.email!,
line_items: [
{
price: selectedPlan.priceId,
quantity: 1,
},
],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?canceled=true`,
metadata: {
userId: session.user.id,
plan: plan,
},
})
return NextResponse.json({ url: checkoutSession.url })
}Configurez les événements à écouter. metadata Implémentez les handlers pour chaque événement.
Étape 4 : Checkout
Le Checkout Stripe gère le paiement de manière sécurisée.
// app/api/stripe/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
import { prisma } from '@/lib/db'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(req: NextRequest) {
const body = await req.text()
const sig = req.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
)
} catch (err) {
console.error('Webhook signature verification failed:', err)
return NextResponse.json(
{ error: 'Signature invalide' },
{ status: 400 }
)
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
const userId = session.metadata?.userId
const plan = session.metadata?.plan
if (userId && plan) {
await prisma.user.update({
where: { id: userId },
data: {
plan,
stripeCustomerId: session.customer as string,
},
})
await prisma.subscription.create({
data: {
userId,
stripeSubscriptionId: session.subscription as string,
status: 'active',
plan,
currentPeriodEnd: new Date(
(session.subscription as { current_period_end: number }).current_period_end * 1000
),
},
})
}
break
}
case 'customer.subscription.updated': {
const subscription = event.data.object as Stripe.Subscription
const status = subscription.status
await prisma.subscription.updateMany({
where: {
stripeSubscriptionId: subscription.id,
},
data: {
status,
currentPeriodEnd: new Date(
subscription.current_period_end * 1000
),
},
})
break
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription
await prisma.subscription.updateMany({
where: {
stripeSubscriptionId: subscription.id,
},
data: { status: 'canceled' },
})
// Downgrade l'utilisateur au plan gratuit
const sub = await prisma.subscription.findUnique({
where: { stripeSubscriptionId: subscription.id },
})
if (sub) {
await prisma.user.update({
where: { id: sub.userId },
data: { plan: 'free' },
})
}
break
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice
// Envoyer un email d'alerte à l'utilisateur
console.error('Paiement échoué pour:', invoice.customer)
break
}
}
return NextResponse.json({ received: true })
}Redirigez vers la page de checkout Stripe.
Étape 5 : Portal client
Le Portal client permet aux utilisateurs de gérer leur abonnement.
# Installer Stripe CLI
npm i -g stripe
# Se connecter à votre compte Stripe
stripe login
# Forwarder les webhooks vers votre serveur local
stripe listen --forward-to localhost:3000/api/stripe/webhook
# Dans un autre terminal, déclencher des événements de test
stripe trigger checkout.session.completed
stripe trigger customer.subscription.deletedLes utilisateurs peuvent changer de plan, ajouter une carte ou annuler. webhook signing secret Gérez les annulations et les remboursements. STRIPE_WEBHOOK_SECRET Testez le flux complet en mode test.
Étape 6 : Gestion des abonnements
Gérez les abonnements actifs, en pause et annulés.
// app/api/stripe/portal/route.ts
import { NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST() {
const session = await auth()
if (!session?.user) {
return NextResponse.json(
{ error: 'Non autorisé' },
{ status: 401 }
)
}
const portalSession = await stripe.billingPortal.sessions.create({
customer: session.user.stripeCustomerId!,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
})
return NextResponse.json({ url: portalSession.url })
}Synchronisez les données avec votre base.
Étape 7 : Production
Passez en mode production et testez avec de vrais paiements.
// components/SubscriptionStatus.tsx
'use client'
import { useState } from 'react'
interface Props {
plan: string
status: string
currentPeriodEnd: string
}
export function SubscriptionStatus({ plan, status, currentPeriodEnd }: Props) {
const [loading, setLoading] = useState(false)
const handleManage = async () => {
setLoading(true)
const res = await fetch('/api/stripe/portal', { method: 'POST' })
const data = await res.json()
if (data.url) {
window.location.href = data.url
}
setLoading(false)
}
return (
<div className="rounded-xl border border-indigo-800/50 bg-indigo-900/30 p-6">
<h3 className="text-lg font-semibold text-white">Abonnement</h3>
<div className="mt-4 space-y-2">
<p className="text-indigo-200">
Plan : <span className="font-semibold text-white capitalize">{plan}</span>
</p>
<p className="text-indigo-200">
Statut : <span className={status === 'active' ? 'text-green-400' : 'text-red-400'}>{status === 'active' ? 'Actif' : 'Inactif'}</span>
</p>
<p className="text-sm text-indigo-400">
Renouvellement le {new Date(currentPeriodEnd).toLocaleDateString('fr-FR')}
</p>
</div>
<button
onClick={handleManage}
disabled={loading}
className="mt-6 rounded-lg border border-indigo-500 px-4 py-2 text-sm font-semibold text-indigo-200 hover:bg-indigo-900/50 transition-all disabled:opacity-50"
>
{loading ? 'Chargement...' : 'Gérer l'abonnement'}
</button>
</div>
)
}Bonnes pratiques
Configurez les webhooks correctement
Les webhooks sont essentiels pour synchroniser les données entre Stripe et votre base. constructEvent() Écoutez les événements importants : checkout.session.completed, customer.subscription.updated.
Utilisez le mode test
Testez tous les flux de paiement avant de passer en production. updateMany Stripe fournit des numéros de carte de test pour valider votre intégration.
Gérez les erreurs de paiement
Prévoyez des messages d'erreur clairs pour vos utilisateurs.
Documentez votre intégration
Créez une documentation interne pour faciliter la maintenance.
Récapitulatif
Configurer Stripe
Commencez à facturer vos utilisateurs dès maintenant. NeuraSaaS Créez votre compte et configurez Stripe en quelques minutes.
Voir les tarifs