Commet
使用 Commet 进行计费和订阅的更佳认证插件
Commet 是适用于 SaaS 和 AI 产品的计费和支付解决方案。作为代理商(Merchant of Record),Commet 处理订阅、基于使用的计费、税务合规和全球支付——让您几分钟内即可开始实现盈利。
此插件由 Commet 团队维护。如遇错误、问题或功能请求, 请联系Commet 支持。
功能
- 注册时自动创建客户
- 客户门户用于自助式账单管理
- 订阅管理(获取、更改计划、取消)
- 功能访问控制(布尔值、计量、席位)
- 计量计费的使用跟踪
- 按用户定价的席位管理
- 可选的带签名验证的 webhook 处理
安装
pnpm add better-auth @commet/better-auth @commet/node准备工作
在 Commet 控制面板 获取您的 API 密钥。
COMMET_API_KEY=ck_...
COMMET_ENVIRONMENT=sandbox # 或 production服务器配置
import { betterAuth } from "better-auth";
import {
commet,
portal,
subscriptions,
features,
usage,
seats,
} from "@commet/better-auth";
import { Commet } from "@commet/node";
const commetClient = new Commet({
apiKey: process.env.COMMET_API_KEY,
environment: process.env.COMMET_ENVIRONMENT, // 'sandbox' 或 'production'
});
export const auth = betterAuth({
// ... 您的配置
plugins: [
commet({
client: commetClient,
createCustomerOnSignUp: true,
use: [
portal(),
subscriptions(),
features(),
usage(),
seats(),
],
}),
],
});客户端配置
import { createAuthClient } from "better-auth/react";
import { commetClient } from "@commet/better-auth";
export const authClient = createAuthClient({
plugins: [commetClient()],
});配置选项
commet({
client: commetClient, // 必填:Commet SDK 实例
createCustomerOnSignUp: true, // 注册时自动创建客户
getCustomerCreateParams: ({ user }) => ({
legalName: user.name,
metadata: { source: "web" },
}),
use: [/* 插件列表 */],
})启用 createCustomerOnSignUp 后,Commet 会自动创建一个客户,externalId 设置为用户 ID,无需数据库映射。
门户插件
将用户重定向至 Commet 客户门户,进行自助账单管理。
import { commet, portal } from "@commet/better-auth";
commet({
client: commetClient,
use: [
portal({ returnUrl: "/dashboard" }),
],
})// 跳转至 Commet 客户门户
await authClient.customer.portal();订阅插件
管理客户订阅。
import { commet, subscriptions } from "@commet/better-auth";
commet({
client: commetClient,
use: [subscriptions()],
})// 获取当前订阅
const { data: subscription } = await authClient.subscription.get();
// 更改计划
await authClient.subscription.changePlan({
subscriptionId: "sub_xxx",
planCode: "enterprise",
billingInterval: "yearly",
});
// 取消订阅
await authClient.subscription.cancel({
subscriptionId: "sub_xxx",
reason: "太贵了",
immediate: false, // 期末取消
});功能插件
检查认证用户的功能访问权限。
import { commet, features } from "@commet/better-auth";
commet({
client: commetClient,
use: [features()],
})// 列出所有功能
const { data: featuresList } = await authClient.features.list();
// 获取特定功能
const { data: feature } = await authClient.features.get("api_calls");
// 检查功能是否启用(布尔值)
const { data: check } = await authClient.features.check("sso");
// 检查用户是否可以多使用一个单位(计量)
const { data: canUse } = await authClient.features.canUse("api_calls");
// 返回: { allowed: boolean, willBeCharged: boolean }使用插件
跟踪计量计费的使用事件。
import { commet, usage } from "@commet/better-auth";
commet({
client: commetClient,
use: [usage()],
})await authClient.usage.track({
eventType: "api_call",
value: 1,
idempotencyKey: `evt_${Date.now()}`,
properties: { endpoint: "/api/generate" },
});认证用户会自动关联到事件。
席位插件
管理基于席位的许可证。
import { commet, seats } from "@commet/better-auth";
commet({
client: commetClient,
use: [seats()],
})// 列出所有席位余额
const { data: seatBalances } = await authClient.seats.list();
// 添加席位
await authClient.seats.add({ seatType: "member", count: 5 });
// 移除席位
await authClient.seats.remove({ seatType: "member", count: 2 });
// 设置精确数量
await authClient.seats.set({ seatType: "admin", count: 3 });
// 一次设置所有席位类型数量
await authClient.seats.setAll({ admin: 2, member: 10, viewer: 50 });Webhooks 插件(可选)
处理 Commet webhooks。此功能可选,因为您始终可以直接查询状态。
import { commet, webhooks } from "@commet/better-auth";
commet({
client: commetClient,
use: [
webhooks({
secret: process.env.COMMET_WEBHOOK_SECRET,
onPayload: (payload) => {
// 通用处理器
},
onSubscriptionCreated: (payload) => {},
onSubscriptionActivated: (payload) => {},
onSubscriptionCanceled: (payload) => {},
onSubscriptionUpdated: (payload) => {},
}),
],
})在 Commet 控制面板中配置 webhook 端点:/api/auth/commet/webhooks
完整示例
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import {
commet as commetPlugin,
portal,
subscriptions,
features,
usage,
seats,
} from "@commet/better-auth";
import { Commet } from "@commet/node";
import { db } from "./db";
import * as schema from "./schema";
const commetClient = new Commet({
apiKey: process.env.COMMET_API_KEY!,
environment: process.env.COMMET_ENVIRONMENT as "sandbox" | "production",
});
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg", schema }),
emailAndPassword: { enabled: true },
plugins: [
commetPlugin({
client: commetClient,
createCustomerOnSignUp: true,
getCustomerCreateParams: ({ user }) => ({
legalName: user.name,
}),
use: [
portal({ returnUrl: "/dashboard" }),
subscriptions(),
features(),
usage(),
seats(),
],
}),
],
});import { createAuthClient } from "better-auth/react";
import { commetClient } from "@commet/better-auth";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL,
plugins: [commetClient()],
});
export const { signIn, signUp, signOut, useSession } = authClient;"use client";
import { authClient } from "@/lib/auth-client";
export function BillingSection() {
const handlePortal = async () => {
await authClient.customer.portal();
};
const checkFeature = async () => {
const { data } = await authClient.features.canUse("api_calls");
if (data?.allowed) {
// 执行操作
await authClient.usage.track({ eventType: "api_call" });
}
};
return (
<div>
<button onClick={handlePortal}>管理账单</button>
<button onClick={checkFeature}>使用功能</button>
</div>
);
}