mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 23:53:17 -05:00
345 lines
7.9 KiB
Plaintext
345 lines
7.9 KiB
Plaintext
---
|
|
title: Commet
|
|
description: Better Auth Plugin for Billing and Subscriptions using Commet
|
|
---
|
|
|
|
[Commet](https://commet.co) is the billing and payments solution for SaaS and AI products. As a Merchant of Record, Commet handles subscriptions, usage-based billing, tax compliance, and global payments—so you can start monetizing in minutes.
|
|
|
|
<Callout>
|
|
This plugin is maintained by the Commet team. For bugs, issues or feature requests,
|
|
please contact [Commet support](https://commet.co).
|
|
</Callout>
|
|
|
|
## Features
|
|
|
|
- Automatic customer creation on signup
|
|
- Customer Portal for self-service billing management
|
|
- Subscription management (get, change plan, cancel)
|
|
- Feature access control (boolean, metered, seats)
|
|
- Usage tracking for metered billing
|
|
- Seat management for per-user pricing
|
|
- Optional webhook handling with signature verification
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pnpm add better-auth @commet/better-auth @commet/node
|
|
```
|
|
|
|
## Preparation
|
|
|
|
Get your API key from the [Commet dashboard](https://sandbox.commet.co).
|
|
|
|
```bash title=".env"
|
|
COMMET_API_KEY=ck_...
|
|
COMMET_ENVIRONMENT=sandbox # or production
|
|
```
|
|
|
|
## Server Configuration
|
|
|
|
```typescript title="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
|
|
|
|
```typescript title="auth-client.ts"
|
|
import { createAuthClient } from "better-auth/react";
|
|
import { commetClient } from "@commet/better-auth";
|
|
|
|
export const authClient = createAuthClient({
|
|
plugins: [commetClient()],
|
|
});
|
|
```
|
|
|
|
## Configuration Options
|
|
|
|
```typescript
|
|
commet({
|
|
client: commetClient, // Required: Commet SDK instance
|
|
createCustomerOnSignUp: true, // Auto-create customer on signup
|
|
getCustomerCreateParams: ({ user }) => ({
|
|
legalName: user.name,
|
|
metadata: { source: "web" },
|
|
}),
|
|
use: [/* plugins */],
|
|
})
|
|
```
|
|
|
|
When `createCustomerOnSignUp` is enabled, a Commet customer is automatically created with `externalId` set to the user's ID. No database mapping required.
|
|
|
|
## Portal Plugin
|
|
|
|
Redirects users to the Commet customer portal for self-service billing management.
|
|
|
|
```typescript title="Server"
|
|
import { commet, portal } from "@commet/better-auth";
|
|
|
|
commet({
|
|
client: commetClient,
|
|
use: [
|
|
portal({ returnUrl: "/dashboard" }),
|
|
],
|
|
})
|
|
```
|
|
|
|
```typescript title="Client"
|
|
// Redirects to Commet customer portal
|
|
await authClient.customer.portal();
|
|
```
|
|
|
|
## Subscriptions Plugin
|
|
|
|
Manage customer subscriptions.
|
|
|
|
```typescript title="Server"
|
|
import { commet, subscriptions } from "@commet/better-auth";
|
|
|
|
commet({
|
|
client: commetClient,
|
|
use: [subscriptions()],
|
|
})
|
|
```
|
|
|
|
```typescript title="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.
|
|
|
|
```typescript title="Server"
|
|
import { commet, features } from "@commet/better-auth";
|
|
|
|
commet({
|
|
client: commetClient,
|
|
use: [features()],
|
|
})
|
|
```
|
|
|
|
```typescript title="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.
|
|
|
|
```typescript title="Server"
|
|
import { commet, usage } from "@commet/better-auth";
|
|
|
|
commet({
|
|
client: commetClient,
|
|
use: [usage()],
|
|
})
|
|
```
|
|
|
|
```typescript title="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.
|
|
|
|
```typescript title="Server"
|
|
import { commet, seats } from "@commet/better-auth";
|
|
|
|
commet({
|
|
client: commetClient,
|
|
use: [seats()],
|
|
})
|
|
```
|
|
|
|
```typescript title="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 Plugin (Optional)
|
|
|
|
Handle Commet webhooks. This is optional since you can always query state directly.
|
|
|
|
```typescript title="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) => {},
|
|
}),
|
|
],
|
|
})
|
|
```
|
|
|
|
Configure the webhook endpoint in your Commet dashboard: `/api/auth/commet/webhooks`
|
|
|
|
## Full Example
|
|
|
|
```typescript title="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(),
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
```
|
|
|
|
```typescript title="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;
|
|
```
|
|
|
|
```tsx title="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>
|
|
);
|
|
}
|
|
```
|
|
|