Chargebee
用于 Better Auth 的 Chargebee 插件,用于管理订阅和支付。
Chargebee 插件将 Chargebee 的 订阅管理和计费功能与 Better Auth 集成。由于支付和认证通常紧密耦合,此插件简化了 Chargebee 集成到应用程序的流程,处理客户创建、订阅管理和 Webhook 处理。
此插件由 Chargebee 团队维护。关于错误、问题或功能请求, 请访问 Chargebee GitHub 仓库。
在 Chargebee Discord 上获取支持
有问题吗?我们的团队随时在 Discord 上为您提供帮助。
功能
- 用户注册时自动创建 Chargebee 客户
- 管理订阅计划和定价(基于项目:计划、附加组件、费用)
- 处理订阅生命周期事件(创建、更新、取消)
- 使用 Basic Auth 验证安全处理 Chargebee Webhook
- 将订阅数据暴露给应用程序
- 支持试用期和多项目订阅
- 自动试用滥用预防——每个账户在所有计划中只能获得一次试用
- 灵活的引用系统,用于将订阅与用户或组织关联
- 团队订阅支持及席位管理
- 通过 Chargebee 托管页面进行托管结账和门户
- 自助计费门户,用于管理支付方式、发票和订阅
安装
将插件添加到认证配置
import { betterAuth } from "better-auth"
import { chargebee } from "@chargebee/better-auth"
import Chargebee from "chargebee"
const chargebeeClient = new Chargebee({
apiKey: process.env.CHARGEBEE_API_KEY!,
site: process.env.CHARGEBEE_SITE!,
})
export const auth = betterAuth({
// ... 您的现有配置
plugins: [
chargebee({
chargebeeClient,
createCustomerOnSignUp: true,
webhookUsername: process.env.CHARGEBEE_WEBHOOK_USERNAME,
webhookPassword: process.env.CHARGEBEE_WEBHOOK_PASSWORD,
})
]
})添加客户端插件
import { createAuthClient } from "better-auth/client"
import { chargebeeClient } from "@chargebee/better-auth/client"
export const authClient = createAuthClient({
// ... 您的现有配置
plugins: [
chargebeeClient({
subscription: true // 如果要启用订阅管理
})
]
})设置 Chargebee Webhook
在您的 Chargebee 仪表板中创建一个指向以下地址的 Webhook 端点:
https://your-domain.com/api/auth/chargebee/webhook/api/auth 是认证服务器的默认路径。
确保至少选择以下事件:
subscription_createdsubscription_activatedsubscription_changedsubscription_renewedsubscription_startedsubscription_cancelledsubscription_cancellation_scheduledcustomer_deleted
如果设置了 webhookUsername 和 webhookPassword,请在 Chargebee Webhook 设置中配置相同的 Basic Authentication 凭据。
用法
客户管理
您可以将此插件仅用于客户管理,而无需启用订阅。这在您只想将 Chargebee 客户与用户关联时非常有用。
当设置 createCustomerOnSignUp: true 时,注册时会自动创建一个 Chargebee 客户,并将其关联到数据库中的用户。
您可以自定义客户创建流程:
chargebee({
// ... 其他选项
createCustomerOnSignUp: true,
onCustomerCreate: async ({ chargebeeCustomer, user }) => {
// 对新创建的客户执行一些操作
console.log(`Customer ${chargebeeCustomer.id} created for user ${user.id}`);
},
})传递额外的客户参数
Better Auth 将名称存储在单个 user.name 字段中。如果您希望在创建时传递 first_name、last_name 或任何其他 Chargebee 客户字段,请使用 getCustomerCreateParams:
chargebee({
// ... 其他选项
createCustomerOnSignUp: true,
getCustomerCreateParams: (user) => {
const [firstName, ...rest] = (user.name ?? "").split(" ");
return {
first_name: firstName || undefined,
last_name: rest.join(" ") || undefined,
// phone: user.phoneNumber,
// 任何其他 Chargebee Customer.CreateInputParam 字段
};
},
})此回调接收 user 对象和一个可选的 ctx(请求上下文——仅在按需创建客户时可用,而不是在注册期间)。
getCustomerCreateParams 适用于 createCustomerOnSignUp 和按需客户创建(例如,当用户首次订阅且没有先前注册时)。对于组织,请使用 organization.getCustomerCreateParams。
订阅管理
定义计划
Chargebee 使用基于项目的计费模型。您可以静态定义订阅计划,也可以从数据库中动态定义:
静态计划:
subscription: {
enabled: true,
plans: [
{
name: "starter", // 存储到数据库时计划名称会自动转为小写
itemPriceId: "starter-USD-Monthly", // 来自 Chargebee 的 item price ID
type: "plan",
limits: {
projects: 5,
storage: 10
}
},
{
name: "pro",
itemPriceId: "pro-USD-Monthly",
type: "plan",
limits: {
projects: 20,
storage: 50
},
freeTrial: {
days: 14,
}
}
]
}从数据库动态获取计划(推荐):
推荐从您自己的数据库中获取计划。这样您可以完全控制计划数据,使用自定义元数据(限制、功能、展示信息)丰富计划,并避免在认证配置中硬编码 Chargebee 配置:
subscription: {
enabled: true,
plans: async () => {
const plans = await db.query("SELECT * FROM plans");
return plans.map(plan => ({
name: plan.name,
itemPriceId: plan.chargebee_item_price_id,
type: "plan" as const,
limits: JSON.parse(plan.limits)
}));
}
}更多内容请参见 计划配置。
创建订阅
要创建新订阅,请使用 subscription.create 方法:
const { data, error } = await authClient.subscription.create({ itemPriceId: "pro-USD-Monthly", // required, 来自 Chargebee 的 item price ID(s)。单个字符串或数组用于多项目订阅。 referenceId: "123", // 订阅的引用 ID。默认基于 customerType。 metadata, // 要与订阅一起存储的附加元数据。 customerType, // 计费客户的类型。(默认:"user") seats: 1, // 席位数量(如果适用)。 successUrl, // required, 用户完成付款或设置后重定向的 URL。 cancelUrl, // required, 如果设置,用户取消后将跳转至此。 disableRedirect: false, // required, 禁用订阅成功后的重定向。 trialEnd, // 试用结束时间(Unix 时间戳)。});itemPriceIdstring | string[]required来自 Chargebee 的 item price ID(s)。单个字符串或数组用于多项目订阅。
referenceIdstring订阅的引用 ID。默认基于 customerType。
metadataRecord<string, any>要与订阅一起存储的附加元数据。
customerType"user" | "organization"计费客户的类型。(默认:"user")
seatsnumber席位数量(如果适用)。
successUrlstringrequired用户完成付款或设置后重定向的 URL。
cancelUrlstringrequired如果设置,用户取消后将跳转至此。
disableRedirectbooleanrequired禁用订阅成功后的重定向。
trialEndnumber试用结束时间(Unix 时间戳)。
简单示例:
这将创建一个 Chargebee Hosted Page,并将用户重定向到 Chargebee 结账页面。
await authClient.subscription.create({
itemPriceId: "pro-USD-Monthly",
successUrl: "/dashboard",
cancelUrl: "/pricing",
});结账重定向的工作原理
插件不会直接重定向到您的 successUrl。相反,它将 Chargebee 的 redirect_url 设置为一个内部注册的端点:
GET {baseURL}/subscription/success?callbackURL=<your-successUrl>&subscriptionId=<id>Chargebee 在成功结账后访问该端点,然后插件会立即将用户转发到您原始的 successUrl。这为插件提供了一个钩子点,位于 Chargebee 的托管式页面重定向和您的应用程序之间,这样可以在不更改调用站点代码的情况下插入未来的中间件(例如会话刷新、订阅同步)。
切换计划
要将现有订阅切换到不同计划,请使用 subscription.update 方法。这确保用户只为新计划付费,而不是同时为两个计划付费:
const { data, error } = await authClient.subscription.update({ itemPriceId: "pro-USD-Monthly", // required, 来自 Chargebee 的 item price ID(s)。单个字符串或数组用于多项目订阅。 referenceId: "123", // 订阅的引用 ID。默认基于 customerType。 subscriptionId: "sub_123", // 要更新的订阅 ID。 metadata, // 要与订阅一起存储的附加元数据。 customerType, // 计费客户的类型。(默认:"user") seats: 1, // 要更新到的席位数量(如果适用)。 successUrl, // required, 用户完成付款或设置后重定向的 URL。 cancelUrl, // required, 如果设置,用户取消后将跳转至此。 returnUrl, // 返回的 URL。 disableRedirect: false, // required, 禁用重定向。});itemPriceIdstring | string[]required来自 Chargebee 的 item price ID(s)。单个字符串或数组用于多项目订阅。
referenceIdstring订阅的引用 ID。默认基于 customerType。
subscriptionIdstring要更新的订阅 ID。
metadataRecord<string, any>要与订阅一起存储的附加元数据。
customerType"user" | "organization"计费客户的类型。(默认:"user")
seatsnumber要更新到的席位数量(如果适用)。
successUrlstringrequired用户完成付款或设置后重定向的 URL。
cancelUrlstringrequired如果设置,用户取消后将跳转至此。
returnUrlstring返回的 URL。
disableRedirectbooleanrequired禁用重定向。
await authClient.subscription.update({
itemPriceId: "enterprise-USD-Monthly", // 新的 item price id
successUrl: "/dashboard",
cancelUrl: "/pricing",
});该插件仅支持每个引用 ID(用户或组织)同时只有一个活跃或试用中的订阅。使用 subscription.update 当用户已有活跃订阅并希望切换计划时。当用户没有活跃订阅时,请使用 subscription.create。
如果用户已有活跃订阅,则必须使用 subscription.update 并提供 subscriptionId 参数。否则,尝试使用 subscription.create 创建新订阅将因 ALREADY_SUBSCRIBED 错误而失败。
列出活跃订阅
要获取当前用户或组织的活跃订阅,请使用 subscription.list 方法:
const { data, error } = await authClient.subscription.list({ query: { referenceId, // 订阅的引用 ID。默认基于 customerType。 customerType, // 计费客户的类型。(默认:"user") },});referenceIdstring订阅的引用 ID。默认基于 customerType。
customerType"user" | "organization"计费客户的类型。(默认:"user")
const { data } = await authClient.subscription.list();
// data → 经过计划限制和 itemPriceId 增强的活跃/试用中订阅数组
// 对于组织:
const { data: orgSubscriptions } = await authClient.subscription.list({
query: {
referenceId: "org_123",
customerType: "organization"
}
});取消订阅
要取消订阅,请使用 subscription.cancel 方法。这会将用户重定向到 Chargebee Portal,用户可以在那里取消订阅。当订阅在当前计费周期结束时取消时,Chargebee 会将其标记为 non_renewing。只有在周期结束时,订阅状态才会变为 cancelled。
const { data, error } = await authClient.subscription.cancel({ referenceId: 'org_123', // 要取消的订阅的引用 ID。默认基于 customerType。 customerType, // 计费客户的类型。(默认:"user") subscriptionId: 'sub_123', // 要取消的订阅的 ID。 returnUrl: '/account', // required, 用户点击返回您的网站链接时要跳转的 URL。});referenceIdstring要取消的订阅的引用 ID。默认基于 customerType。
customerType"user" | "organization"计费客户的类型。(默认:"user")
subscriptionIdstring要取消的订阅的 ID。
returnUrlstringrequired用户点击返回您的网站链接时要跳转的 URL。
理解取消状态
Chargebee 支持不同的取消行为:
| 字段 | 描述 |
|---|---|
canceledAt | 订阅被取消的时间。 |
status | 当订阅结束时状态变为 "cancelled"。 |
计费门户会话
为了获得完整的自助计费体验,您可以打开 Chargebee 客户门户,让用户管理其计费的所有方面:
const { data, error } = await authClient.subscription.portal({ referenceId: 'org_123', // 订阅的引用 ID。默认基于 customerType。 customerType, // 计费客户的类型。(默认:"user") returnUrl: '/account', // required, 用户完成门户会话后重定向的 URL。 disableRedirect: false, // 禁用重定向。});referenceIdstring订阅的引用 ID。默认基于 customerType。
customerType"user" | "organization"计费客户的类型。(默认:"user")
returnUrlstringrequired用户完成门户会话后重定向的 URL。
disableRedirectboolean禁用重定向。
示例:
await authClient.subscription.portal({
returnUrl: "/account/billing",
fetchOptions: {
onSuccess: (ctx) => {
// 重定向到 Chargebee 门户
window.location.href = ctx.data.url;
}
}
});用于组织计费:
await authClient.subscription.portal({
referenceId: "org_123456",
customerType: "organization",
returnUrl: "/org/billing"
});门户允许用户:
- 更新支付方式(信用卡、银行账户)
- 查看和下载发票
- 管理订阅(升级、降级、取消)
- 更新账单地址和联系人信息
- 查看订阅历史记录
- 应用促销优惠码
门户会话提供完整的自助服务体验,并推荐在您希望让用户完全控制其计费时,优于单独的操作(如取消)。
引用系统
默认情况下,订阅会关联到用户 ID。不过,您可以使用自定义引用 ID 将订阅关联到其他实体,例如组织:
// 为组织创建订阅
await authClient.subscription.create({
itemPriceId: "team-USD-Monthly",
referenceId: "org_123456",
customerType: "organization",
successUrl: "/dashboard",
cancelUrl: "/pricing",
seats: 10 // 团队计划的座位数
});
// 列出组织的订阅
const { data: subscriptions } = await authClient.subscription.list({
query: {
referenceId: "org_123456",
customerType: "organization"
}
});带席位数的团队订阅
对于团队或组织计划,您可以指定席位数量:
await authClient.subscription.create({
itemPriceId: "team-USD-Monthly",
referenceId: "org_123456",
customerType: "organization",
seats: 10, // 10 名团队成员
successUrl: "/org/billing/success",
cancelUrl: "/org/billing"
});seats 参数将作为订阅项的数量传递给 Chargebee。您可以在应用逻辑中使用该值来限制团队或组织中的成员数量。
要授权引用 ID,请实现 authorizeReference 函数:
subscription: {
// ... 其他选项
authorizeReference: async ({ user, session, referenceId, action }) => {
// 检查用户是否有权限管理此引用的订阅
if (action === "create-subscription" || action === "update-subscription" || action === "cancel-subscription" || action === "billing-portal") {
const org = await db.member.findFirst({
where: {
organizationId: referenceId,
userId: user.id
}
});
return org?.role === "owner"
}
return true;
}
}Webhook 处理
此插件会自动处理来自 Chargebee 的常见 Webhook 事件:
subscription_created– 在 Chargebee 中创建订阅时创建订阅。subscription_activated– 当订阅在 Chargebee 中变为活跃时更新订阅。subscription_changed– 当 Chargebee 中发生更改时更新订阅。subscription_renewed– 在续订时更新订阅。subscription_started– 当试用结束并且订阅开始时更新订阅。subscription_cancelled– 将订阅标记为已取消。subscription_cancellation_scheduled– 使用计划取消的详细信息更新订阅。customer_deleted– 移除客户及其关联的任何订阅。
您也可以使用 webhookHandler 处理自定义事件,这为您提供了对类型化处理程序的直接访问:
import { WebhookEventType, WebhookHandler } from "chargebee"
chargebee({
chargebeeClient,
createCustomerOnSignUp: true,
webhookHandler: (handler: WebhookHandler) => {
handler.on(WebhookEventType.PaymentFailed, async ({ event }) => {
// 处理支付失败
});
handler.on(WebhookEventType.InvoiceGenerated, async ({ event }) => {
// 处理已生成的发票
});
}
})订阅生命周期钩子
您可以在各种订阅生命周期事件中挂钩:
subscription: {
// ... 其他选项
onSubscriptionComplete: async ({ subscription, chargebeeSubscription, plan }) => {
// 当通过托管页面成功创建订阅时调用
await sendWelcomeEmail(subscription.referenceId, plan.name);
},
onSubscriptionCreated: async ({ subscription, chargebeeSubscription, plan }) => {
// 当订阅被创建时调用
await sendSubscriptionCreatedEmail(subscription.referenceId, plan.name);
},
onSubscriptionUpdate: async ({ subscription }) => {
// 当订阅被更新时调用
console.log(`Subscription ${subscription.id} updated`);
},
onSubscriptionDeleted: async ({ subscription, chargebeeSubscription }) => {
// 当订阅被删除时调用
await sendCancellationEmail(subscription.referenceId);
},
onTrialStart: async ({ subscription }) => {
// 当试用开始时调用
await sendTrialStartEmail(subscription.referenceId);
},
onTrialEnd: async ({ subscription }) => {
// 当试用结束时调用
await sendTrialEndEmail(subscription.referenceId);
}
}试用期
您可以为计划配置试用期:
{
name: "pro",
itemPriceId: "pro-USD-Monthly",
type: "plan",
freeTrial: {
days: 14, // 自动应用 14 天试用期
},
limits: {
projects: 100,
storage: 500,
}
}当用户订阅此计划时,试用期会自动应用——无需手动传递 trialEnd:
// 将根据计划配置自动计算并应用试用期
await authClient.subscription.create({
itemPriceId: "pro-USD-Monthly", // 带 14 天试用期的计划
successUrl: "/dashboard",
cancelUrl: "/pricing",
});
// ✅ 用户会自动获得 14 天试用期!插件会将试用结束日期计算为:当前日期 + 试用天数。
防止重复试用
为了防止用户获得多次试用,请启用 preventDuplicateTrials:
subscription: {
enabled: true,
plans,
preventDuplicateTrials: true, // 用户只能获得一次试用
}覆盖试用结束日期(可选)
要设置自定义试用结束日期,请传入 trialEnd(Unix 时间戳):
await authClient.subscription.create({
itemPriceId: "pro-USD-Monthly",
successUrl: "/dashboard",
cancelUrl: "/pricing",
trialEnd: 1735689600, // 自定义试用结束:2025 年 1 月 1 日
});试用期仅适用于新订阅。对现有订阅的更新不能包含试用期(Chargebee 限制)。
架构
Chargebee 插件会向你的数据库添加以下表:
用户
表名:user
组织
表名:organization (仅当 organization.enabled 为 true 时)
订阅
表名:subscription
订阅项
表名:subscriptionItem
自定义架构
如果支持,你可以向 Chargebee 插件传入 schema 选项来更改架构表名或字段:
chargebee({
// ... 其他选项
schema: {
subscription: {
modelName: "chargebeeSubscriptions", // 将 subscription 表映射到 chargebeeSubscriptions
fields: {
referenceId: "userId" // 将 referenceId 字段映射到 userId
}
}
}
})选项
| 选项 | 类型 | 描述 |
|---|---|---|
chargebeeClient | Chargebee | Chargebee 客户端实例。必需。 |
webhookUsername | string | Webhook 端点的基本认证用户名。建议在生产环境中使用。 |
webhookPassword | string | Webhook 端点的基本认证密码。建议在生产环境中使用。 |
createCustomerOnSignUp | boolean | 用户注册时是否自动创建 Chargebee 客户。默认值:false。 |
getCustomerCreateParams | function | 返回传递给 cb.customer.create 的额外参数(用于用户客户),例如 first_name、last_name。接收 user 和可选的 ctx。 |
onCustomerCreate | function | 客户创建后的回调函数。接收 { chargebeeCustomer, user }。 |
webhookHandler | function | 接收 webhook 处理程序实例的回调函数。调用 handler.on(EventType, fn) 来注册类型化的事件监听器。 |
subscription | object | 订阅配置。参见下面的 订阅选项。 |
organization | object | 启用组织客户支持。参见下面的 组织选项。 |
订阅选项
| 选项 | 类型 | 描述 |
|---|---|---|
enabled | boolean | 是否启用订阅功能。必需。 |
plans | ChargebeePlan[] 或 function | 订阅计划数组或返回计划的异步函数。如果启用,则必需。 |
requireEmailVerification | boolean | 是否要求在允许创建订阅之前进行电子邮件验证。默认值:false。 |
preventDuplicateTrials | boolean | 防止用户获得多个试用。默认值:false。 |
authorizeReference | function | 授权参考 ID。接收 { user, session, referenceId, action } 和上下文。 |
getHostedPageParams | function | 自定义 Chargebee Hosted Page 参数。接收 { user, session, plan, subscription }、请求和上下文。 |
onSubscriptionComplete | function | 通过托管页面创建订阅后调用。接收 { subscription, chargebeeSubscription, plan }。 |
onSubscriptionCreated | function | 订阅创建后调用。接收 { subscription, chargebeeSubscription, plan }。 |
onSubscriptionUpdate | function | 订阅更新时调用。接收 { subscription }。 |
onSubscriptionDeleted | function | 订阅删除时调用。接收 { subscription, chargebeeSubscription }。 |
onTrialStart | function | 试用开始时调用。接收 { subscription }。 |
onTrialEnd | function | 试用结束时调用。接收 { subscription }。 |
计划配置
| 选项 | 类型 | 描述 |
|---|---|---|
name | string | 计划名称。必需。 |
itemPriceId | string | Chargebee 项目价格 ID。必需。 |
itemId | string | Chargebee 项目 ID。可选。 |
itemFamilyId | string | Chargebee 项目系列 ID。可选。 |
type | string | 类型:"plan"、"addon" 或 "charge"。必需。 |
limits | object | 计划限制(例如 { projects: 10, storage: 5 })。 |
freeTrial | object | 试用配置。参见下面的 免费试用配置。 |
trialPeriod | number | 试用期长度。可选。 |
trialPeriodUnit | string | "day" 或 "month"。可选。 |
billingCycles | number | 计费周期数。可选。 |
免费试用配置
| 选项 | 类型 | 描述 |
|---|---|---|
days | number | 试用天数。必需。 |
组织选项
| 选项 | 类型 | 描述 |
|---|---|---|
enabled | boolean | 启用组织客户支持。必需。 |
getCustomerCreateParams | function | 为组织自定义 Chargebee 客户创建参数。接收 organization 和上下文。 |
onCustomerCreate | function | 组织客户创建后的回调函数。接收 { chargebeeCustomer, organization } 和上下文。 |
高级用法
与组织一起使用
Chargebee 插件与 organization 插件 集成,可将组织作为 Chargebee 客户使用。订阅的计费主体不再是单个用户,而是组织。对于 B2B 服务,这很有用,因为计费绑定到组织而不是个人用户。
启用组织客户时:
- 组织首次订阅时,会自动创建 Chargebee 客户
- 组织名称更改会同步到 Chargebee 客户
- 拥有活动订阅的组织无法被删除
启用组织客户
要启用组织客户,请将 organization.enabled 设置为 true,并确保已安装 organization 插件:
plugins: [
organization(),
chargebee({
// ... 其他选项
subscription: {
enabled: true,
plans: [...],
},
organization: {
enabled: true
}
})
]当 organization.enabled: true 时,插件会自动从 user 表中排除 chargebeeCustomerId 并禁用用户级计费钩子。使用仅限组织范围的计费时,无需将该列添加到数据库架构中。
创建组织订阅
即使启用了组织客户,用户订阅仍然可用,并且是默认选项。若要使用组织作为计费主体,请传入 customerType: "organization":
await authClient.subscription.create({
itemPriceId: "team-USD-Monthly",
referenceId: activeOrg.id,
customerType: "organization",
seats: 10,
successUrl: "/org/billing/success",
cancelUrl: "/org/billing"
});授权
请务必实现 authorizeReference 函数,以验证用户是否有权限管理该组织的订阅:
subscription: {
// ... 其他订阅选项
authorizeReference: async ({ user, referenceId, action }) => {
const member = await db.members.findFirst({
where: {
userId: user.id,
organizationId: referenceId
}
});
return member?.role === "owner" || member?.role === "admin";
}
}组织计费邮箱
与用户不同,组织的计费邮箱不会自动同步,因为组织本身没有唯一的邮箱。组织通常会使用一个与用户账户分开的专用计费邮箱。
要在结账后更改计费邮箱,请通过 Chargebee Dashboard 更新,或使用 chargebeeClient 实现自定义逻辑:
await chargebeeClient.customer.update(organization.chargebeeCustomerId, {
email: "[email protected]"
});自定义 Hosted Page 参数
你可以为 Chargebee Hosted Page 自定义附加参数:
getHostedPageParams: async ({ user, session, plan, subscription }, request, ctx) => {
return {
embed: false,
layout: "in_app",
pass_thru_content: JSON.stringify({
userId: user.id,
planType: "business"
}),
redirect_url: "https://yourdomain.com/success",
cancel_url: "https://yourdomain.com/cancel"
};
}试用期管理
Chargebee 插件会自动防止用户获得多个免费试用期。一旦用户使用过一次试用期(无论哪个计划),他们就不再有资格在任何计划上获得额外试用。
工作原理:
- 系统会跟踪每个用户在所有计划中的试用使用情况
- 当用户订阅带有试用的计划时,系统会检查其订阅历史记录
- 如果用户曾经有过试用(通过
trialStart/trialEnd字段或in_trial状态标识),则不会提供新的试用 - 这可以防止用户取消订阅后重新注册以获得多个免费试用
示例场景:
- 用户订阅带有 7 天试用期的 "Starter" 计划
- 用户在试用期结束后取消订阅
- 用户尝试订阅 "Premium" 计划——不会提供试用
- 用户将立即被收取 Premium 计划费用
启用 preventDuplicateTrials 后,这种行为会自动生效,无需额外配置。试用资格会在创建订阅时确定,且不能通过配置覆盖。
故障排查
列/字段命名错误
如果你看到类似 no such column: "chargebee_customer_id" 或 no such column: "chargebeeCustomerId" 的错误:
原因: 数据库列名与适配器的 schema 定义不匹配。
解决方案:
- 运行
npx auth generate,使用 Chargebee 插件字段重新生成 schema。 - 运行
npx auth migrate将迁移应用到数据库,或使用 ORM 的迁移工具。 - 如果是从另一个适配器手动迁移,请确保列名符合数据库适配器的命名约定。
- 查阅数据库文档,了解与你的适配器(Prisma、Drizzle、Kysely 等)对应的字段名映射。
Webhook 问题
如果 webhook 没有被正确处理:
- 检查 webhook URL 是否已在 Chargebee dashboard 中正确配置
- 验证 Basic Auth 凭据(
webhookUsername和webhookPassword)是否正确 - 确保已在 Chargebee dashboard 中选择所有必要事件
- 检查服务器日志中 webhook 处理期间是否有任何错误
订阅状态问题
如果订阅状态没有正确更新:
- 确保 webhook 事件已被接收并处理
- 检查
chargebeeCustomerId和chargebeeSubscriptionId字段是否已正确填充 - 验证应用和 Chargebee 之间的 reference ID 是否匹配
在本地测试 Webhook
对于本地开发,你可以使用隧道(例如 ngrok)将 webhook 转发到本地环境:
ngrok http 3000然后将 Chargebee webhook 配置为指向:
https://your-ngrok-url/api/auth/chargebee/webhook请确保在 Chargebee 和本地环境变量中使用相同的 Basic Auth 凭据。