Commet

Commet 是针对 SaaS 和 AI 产品的账单与支付解决方案。作为记录商(Merchant of Record),Commet 处理订阅、按使用量计费、税务合规和全球支付 - 让你几分钟内即可开始盈利。

这个插件由 Commet 团队维护。如遇到漏洞、问题或功能请求,请联系 Commet 支持

特性

🌐 Features

  • 注册时自动创建客户
  • 客户门户用于自助账单管理
  • 订阅管理(获取、更改计划、取消)
  • 功能访问控制(布尔值、计量、座位)
  • 计量计费的使用追踪
  • 每用户定价的座位管理
  • 可选的带签名验证的 Webhook 处理

安装

🌐 Installation

pnpm add better-auth @commet/better-auth @commet/node

准备

🌐 Preparation

Commet 控制面板 获取你的 API 密钥。

🌐 Get your API key from the Commet dashboard.

.env
COMMET_API_KEY=ck_...
COMMET_ENVIRONMENT=sandbox # or production

服务器配置

🌐 Server Configuration

auth.ts
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' or 'production'
});

export const auth = betterAuth({
  // ... your config
  plugins: [
    commet({
      client: commetClient,
      createCustomerOnSignUp: true,
      use: [
        portal(),
        subscriptions(),
        features(),
        usage(),
        seats(),
      ],
    }),
  ],
});

客户端配置

🌐 Client Configuration

auth-client.ts
import { createAuthClient } from "better-auth/react";
import { commetClient } from "@commet/better-auth";

export const authClient = createAuthClient({
  plugins: [commetClient()],
});

配置选项

🌐 Configuration Options

commet({
  client: commetClient,                    // Required: Commet SDK instance
  createCustomerOnSignUp: true,            // Auto-create customer on signup
  getCustomerCreateParams: ({ user }) => ({
    legalName: user.name,
    metadata: { source: "web" },
  }),
  use: [/* plugins */],
})

当启用 createCustomerOnSignUp 时,会自动创建一个 Commet 客户,并将 externalId 设置为用户的 ID。无需数据库映射。

🌐 When createCustomerOnSignUp is enabled, a Commet customer is automatically created with externalId set to the user's ID. No database mapping required.

门户插件

🌐 Portal Plugin

将用户重定向到 Commet 客户门户,自助管理账单。

🌐 Redirects users to the Commet customer portal for self-service billing management.

Server
import { commet, portal } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [
    portal({ returnUrl: "/dashboard" }),
  ],
})
Client
// Redirects to Commet customer portal
await authClient.customer.portal();

订阅插件

🌐 Subscriptions Plugin

管理客户订阅。

🌐 Manage customer subscriptions.

Server
import { commet, subscriptions } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [subscriptions()],
})
Client
// Get current subscription
const { data: subscription } = await authClient.subscription.get();

// Change plan
await authClient.subscription.changePlan({
  subscriptionId: "sub_xxx",
  planCode: "enterprise",
  billingInterval: "yearly",
});

// Cancel subscription
await authClient.subscription.cancel({
  subscriptionId: "sub_xxx",
  reason: "Too expensive",
  immediate: false, // Cancel at period end
});

功能插件

🌐 Features Plugin

检查经过身份验证的用户的功能访问权限。

🌐 Check feature access for the authenticated user.

Server
import { commet, features } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [features()],
})
Client
// List all features
const { data: featuresList } = await authClient.features.list();

// Get specific feature
const { data: feature } = await authClient.features.get("api_calls");

// Check if feature is enabled (boolean)
const { data: check } = await authClient.features.check("sso");

// Check if user can use one more unit (metered)
const { data: canUse } = await authClient.features.canUse("api_calls");
// Returns: { allowed: boolean, willBeCharged: boolean }

使用插件

🌐 Usage Plugin

跟踪计量计费的使用事件。

🌐 Track usage events for metered billing.

Server
import { commet, usage } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [usage()],
})
Client
await authClient.usage.track({
  eventType: "api_call",
  value: 1,
  idempotencyKey: `evt_${Date.now()}`,
  properties: { endpoint: "/api/generate" },
});

经过身份验证的用户会自动与该事件关联。

🌐 The authenticated user is automatically associated with the event.

座位插件

🌐 Seats Plugin

管理基于席位的许可证。

🌐 Manage seat-based licenses.

Server
import { commet, seats } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [seats()],
})
Client
// List all seat balances
const { data: seatBalances } = await authClient.seats.list();

// Add seats
await authClient.seats.add({ seatType: "member", count: 5 });

// Remove seats
await authClient.seats.remove({ seatType: "member", count: 2 });

// Set exact count
await authClient.seats.set({ seatType: "admin", count: 3 });

// Set all seat types at once
await authClient.seats.setAll({ admin: 2, member: 10, viewer: 50 });

Webhooks 插件(可选)

🌐 Webhooks Plugin (Optional)

处理 Commet 的 webhooks。这是可选的,因为你始终可以直接查询状态。

🌐 Handle Commet webhooks. This is optional since you can always query state directly.

Server
import { commet, webhooks } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [
    webhooks({
      secret: process.env.COMMET_WEBHOOK_SECRET,
      onPayload: (payload) => {
        // Catch-all handler
      },
      onSubscriptionCreated: (payload) => {},
      onSubscriptionActivated: (payload) => {},
      onSubscriptionCanceled: (payload) => {},
      onSubscriptionUpdated: (payload) => {},
    }),
  ],
})

在你的 Commet 仪表板中配置 webhook 端点:/api/auth/commet/webhooks

🌐 Configure the webhook endpoint in your Commet dashboard: /api/auth/commet/webhooks

完整示例

🌐 Full Example

auth.ts
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(),
      ],
    }),
  ],
});
auth-client.ts
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;
dashboard.tsx
"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) {
      // Proceed with action
      await authClient.usage.track({ eventType: "api_call" });
    }
  };

  return (
    <div>
      <button onClick={handlePortal}>Manage Billing</button>
      <button onClick={checkFeature}>Use Feature</button>
    </div>
  );
}

On this page