Facturation
ARRÊT, prérequis. La facturation doit être activée avant que
<PricingTable />,<CheckoutButton />,has({ plan }), ouhas({ feature })fonctionnent. Deux chemins : (1) Dashboard → Billing → Settings, ou (2)clerk enable billing(voir « Agent-first : Programmatic billing config » ci-dessous). L'activation crée automatiquement les plans par défautfree_user/free_org. Les instances de développement peuvent utiliser la passerelle Clerk de développement partagée (aucun compte Stripe requis) ; la production nécessite un compte Stripe pour le traitement des paiements uniquement.Note : Les APIs de facturation sont encore expérimentales. Épinglez les versions de vos packages
@clerk/nextjsetclerk-js. Voir le skillclerkpour le tableau des versions supportées.
Démarrage rapide
-
Activez la facturation, via Dashboard → Billing → Settings ou
clerk enable billing(voir section Agent-first). Sauter cette étape génèrecannot_render_billing_disableden dev et rend vide en production. -
Créez des plans dans l'onglet correspondant, Dashboard → Billing → Plans. Deux onglets, les slugs sont limités par onglet, non déplaçables après création :
- User Plans →
<PricingTable />(par défautfor="user") - Organization Plans →
<PricingTable for="organization" />
Un mauvais onglet est la cause #1 d'un
<PricingTable />vide. Les plans vivent dans Clerk ; ils ne sont pas synchronisés avec Stripe. - User Plans →
-
Ajoutez des fonctionnalités à l'intérieur d'un plan, ouvrez le plan dans Dashboard → Billing → Plans, utilisez sa section Features. Les fonctionnalités sont limitées par plan, pas globales. Le même slug peut s'attacher à plusieurs plans ;
has({ feature: 'export' })correspond si le plan actif contient ce slug. -
Rendez
<PricingTable />(passezfor="organization"pour B2B). -
Limitez l'accès avec
has({ plan })ouhas({ feature })depuisauth(). -
Gérez les webhooks de facturation pour le cycle de vie des abonnements.
Raccourcis du Dashboard
| Action | URL |
|---|---|
| Activer la facturation | https://dashboard.clerk.com/last-active?path=billing/settings |
| Créer / modifier les plans | https://dashboard.clerk.com/last-active?path=billing/plans |
| Mode adhésion (coexistence B2C + B2B) | https://dashboard.clerk.com/last-active?path=organizations-settings |
| Modifier les fonctionnalités | Plans → cliquez sur un plan → section Features (pas d'URL directe) |
Agent-first : Configuration programmatique de la facturation
La configuration complète de la facturation (bascules d'activation, plans, fonctionnalités, attachements plan-fonctionnalité) est modifiable via PLAPI sans toucher au Dashboard. Utile pour les agents qui créent des plans, répliquent la configuration entre instances, ou versionnent la structure de facturation.
Prérequis : projet lié à l'application Clerk (clerk auth login + clerk link, voir clerk-cli). La facturation est au niveau du compte uniquement — contrairement aux orgs, elle ne peut pas être activée sur une application non réclamée ; réclamez d'abord l'application.
Activer la facturation via CLI
clerk enable billing # les deux cibles (par défaut, crée automatiquement free_user + free_org)
clerk enable billing --for org # org uniquement
clerk enable billing --for user # user uniquement
Extraire la configuration de facturation actuelle
clerk config pull --keys billing > billing.json
Cela écrit la configuration de facturation actuelle (bascules + plans + fonctionnalités) pour l'instance liée dans billing.json.
Modifier et appliquer
Modifiez billing.json pour ajouter/supprimer des plans ou des fonctionnalités, puis prévisualisez le diff et appliquez :
clerk config patch --file billing.json --dry-run
clerk config patch --file billing.json
Passez --instance prod pour cibler l'instance de production à la place du dev.
PATCH brut (contrôle total)
Pour les mises à jour ponctuelles de plan/fonctionnalité sans fichier de configuration :
clerk api --platform PATCH /v1/platform/applications/<app_id>/instances/<ins_id>/config \
-d '{"billing":{"plans":[{"slug":"pro","name":"Pro","amount":2000,"currency":"usd","payer_type":"user","is_recurring":true}],"features":[{"slug":"export","name":"Export"}]}}'
Notes
- Cela gère la configuration de facturation (bascules + catalogue de plans + fonctionnalités). Le cycle de vie des abonnements (utilisateurs choisissant un plan, checkout, renouvellement, annulation) circule toujours via
<PricingTable />+ webhooks de facturation, voir le skillclerk-webhookspour les événements du cycle de vie. - La manipulation de la carte
featuresde niveau supérieur et les attachements plan-fonctionnalité (sync) sont entièrement supportés via le gestionnaire de configuration PLAPI de facturation.
De quoi avez-vous besoin ?
| Tâche | Référence |
|---|---|
Props <PricingTable />, <CheckoutButton />, modèles de facturation <Show> |
references/billing-components.md |
Modèles B2C (abonnements utilisateur individuels, prérequis Membership optional) |
references/b2c-patterns.md |
| Modèles B2B (abonnements org, plans à limite de sièges, UI de facturation gated-admin) | references/b2b-patterns.md |
| Catalogue d'événements webhook, formes de payload, modèles de gestionnaire | references/billing-webhooks.md |
Références
| Référence | Description |
|---|---|
references/billing-components.md |
<PricingTable /> et UI d'abonnement |
references/b2c-patterns.md |
Modèles de facturation d'abonnement B2C |
references/b2b-patterns.md |
Facturation B2B avec abonnements d'organisation et plans à limite de sièges |
references/billing-webhooks.md |
Gestion des événements du cycle de vie des abonnements |
Documentation
Fonctionnalités vs Plans : Quand utiliser lequel
Utilisez has({ feature: 'slug' }) pour limiter une capacité spécifique, export, analytics, accès API, logs d'audit.
Utilisez has({ plan: 'slug' }) pour limiter un niveau, afficher le dashboard pro, vérifier le niveau d'abonnement org, rediriger les utilisateurs gratuits.
| Scénario | Vérification correcte |
|---|---|
| Limiter le bouton « Export CSV » | has({ feature: 'export' }) |
| Limiter la section « Analytics » | has({ feature: 'analytics' }) |
| Limiter tout /dashboard/pro | has({ plan: 'pro' }) |
| Vérifier si org a un abonnement team | has({ plan: 'org:team' }) |
| Limiter la configuration SSO | has({ feature: 'sso' }) |
Quand un utilisateur dit « limiter la fonctionnalité d'export » ou « limiter les analytics », utilisez toujours has({ feature }). Utilisez uniquement has({ plan }) quand la limite est le niveau de plan lui-même, pas une capacité spécifique à l'intérieur.
Modèles clés
1. Rendre le tableau de prix
Afficher les plans disponibles aux utilisateurs avec un seul composant :
import { PricingTable } from '@clerk/nextjs'
export default function PricingPage() {
return (
<main>
<h1>Choose a plan</h1>
<PricingTable />
</main>
)
}
<PricingTable /> rend automatiquement tous les plans configurés dans le Clerk Dashboard. Sélectionner un plan ouvre le tiroir de checkout in-app de Clerk. Aucune prop requise pour l'utilisation basique. Pour B2B, passez for="organization" pour rendre les plans au niveau org au lieu des plans utilisateur.
2. Vérifier les droits de fonctionnalité (côté serveur)
Limitez par fonctionnalités individuelles, c'est l'approche préférée pour les capacités spécifiques :
import { auth } from '@clerk/nextjs/server'
export default async function AnalyticsPage() {
const { has } = await auth()
const canViewAnalytics = has({ feature: 'analytics' })
const canExport = has({ feature: 'export' })
return (
<div>
{canViewAnalytics && <AnalyticsChart />}
{canExport && <ExportButton />}
</div>
)
}
Les fonctionnalités sont configurées dans Clerk Dashboard → Billing → Features et assignées aux plans. Utilisez has({ feature }) au lieu de has({ plan }) quand vous limitez les capacités granulaires, vérifiez la fonctionnalité, pas le plan.
3. Vérifier les droits de fonctionnalité (côté client)
Utilisez useAuth() pour la limitation de fonctionnalité côté client. Combinez avec les vérifications côté serveur pour une couverture complète :
'use client'
import { useAuth } from '@clerk/nextjs'
export function FeatureGatedUI() {
const { has, isLoaded } = useAuth()
if (!isLoaded) return null
const canExport = has?.({ feature: 'export' })
const canAnalytics = has?.({ feature: 'analytics' })
return (
<div>
{canAnalytics && <AnalyticsSection />}
{canExport ? <ExportButton /> : <UpgradeToExport />}
</div>
)
}
Les Server Components utilisent auth(), les Client Components utilisent useAuth(). Les deux supportent has({ feature }) et has({ plan }).
4. Vérifier le plan d'abonnement côté serveur
Limitez l'accès par plan d'abonnement (utilisez ceci pour les portes au niveau du niveau, pas les fonctionnalités individuelles) :
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function ProDashboard() {
const { has } = await auth()
if (!has({ plan: 'pro' })) {
redirect('/pricing')
}
return <ProFeatures />
}
5. Vérifications de plan côté client
Utilisez le hook useAuth() pour les composants client :
'use client'
import { useAuth } from '@clerk/nextjs'
export function UpgradePrompt() {
const { has } = useAuth()
if (has?.({ plan: 'pro' })) {
return null
}
return (
<div>
<p>Upgrade to Pro to access this feature</p>
<a href="/pricing">View plans</a>
</div>
)
}
6. Facturation B2B basée sur les sièges avec les organisations
Les plans org peuvent avoir une limite de sièges (plafond d'adhésion) que Clerk applique au moment de l'invitation. Utilisez le préfixe slug org: sur les vérifications de plan côté org (par ex. has({ plan: 'org:team' })) pour garder la limitation sans ambiguïté. Rendez la page de prix B2B avec <PricingTable for="organization" />, et utilisez <OrganizationProfile /> pour l'UI de facturation du compte org.
Voir references/b2b-patterns.md pour la nomenclature des plans hiérarchisés, les invariants de limite de sièges, la facturation réservée à l'admin, et les gestionnaires webhook.
7. Afficher l'état de l'abonnement
Vérifiez des plans spécifiques avec has({ plan }), ou utilisez useSubscription() pour les détails complets de l'abonnement dans les composants client. Ne lisez pas directement les informations de plan depuis sessionClaims, ce n'est pas le chemin supporté.
Composant serveur, vérifier des plans spécifiques :
import { auth } from '@clerk/nextjs/server'
export default async function AccountPage() {
const { has } = await auth()
const currentPlan = has({ plan: 'pro' })
? 'pro'
: has({ plan: 'starter' })
? 'starter'
: 'free'
return (
<div>
<h2>Current Plan</h2>
<p>You are on the {currentPlan} plan</p>
{currentPlan === 'free' && <a href="/pricing">Upgrade</a>}
</div>
)
}
Composant client, détails complets de l'abonnement via useSubscription() :
'use client'
import { useSubscription } from '@clerk/nextjs/experimental'
export function SubscriptionDetails() {
const { data: subscription, isLoading } = useSubscription()
if (isLoading) return null
if (!subscription) return <a href="/pricing">Choose a plan</a>
return (
<div>
<p>Status: {subscription.status}</p>
{subscription.nextPayment && (
<p>Next payment: {subscription.nextPayment.date.toLocaleDateString()}</p>
)}
</div>
)
}
useSubscription()est pour l'affichage uniquement. Pour les vérifications d'autorisation (limiter le contenu ou les routes), utilisez toujourshas({ plan })ouhas({ feature }).
8. Protéger les routes API par plan
Limitez les routes API en utilisant auth() :
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
export async function GET() {
const { has } = await auth()
if (!has({ plan: 'pro' })) {
return NextResponse.json({ error: 'Pro plan required' }, { status: 403 })
}
return NextResponse.json({ data: 'premium data' })
}
9. Gérer les webhooks de facturation
Les noms d'événements Clerk diffèrent des noms d'événements Stripe. Les webhooks de facturation Clerk utilisent la notation pointée et camelCase, pas le format underscore de Stripe.
Il n'y a pas d'événement
subscription.canceled. L'annulation se déclenche au niveau de l'élément sous la formesubscriptionItem.canceled.
Intention Nom d'événement Stripe Nom d'événement Clerk Abonnement créé customer.subscription.createdsubscription.createdAbonnement mis à jour customer.subscription.updatedsubscription.updatedAbonnement actif (aucun) subscription.activeAbonnement en retard (aucun) subscription.pastDueÉlément d'abonnement annulé customer.subscription.deletedsubscriptionItem.canceledÉlément d'abonnement en retard invoice.payment_failedsubscriptionItem.pastDueÉlément d'abonnement mis à jour (aucun) subscriptionItem.updatedÉlément d'abonnement actif (aucun) subscriptionItem.activeRenouvellement à venir de l'élément d'abonnement (aucun) subscriptionItem.upcomingÉlément d'abonnement terminé (aucun) subscriptionItem.endedÉlément d'abonnement abandonné (aucun) subscriptionItem.abandonedÉlément d'abonnement expiré (aucun) subscriptionItem.expiredÉlément d'abonnement incomplet (aucun) subscriptionItem.incompleteEssai gratuit se terminant bientôt (aucun) subscriptionItem.freeTrialEndingTentative de paiement créée (aucun) paymentAttempt.createdTentative de paiement mise à jour (aucun) paymentAttempt.updatedUtilisez toujours les noms d'événements Clerk, jamais ceux de Stripe, dans les vérifications
evt.type.
Forme du payload. Les payloads de webhook de facturation Clerk sont imbriqués. L'entité abonnée se trouve sous
evt.data.payer(champs :user_id?,organization_id?). Les informations du plan sont sur chaque élément sousevt.data.items[i].plan.slug. L'ID d'abonnement est simplementevt.data.id. Les éléments d'abonnement ne portent pas de champ de référence arrièresubscription_id, donc dans les gestionnairessubscriptionItem.*vous identifiez l'enregistrement par l'ID d'élément (evt.data.id) ou cherchez par payeur plus plan.
Gestionnaire minimal pour ancrer le modèle (importer depuis @clerk/nextjs/webhooks, vérifier, brancher sur le nom d'événement Clerk) :
import { verifyWebhook } from '@clerk/nextjs/webhooks'
import { NextRequest } from 'next/server'
import { db } from '@/lib/db'
export async function POST(req: NextRequest) {
let evt
try {
evt = await verifyWebhook(req)
} catch {
return new Response('Verification failed', { status: 400 })
}
if (evt.type === 'subscription.created') {
const { id, payer, items, status } = evt.data
const entityId = payer.organization_id ?? payer.user_id
const plan = items[0]?.plan?.slug
await db.subscriptions.upsert({
where: { subscriptionId: id },
create: { subscriptionId: id, entityId, plan, status },
update: { entityId, plan, status },
})
}
// Ajoutez plus de branches selon le catalogue d'événements ci-dessus (subscription.updated,
// subscriptionItem.canceled, subscriptionItem.pastDue, etc.)
return new Response('OK', { status: 200 })
}
Pour le modèle complet couvrant les 15 événements, les déclarations de type TS depuis @clerk/backend, la configuration de route publique proxy.ts, et la table des valeurs de statut d'abonnement, voir references/billing-webhooks.md.
10. Flux de mise à niveau / rétrogradation
Laissez les utilisateurs gérer leur abonnement depuis l'intérieur de l'app :
import { PricingTable } from '@clerk/nextjs'
import { auth } from '@clerk/nextjs/server'
export default async function BillingPage() {
const { has } = await auth()
const isPro = has({ plan: 'pro' })
return (
<div>
<h1>Billing</h1>
{isPro ? (
<div>
<p>You are on the Pro plan</p>
<PricingTable />
</div>
) : (
<div>
<p>Upgrade to access premium features</p>
<PricingTable />
</div>
)}
</div>
)
}
<PricingTable /> rend différemment pour les utilisateurs abonnés, il affiche le plan actuel et permet les mises à niveau ou les annulations, tout via le tiroir de checkout in-app de Clerk.
Nomenclature des plans et fonctionnalités
Les slugs de plan et les slugs de fonctionnalité sont définis dans Clerk Dashboard → Billing. Conventions courantes :
| Niveau | Slug de plan | Fonctionnalités d'exemple |
|---|---|---|
| Gratuit | (aucune vérification de plan requise) | fonctionnalités basiques |
| Starter | starter |
analytics, api_access |
| Pro | pro |
analytics, export, team |
| Enterprise | enterprise |
toutes les fonctionnalités + sso, audit_logs |
Utilisez les slugs en minuscules correspondant à ce que vous définissez dans le dashboard.
Facturation B2B vs B2C
| Scénario | Qui s'abonne | Vérification de plan |
|---|---|---|
| B2C SaaS | Utilisateur individuel | has({ plan: 'pro' }) sur la session utilisateur |
| B2B SaaS | Organisation | has({ plan: 'org:team' }) sur la session org |
| B2B limité en sièges | Organisation | Le plan a un plafond de sièges ; les prix sont par plan, pas par membre, échelonnez vos plans pour les plus grandes orgs |
Pour B2B, assurez-vous que l'utilisateur a une session org active. La vérification has() évalue l'entité active (utilisateur ou org).
Flux de checkout
Clerk rend son propre tiroir de checkout automatiquement via <PricingTable /> et <CheckoutButton />. Les plans et les prix vivent dans Clerk. Pour déclencher le checkout à partir d'une server action, redirigez vers une page qui rend <PricingTable /> :
'use server'
import { redirect } from 'next/navigation'
export async function upgradeAction() {
redirect('/pricing')
}
Signatures d'erreur (diagnostiquez rapidement)
Quand vous voyez l'une de ces erreurs ou symptômes, la correction est presque toujours une bascule du Dashboard, pas un changement de code. Ne commencez pas à éditer des composants.
| Erreur / symptôme | Cause racine | Correction |
|---|---|---|
Clerk: 🔒 The <PricingTable/> component cannot be rendered when billing is disabled. (code : cannot_render_billing_disabled, dev uniquement) |
La facturation n'est pas activée pour cette instance | Activez la facturation à dashboard.clerk.com → Billing → Settings, ou exécutez clerk enable billing. |
<PricingTable /> rend vide |
Aucun plan, OU plan dans le mauvais onglet (User vs Organization), OU Facturation non activée | Créez un plan dans l'onglet correspondant ; passez for="organization" pour B2B ; vérifiez Billing Settings |
| Les utilisateurs ne peuvent pas s'abonner à un plan personnel sur une app B2C + B2B | Mode d'adhésion requis (par défaut depuis 2025-08-22) désactive les comptes personnels, les utilisateurs connectés sont forcés dans choose-organization et n'arrivent jamais dans un état d'abonnement personnel |
Si vous avez besoin que les abonnements personnels + org coexistent : Dashboard → Organizations settings → Membership optional |
| Impossible de trouver une page Features | Les fonctionnalités sont par plan, pas globales | Dashboard → Billing → Plans → cliquez sur un plan → Features |
has({ plan: 'pro' }) retourne toujours false après un checkout réussi |
Le jeton de session n'a pas été actualisé pour inclure le nouveau plan | await clerk.session?.reload() ou naviguez pour forcer une nouvelle session |
has({ plan: 'pro' }) retourne false avant une tentative d'abonnement |
Slug de plan incompatible (sensible à la casse), OU Facturation non activée, OU passerelle de paiement non connectée en production | Vérifiez le slug dans Dashboard → Billing → Plans ; confirmez que Billing → Settings affiche activé + passerelle connectée |
has({ permission: 'org:x:y' }) retourne false pour un utilisateur qui a le rôle |
La Feature liée à cette permission n'est pas incluse dans le Plan actif de l'organisation | Ajoutez la Feature au Plan dans Dashboard → Billing → Plans → Features |
| Webhook 401 / vérification de signature échouée | Incompatibilité CLERK_WEBHOOK_SIGNING_SECRET ou route protégée par middleware |
Copiez le Signing Secret depuis Dashboard → Webhooks ; ajoutez la route webhook à createRouteMatcher(['/api/webhooks(.*)']) |
Permissions des portes de facturation
Quand la facturation est activée, has({ permission: 'org:posts:edit' }) retourne false si la Feature associée à cette permission n'est pas incluse dans le Plan actif de l'organisation, même si l'utilisateur a la permission assignée via son rôle. C'est intentionnel : la facturation limite les permissions au niveau de la fonctionnalité. Assurez-vous toujours que la Feature requise est attachée au Plan dans Dashboard → Billing → Plans → Features.
Voir aussi
clerk-setup- Installation initiale de Clerkclerk-orgs- Organisations B2B (requises pour la facturation B2B et les plans à limite de sièges)clerk-webhooks- Vérification de signature webhook et routage