clerk-billing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBilling
Billing
STOP, Dashboard-only prerequisite. Billing must be enabled from the Clerk Dashboard before any,<PricingTable />,<CheckoutButton />, orhas({ plan })usage works. The Clerk CLI and Backend API do not expose a toggle for this today, the only path is dashboard.clerk.com → your app → Billing → Settings. Dev instances can use the shared Clerk development gateway (no Stripe account needed); production requires a Stripe account for payment processing only.has({ feature })Note: Billing APIs are still experimental. Pin yourand@clerk/nextjspackage versions. Seeclerk-jsskill for the supported version table.clerk
⚠️ 注意:仅支持通过仪表盘配置。在使用任何、<PricingTable />、<CheckoutButton />或has({ plan })之前,必须先在Clerk仪表盘中启用Billing功能。目前Clerk CLI和后端API不提供启用开关,唯一路径是dashboard.clerk.com → 你的应用 → Billing → Settings。开发环境可使用共享的Clerk开发网关(无需Stripe账户);生产环境仅需绑定Stripe账户用于支付处理。has({ feature })提示:Billing API仍处于实验阶段。请固定和@clerk/nextjs的包版本。查看clerk-js技能文档获取支持版本列表。clerk
Quick Start
快速开始
-
Enable Billing, Dashboard → Billing → Settings. Dashboard-only; no CLI or API path. Skipping this throwsin dev and renders empty in prod.
cannot_render_billing_disabled -
Create plans in the matching tab, Dashboard → Billing → Plans. Two tabs, slugs scoped per tab, not movable after creation:
- User Plans → (default
<PricingTable />)for="user" - Organization Plans →
<PricingTable for="organization" />
Wrong-tab is the #1 cause of an empty. Plans live in Clerk; not synced to Stripe.<PricingTable /> - User Plans →
-
Add features inside a plan, open the plan in Dashboard → Billing → Plans, use its Features section. Features are scoped per plan, not global. The same slug can attach to multiple plans;matches if the active plan contains that slug.
has({ feature: 'export' }) -
Render(pass
<PricingTable />for B2B).for="organization" -
Gate access withor
has({ plan })fromhas({ feature }).auth() -
Handle billing webhooks for subscription lifecycle.
-
启用Billing功能,访问仪表盘 → Billing → 设置。仅支持仪表盘配置;无CLI或API路径。跳过此步骤会在开发环境抛出错误,生产环境则渲染空内容。
cannot_render_billing_disabled -
在对应标签页创建计划,访问仪表盘 → Billing → 计划。分为两个标签页,计划slug按标签页划分,创建后不可移动:
- 用户计划 → (默认
<PricingTable />)for="user" - 组织计划 →
<PricingTable for="organization" />
选错标签页是渲染为空的最常见原因。计划存储在Clerk中;不会同步到Stripe。<PricingTable /> - 用户计划 →
-
在计划内添加功能,在仪表盘 → Billing → 计划中打开对应计划,使用其功能模块。功能按计划划分,而非全局。同一slug可绑定多个计划;会匹配包含该slug的任何活跃计划。
has({ feature: 'export' }) -
渲染(B2B场景需传入
<PricingTable />)。for="organization" -
使用中的
auth()或has({ plan })管控访问权限。has({ feature }) -
处理账单Webhook以管理订阅生命周期。
Dashboard shortcuts
仪表盘快捷入口
| Action | URL |
|---|---|
| Enable Billing | |
| Create / edit plans | |
| Membership mode (B2C + B2B coexistence) | |
| Edit features | Plans → click a plan → Features section (no direct URL) |
| 操作 | 链接 |
|---|---|
| 启用Billing功能 | |
| 创建/编辑计划 | |
| 会员模式(B2C+B2B共存) | |
| 编辑功能 | 计划 → 点击对应计划 → 功能模块(无直接链接) |
What Do You Need?
所需参考文档
| Task | Reference |
|---|---|
| references/billing-components.md |
B2C patterns (individual user subscriptions, | references/b2c-patterns.md |
| B2B patterns (org subscriptions, seat-limit plans, admin-gated billing UI) | references/b2b-patterns.md |
| Webhook event catalog, payload shapes, handler templates | references/billing-webhooks.md |
| 任务 | 参考链接 |
|---|---|
| references/billing-components.md |
| B2C模式(个人用户订阅,需先设置“可选会员”) | references/b2c-patterns.md |
| B2B模式(组织订阅、席位限制计划、管理员专属账单UI) | references/b2b-patterns.md |
| Webhook事件目录、负载结构、处理器模板 | references/billing-webhooks.md |
References
参考文档说明
| Reference | Description |
|---|---|
| |
| B2C subscription billing patterns |
| B2B billing with organization subscriptions and seat-limit plans |
| Subscription lifecycle event handling |
| 参考文档 | 描述 |
|---|---|
| |
| B2C订阅账单实现模式 |
| 基于组织订阅和席位限制计划的B2B账单方案 |
| 订阅生命周期事件处理指南 |
Documentation
官方文档
Features vs Plans: When to Use Which
功能与计划:何时使用哪种方式
Use when gating a specific capability, export, analytics, API access, audit logs.
has({ feature: 'slug' })Use when gating a tier, showing the pro dashboard, checking org subscription level, redirecting free users.
has({ plan: 'slug' })| Scenario | Correct check |
|---|---|
| Gate the "Export CSV" button | |
| Gate the "Analytics" section | |
| Gate all of /dashboard/pro | |
| Check if org has team subscription | |
| Gate SSO configuration | |
When a user says "gate the export feature" or "gate analytics", always use . Only use when the gate is the plan tier itself, not a specific capability within it.
has({ feature })has({ plan })当管控特定功能(如导出、分析、API访问、审计日志)时,使用。
has({ feature: 'slug' })当管控整个套餐层级(如专业版仪表盘、检查组织订阅等级、引导免费用户升级)时,使用。
has({ plan: 'slug' })| 场景 | 正确校验方式 |
|---|---|
| 管控“导出CSV”按钮 | |
| 管控“分析”板块 | |
| 管控整个/dashboard/pro页面 | |
| 检查组织是否有团队订阅 | |
| 管控SSO配置 | |
当用户要求“管控导出功能”或“管控分析功能”时,务必使用。仅当管控目标是套餐层级本身(而非套餐内的特定功能)时,才使用。
has({ feature })has({ plan })Key Patterns
核心实现模式
1. Render the Pricing Table
1. 渲染价格表
Show available plans to users with a single component:
tsx
import { PricingTable } from '@clerk/nextjs'
export default function PricingPage() {
return (
<main>
<h1>Choose a plan</h1>
<PricingTable />
</main>
)
}<PricingTable />for="organization"使用单个组件向用户展示可用计划:
tsx
import { PricingTable } from '@clerk/nextjs'
export default function PricingPage() {
return (
<main>
<h1>选择你的套餐</h1>
<PricingTable />
</main>
)
}<PricingTable />for="organization"2. Check Feature Entitlements (Server-Side)
2. 服务端校验功能权限
Gate by individual features, this is the preferred approach for specific capabilities:
typescript
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>
)
}Features are configured in Clerk Dashboard → Billing → Features and assigned to plans. Use instead of when gating granular capabilities, check the feature, not the plan.
has({ feature })has({ plan })按单个功能管控访问权限,这是特定功能管控的推荐方式:
typescript
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>
)
}功能需在Clerk仪表盘 → Billing → 功能中配置并分配给对应计划。管控细粒度功能时,使用而非,直接校验功能而非套餐。
has({ feature })has({ plan })3. Check Feature Entitlements (Client-Side)
3. 客户端校验功能权限
Use for client-side feature gating. Combine with server-side checks for full coverage:
useAuth()tsx
'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>
)
}Server Components use , Client Components use . Both support and .
auth()useAuth()has({ feature })has({ plan })使用进行客户端功能管控。结合服务端校验实现全面覆盖:
useAuth()tsx
'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>
)
}服务端组件使用,客户端组件使用。两者均支持和。
auth()useAuth()has({ feature })has({ plan })4. Check Subscription Plan Server-Side
4. 服务端校验订阅计划
Gate access by subscription plan (use this for tier-level gates, not individual features):
typescript
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 />
}按订阅套餐管控访问权限(仅用于套餐层级管控,而非单个功能):
typescript
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. Client-Side Plan Checks
5. 客户端校验订阅计划
Use hook for client components:
useAuth()tsx
'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>
)
}使用钩子实现客户端组件的套餐校验:
useAuth()tsx
'use client'
import { useAuth } from '@clerk/nextjs'
export function UpgradePrompt() {
const { has } = useAuth()
if (has?.({ plan: 'pro' })) {
return null
}
return (
<div>
<p>升级到专业版以访问此功能</p>
<a href="/pricing">查看套餐</a>
</div>
)
}6. B2B Seat-Based Billing with Organizations
6. B2B基于席位的组织账单
Org plans can carry a seat limit (membership cap) that Clerk enforces at invite time. Use the slug prefix on org-side plan checks (e.g. ) to keep gating unambiguous. Render the B2B pricing page with , and use for the org account billing UI.
org:has({ plan: 'org:team' })<PricingTable for="organization" /><OrganizationProfile />See for tiered plan naming, seat-limit invariants, admin-only billing, and webhook handlers.
references/b2b-patterns.md组织计划可设置席位限制(成员上限),Clerk会在邀请成员时自动强制执行。在组织侧套餐校验中使用前缀的slug(如),确保权限管控清晰。使用渲染B2B价格页,使用作为组织账户的账单UI。
org:has({ plan: 'org:team' })<PricingTable for="organization" /><OrganizationProfile />查看获取分层套餐命名、席位限制规则、管理员专属账单及Webhook处理器的详细说明。
references/b2b-patterns.md7. Display Subscription Status
7. 展示订阅状态
Check specific plans with , or use for full subscription details in client components. Do not read plan information from directly, that is not the supported path.
has({ plan })useSubscription()sessionClaimsServer component, check for specific plans:
typescript
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>
)
}Client component, full subscription details via :
useSubscription()tsx
'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>
)
}is for display only. For authorization checks (gating content or routes), always useuseSubscription()orhas({ plan }).has({ feature })
使用校验特定套餐,或在客户端组件中使用获取完整订阅详情。请勿直接从读取套餐信息,这并非官方支持的方式。
has({ plan })useSubscription()sessionClaims服务端组件,校验特定套餐:
typescript
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>当前套餐</h2>
<p>你正在使用{currentPlan}套餐</p>
{currentPlan === 'free' && <a href="/pricing">立即升级</a>}
</div>
)
}客户端组件,通过获取完整订阅详情:
useSubscription()tsx
'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">选择套餐</a>
return (
<div>
<p>状态: {subscription.status}</p>
{subscription.nextPayment && (
<p>下次支付日期: {subscription.nextPayment.date.toLocaleDateString()}</p>
)}
</div>
)
}仅用于展示。授权校验(管控内容或路由)时,请始终使用useSubscription()或has({ plan })。has({ feature })
8. Protect API Routes by Plan
8. 按套餐保护API路由
Gate API routes using :
auth()typescript
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' })
}使用管控API路由:
auth()typescript
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: '需专业版套餐' }, { status: 403 })
}
return NextResponse.json({ data: '高级数据' })
}9. Handle Billing Webhooks
9. 处理账单Webhook
Clerk event names differ from Stripe event names. Clerk billing webhooks use dot-notation and camelCase, not Stripe's underscore format.There is noevent. Cancellation fires at the item level assubscription.canceled.subscriptionItem.canceled
Intent Stripe event name Clerk event name Subscription created customer.subscription.createdsubscription.createdSubscription updated customer.subscription.updatedsubscription.updatedSubscription active (none) subscription.activeSubscription past due (none) subscription.pastDueSubscription item canceled customer.subscription.deletedsubscriptionItem.canceledSubscription item past due invoice.payment_failedsubscriptionItem.pastDueSubscription item updated (none) subscriptionItem.updatedSubscription item active (none) subscriptionItem.activeSubscription item upcoming renewal (none) subscriptionItem.upcomingSubscription item ended (none) subscriptionItem.endedSubscription item abandoned (none) subscriptionItem.abandonedSubscription item expired (none) subscriptionItem.expiredSubscription item incomplete (none) subscriptionItem.incompleteFree trial ending soon (none) subscriptionItem.freeTrialEndingPayment attempt created (none) paymentAttempt.createdPayment attempt updated (none) paymentAttempt.updatedAlways use Clerk's event names, never Stripe's, inchecks.evt.type
Payload shape. Clerk billing webhook payloads are nested. The subscribing entity lives under(fields:evt.data.payer,user_id?). The plan info is on each item underorganization_id?. The subscription id is simplyevt.data.items[i].plan.slug. Subscription items do not carry aevt.data.idfield back-reference, so insubscription_idhandlers you identify the record by the item id (subscriptionItem.*) or look up by payer plus plan.evt.data.id
Minimal handler to anchor the pattern (import from , verify, branch on Clerk event name):
@clerk/nextjs/webhookstypescript
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 },
})
}
// Add more branches per the event catalog above (subscription.updated,
// subscriptionItem.canceled, subscriptionItem.pastDue, etc.)
return new Response('OK', { status: 200 })
}For the full template covering all 15 events, the TS type declarations from , the public-route setup, and the subscription status value table, see .
@clerk/backendproxy.tsreferences/billing-webhooks.mdClerk事件名称与Stripe不同。Clerk账单Webhook使用点标记和驼峰命名,而非Stripe的下划线格式。不存在事件。取消订阅会在条目级别触发subscription.canceled事件。subscriptionItem.canceled
操作意图 Stripe事件名称 Clerk事件名称 订阅创建 customer.subscription.createdsubscription.created订阅更新 customer.subscription.updatedsubscription.updated订阅激活 (无) subscription.active订阅逾期 (无) subscription.pastDue订阅条目取消 customer.subscription.deletedsubscriptionItem.canceled订阅条目逾期 invoice.payment_failedsubscriptionItem.pastDue订阅条目更新 (无) subscriptionItem.updated订阅条目激活 (无) subscriptionItem.active订阅条目即将续订 (无) subscriptionItem.upcoming订阅条目结束 (无) subscriptionItem.ended订阅条目放弃 (无) subscriptionItem.abandoned订阅条目过期 (无) subscriptionItem.expired订阅条目未完成 (无) subscriptionItem.incomplete免费试用即将结束 (无) subscriptionItem.freeTrialEnding支付尝试创建 (无) paymentAttempt.created支付尝试更新 (无) paymentAttempt.updated请始终使用Clerk的事件名称,切勿在校验中使用Stripe的事件名称。evt.type
负载结构。Clerk账单Webhook负载为嵌套结构。订阅实体位于下(字段:evt.data.payer,user_id?)。套餐信息位于每个条目下的organization_id?。订阅ID为evt.data.items[i].plan.slug。订阅条目不包含evt.data.id反向引用字段,因此在subscription_id处理器中,需通过条目ID(subscriptionItem.*)或按支付方+套餐查询来识别记录。evt.data.id
基础处理器示例(从导入验证方法,验证后按Clerk事件名称分支处理):
@clerk/nextjs/webhookstypescript
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('验证失败', { 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 },
})
}
// 根据上述事件目录添加更多分支(如subscription.updated、
// subscriptionItem.canceled、subscriptionItem.pastDue等)
return new Response('成功', { status: 200 })
}如需覆盖全部15个事件的完整模板、提供的TS类型声明、公共路由配置以及订阅状态值表,请查看。
@clerk/backendproxy.tsreferences/billing-webhooks.md10. Upgrade / Downgrade Flow
10. 升级/降级流程
Let users manage their subscription from inside the app:
tsx
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 />允许用户在应用内管理订阅:
tsx
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>账单管理</h1>
{isPro ? (
<div>
<p>你正在使用专业版套餐</p>
<PricingTable />
</div>
) : (
<div>
<p>升级以访问高级功能</p>
<PricingTable />
</div>
)}
</div>
)
}<PricingTable />Plan and Feature Naming
计划与功能命名规范
Plan slugs and feature slugs are defined in Clerk Dashboard → Billing. Common conventions:
| Tier | Plan Slug | Example Features |
|---|---|---|
| Free | (no plan check needed) | basic features |
| Starter | | |
| Pro | | |
| Enterprise | | all features + |
Use lowercase slugs matching what you define in the dashboard.
计划slug和功能slug在Clerk仪表盘 → Billing中定义。常见命名规范:
| 套餐层级 | 计划Slug | 示例功能 |
|---|---|---|
| 免费版 | (无需套餐校验) | 基础功能 |
| 入门版 | | |
| 专业版 | | |
| 企业版 | | 全部功能 + |
使用小写slug,与仪表盘中定义的内容一致。
B2B vs B2C Billing
B2B与B2C账单对比
| Scenario | Who subscribes | Plan check |
|---|---|---|
| B2C SaaS | Individual user | |
| B2B SaaS | Organization | |
| Seat-limited B2B | Organization | Plan has a seat cap; pricing is per-plan, not per-member, tier your plans for bigger orgs |
For B2B, ensure the user has an active org session. The check evaluates the active entity (user or org).
has()| 场景 | 订阅主体 | 套餐校验方式 |
|---|---|---|
| B2C SaaS | 个人用户 | 在用户会话中使用 |
| B2B SaaS | 组织 | 在组织会话中使用 |
| 有限席位B2B | 组织 | 套餐设置席位上限;按套餐定价而非按成员,为大型组织设计分层套餐 |
B2B场景需确保用户拥有活跃的组织会话。校验会基于当前活跃实体(用户或组织)进行判断。
has()Checkout Flows
结账流程
Clerk renders its own checkout drawer automatically through and . Plans and pricing live in Clerk. To trigger checkout from a server action, redirect to a page that renders :
<PricingTable /><CheckoutButton /><PricingTable />typescript
'use server'
import { redirect } from 'next/navigation'
export async function upgradeAction() {
redirect('/pricing')
}Clerk会通过和自动渲染内置的结账抽屉。计划和价格存储在Clerk中。如需从服务端操作触发结账,请重定向到渲染的页面:
<PricingTable /><CheckoutButton /><PricingTable />typescript
'use server'
import { redirect } from 'next/navigation'
export async function upgradeAction() {
redirect('/pricing')
}Error Signatures (diagnose fast)
错误排查速查
When you see any of these errors or symptoms, the fix is almost always a Dashboard toggle, not a code change. Do not start editing components.
| Error / symptom | Root cause | Fix |
|---|---|---|
| Billing is not enabled for this instance | Enable Billing at dashboard.clerk.com → Billing → Settings. No CLI path. |
| No plans, OR plan in the wrong tab (User vs Organization), OR Billing not enabled | Create plan in matching tab; pass |
| Users can't subscribe to a personal plan on a B2C + B2B app | Membership required mode (default since 2025-08-22) disables personal accounts, signed-in users are forced into | If you need personal + org subscriptions coexisting: Dashboard → Organizations settings → Membership optional |
| Can't find a Features page | Features are per-plan, not global | Dashboard → Billing → Plans → click plan → Features |
| Session token hasn't been refreshed to include the new plan | |
| Plan slug mismatch (case-sensitive), OR Billing not enabled, OR payment gateway not connected in production | Verify slug in Dashboard → Billing → Plans; confirm Billing → Settings shows enabled + connected gateway |
| The Feature tied to that permission is not included in the organization's active Plan | Add the Feature to the Plan in Dashboard → Billing → Plans → Features |
| Webhook 401 / signature verification failed | | Copy the Signing Secret from Dashboard → Webhooks; add the webhook route to |
遇到以下错误或症状时,解决方案几乎都是通过仪表盘设置,而非修改代码。请勿直接编辑组件。
| 错误/症状 | 根本原因 | 修复方案 |
|---|---|---|
| 当前实例未启用Billing功能 | 在dashboard.clerk.com → Billing → 设置启用Billing功能。无CLI路径。 |
| 无计划,或计划在错误标签页(用户/组织),或未启用Billing功能 | 在对应标签页创建计划;B2B场景传入 |
| 同时支持B2C+B2B的应用中,用户无法订阅个人套餐 | 强制会员模式(2025-08-22起默认启用)禁用个人账户,登录用户会被强制进入 | 如需同时支持个人+组织订阅:仪表盘 → 组织设置 → 可选会员 |
| 找不到功能页面 | 功能按计划划分,而非全局 | 仪表盘 → Billing → 计划 → 点击对应计划 → 功能模块 |
成功结账后 | 会话令牌未刷新以包含新套餐信息 | 调用 |
未订阅时 | 计划slug不匹配(区分大小写),或未启用Billing功能,或生产环境未连接支付网关 | 在仪表盘 → Billing → 计划中验证slug;确认Billing → 设置显示已启用且已连接网关 |
拥有对应角色的用户 | 该权限关联的功能未包含在组织的活跃套餐中 | 在仪表盘 → Billing → 计划 → 功能模块中,将功能添加到对应套餐 |
| Webhook返回401 / 签名验证失败 | | 从仪表盘 → Webhooks复制签名密钥;将Webhook路由添加到 |
Billing Gates Permissions
账单管控权限
When Billing is enabled, returns if the Feature associated with that permission is not included in the organization's active Plan, even if the user has the permission assigned via their role. This is by design: billing gates permissions at the feature level. Always ensure the required Feature is attached to the Plan in Dashboard → Billing → Plans → Features.
has({ permission: 'org:posts:edit' })false启用Billing后,若组织的活跃套餐未包含权限关联的功能,即使用户通过角色获得了该权限,仍会返回。这是设计预期:账单会在功能层面管控权限。请确保所需功能已添加到仪表盘 → Billing → 计划 → 功能模块中的对应套餐。
has({ permission: 'org:posts:edit' })falseSee Also
相关技能
- - Initial Clerk install
clerk-setup - - B2B organizations (required for B2B billing and seat-limit plans)
clerk-orgs - - Webhook signature verification and routing
clerk-webhooks
- - Clerk初始安装配置
clerk-setup - - B2B组织管理(B2B账单和席位限制计划的前置依赖)
clerk-orgs - - Webhook签名验证与路由配置
clerk-webhooks