clerk-billing

Par clerk · skills

Clerk Billing pour la gestion des abonnements - afficher le `PricingTable` de Clerk

npx skills add https://github.com/clerk/skills --skill clerk-billing

Facturation

ARRÊT, prérequis. La facturation doit être activée avant que <PricingTable />, <CheckoutButton />, has({ plan }), ou has({ 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éfaut free_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/nextjs et clerk-js. Voir le skill clerk pour le tableau des versions supportées.

Démarrage rapide

  1. Activez la facturation, via Dashboard → Billing → Settings ou clerk enable billing (voir section Agent-first). Sauter cette étape génère cannot_render_billing_disabled en dev et rend vide en production.

  2. 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éfaut for="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.

  3. 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.

  4. Rendez <PricingTable /> (passez for="organization" pour B2B).

  5. Limitez l'accès avec has({ plan }) ou has({ feature }) depuis auth().

  6. 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 skill clerk-webhooks pour les événements du cycle de vie.
  • La manipulation de la carte features de 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 toujours has({ plan }) ou has({ 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 forme subscriptionItem.canceled.

Intention Nom d'événement Stripe Nom d'événement Clerk
Abonnement créé customer.subscription.created subscription.created
Abonnement mis à jour customer.subscription.updated subscription.updated
Abonnement actif (aucun) subscription.active
Abonnement en retard (aucun) subscription.pastDue
Élément d'abonnement annulé customer.subscription.deleted subscriptionItem.canceled
Élément d'abonnement en retard invoice.payment_failed subscriptionItem.pastDue
Élément d'abonnement mis à jour (aucun) subscriptionItem.updated
Élément d'abonnement actif (aucun) subscriptionItem.active
Renouvellement à 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.incomplete
Essai gratuit se terminant bientôt (aucun) subscriptionItem.freeTrialEnding
Tentative de paiement créée (aucun) paymentAttempt.created
Tentative de paiement mise à jour (aucun) paymentAttempt.updated

Utilisez 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 sous evt.data.items[i].plan.slug. L'ID d'abonnement est simplement evt.data.id. Les éléments d'abonnement ne portent pas de champ de référence arrière subscription_id, donc dans les gestionnaires subscriptionItem.* 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 Clerk
  • clerk-orgs - Organisations B2B (requises pour la facturation B2B et les plans à limite de sièges)
  • clerk-webhooks - Vérification de signature webhook et routage

Skills similaires