From f9949b6027e2ba07fac6f71098eedcd78a45b1b4 Mon Sep 17 00:00:00 2001 From: cb-alish Date: Wed, 4 Mar 2026 18:23:41 +0530 Subject: [PATCH] Added documentation for chargebee better-auth. --- docs/content/docs/plugins/chargebee.mdx | 1068 +++++++++++++++++++++++ 1 file changed, 1068 insertions(+) create mode 100644 docs/content/docs/plugins/chargebee.mdx diff --git a/docs/content/docs/plugins/chargebee.mdx b/docs/content/docs/plugins/chargebee.mdx new file mode 100644 index 0000000000..477290ba9d --- /dev/null +++ b/docs/content/docs/plugins/chargebee.mdx @@ -0,0 +1,1068 @@ +--- +title: Chargebee +description: Chargebee plugin for Better Auth to manage subscriptions and payments. +--- + +The Chargebee plugin integrates Chargebee's subscription management and billing functionality with Better Auth. Since payment and authentication are often tightly coupled, this plugin simplifies the integration of Chargebee into your application, handling customer creation, subscription management, and webhook processing. + +## Features + +- Create Chargebee customers automatically when users sign up +- Manage subscription plans and pricing (item-based: plans, addons, charges) +- Process subscription lifecycle events (creation, updates, cancellations) +- Handle Chargebee webhooks securely with Basic Auth verification +- Expose subscription data to your application +- Support for trial periods and multi-item subscriptions +- **Automatic trial abuse prevention** - Users can only get one trial per account across all plans +- Flexible reference system to associate subscriptions with users or organizations +- Team subscription support with seats management +- Hosted checkout and portal via Chargebee Hosted Pages +- Self-service billing portal for managing payment methods, invoices, and subscriptions + +## Installation + + + + ### Install the plugin + + First, install the plugin: + + ```package-install + @chargebee/better-auth + ``` + + If you're using a separate client and server setup, make sure to install the plugin in both parts of your project. + + + + ### Install the Chargebee SDK + + Next, install the Chargebee SDK on your server: + + ```package-install + chargebee + ``` + + + ### Add the plugin to your auth config + + ```ts title="auth.ts" + 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({ + // ... your existing config + plugins: [ + chargebee({ + chargebeeClient, + createCustomerOnSignUp: true, + webhookUsername: process.env.CHARGEBEE_WEBHOOK_USERNAME, + webhookPassword: process.env.CHARGEBEE_WEBHOOK_PASSWORD, + }) + ] + }) + ``` + + + ### Add the client plugin + + ```ts title="auth-client.ts" + import { createAuthClient } from "better-auth/client" + import { chargebeeClient } from "@chargebee/better-auth/client" + + export const authClient = createAuthClient({ + // ... your existing config + plugins: [ + chargebeeClient({ + subscription: true //if you want to enable subscription management + }) + ] + }) + ``` + + + ### Migrate the database + + Run the migration or generate the schema to add the necessary tables to the database. + + + + ```package-install + npx @better-auth/cli migrate + ``` + + + ```package-install + npx @better-auth/cli generate + ``` + + + See the [Schema](#schema) section to add the tables manually. + + + ### Set up Chargebee webhooks + + Create a webhook endpoint in your Chargebee dashboard pointing to: + + ``` + https://your-domain.com/api/auth/chargebee/webhook + ``` + `/api/auth` is the default path for the auth server. + + Make sure to select at least these events: + - `subscription_created` + - `subscription_activated` + - `subscription_changed` + - `subscription_renewed` + - `subscription_started` + - `subscription_cancelled` + - `subscription_cancellation_scheduled` + - `customer_deleted` + + If you set `webhookUsername` and `webhookPassword`, configure the same Basic Authentication credentials in the Chargebee webhook settings. + + + +## Usage + +### Customer Management + +You can use this plugin solely for customer management without enabling subscriptions. This is useful if you just want to link Chargebee customers to your users. + +When you set `createCustomerOnSignUp: true`, a Chargebee customer is automatically created on signup and linked to the user in your database. +You can customize the customer creation process: + +```ts title="auth.ts" +chargebee({ + // ... other options + createCustomerOnSignUp: true, + onCustomerCreate: async ({ chargebeeCustomer, user }) => { + // Do something with the newly created customer + console.log(`Customer ${chargebeeCustomer.id} created for user ${user.id}`); + }, +}) +``` + +### Subscription Management + +#### Defining Plans + +Chargebee uses an item-based billing model. You can define your subscription plans either statically, dynamically from your database, or by fetching directly from the Chargebee API: + +**Static plans:** + +```ts title="auth.ts" +subscription: { + enabled: true, + plans: [ + { + name: "starter", // the name of the plan, it'll be automatically lower cased when stored in the database + itemPriceId: "starter-USD-Monthly", // the item price ID from Chargebee + type: "plan", + limits: { + projects: 5, + storage: 10 + } + }, + { + name: "pro", + itemPriceId: "pro-USD-Monthly", + type: "plan", + limits: { + projects: 20, + storage: 50 + }, + freeTrial: { + days: 14, + } + } + ] +} +``` + +**Fetch from Chargebee API (Recommended):** + +Fetch plans directly from Chargebee to keep them in sync with your Chargebee configuration: + +```ts title="auth.ts" +// Initialize Chargebee client +const chargebeeClient = new Chargebee({ + apiKey: process.env.CHARGEBEE_API_KEY!, + site: process.env.CHARGEBEE_SITE!, +}) + +// Fetch plans directly from Chargebee +const plansResponse = await chargebeeClient.itemPrice.list({ + item_type: { is: 'plan' }, + status: { is: 'active' }, +}) + +const plans = plansResponse.list.map((item) => ({ + name: item.item_price.name, + itemPriceId: item.item_price.id, + type: 'plan' as const, + limits: { + projects: 10, + storage: 50, + }, + // Add free trial if configured in Chargebee + freeTrial: item.item_price.trial_period + ? { days: item.item_price.trial_period } + : undefined, +})) + +export const auth = betterAuth({ + // ... other config + plugins: [ + chargebee({ + chargebeeClient, + subscription: { + enabled: true, + plans, + } + }) + ] +}) +``` + +**Dynamic plans from database:** + +```ts title="auth.ts" +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) + })); + } +} +``` + +see [plan configuration](#plan-configuration) for more. + +#### Creating a Subscription + +To create a subscription, use the `subscription.upgrade` method: + + +```ts +type upgradeSubscription = { + /** + * The item price ID(s) from Chargebee. Single string or array for multi-item subscriptions. + */ + itemPriceId: string | string[] = "pro-USD-Monthly" + /** + * Reference id of the subscription. Defaults based on customerType. + */ + referenceId?: string = "123" + /** + * The id of the subscription to upgrade. + */ + subscriptionId?: string = "sub_123" + /** + * Additional metadata to store with the subscription. + */ + metadata?: Record + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * Number of seats to upgrade to (if applicable). + */ + seats?: number = 1 + /** + * The URL to which the user is sent when payment or setup is complete. + */ + successUrl: string + /** + * If set, customers are directed here if they cancel. + */ + cancelUrl: string + /** + * The URL to return to from the portal (used when upgrading existing subscriptions) + */ + returnUrl?: string + /** + * Disable redirect after successful subscription. + */ + disableRedirect: boolean = false + /** + * Unix timestamp for when the trial should end. + */ + trialEnd?: number +} +``` + + +**Simple Example:** + +```ts title="client.ts" +await authClient.subscription.upgrade({ + itemPriceId: "pro-USD-Monthly", + successUrl: "/dashboard", + cancelUrl: "/pricing", + referenceId: "org_123", // Optional: defaults based on customerType + seats: 5, // Optional: for team plans +}); +``` + +This will create a Chargebee Hosted Page and redirect the user to the Chargebee checkout page. + + +The plugin only supports one active or trialing subscription per reference ID (user or organization) at a time. Multiple concurrent subscriptions for the same reference ID are not supported. + +If the user already has an active subscription, you **must** provide the `subscriptionId` parameter when upgrading. Otherwise, a new subscription may be created alongside the existing one, resulting in duplicate billing. + + +> **Important:** The `successUrl` parameter will be internally modified to handle race conditions between checkout completion and webhook processing. The plugin creates an intermediate redirect that ensures subscription status is properly updated before redirecting to your success page. + +```ts +const { error } = await authClient.subscription.upgrade({ + itemPriceId: "pro-USD-Monthly", + successUrl: "/dashboard", + cancelUrl: "/pricing", +}); +if(error) { + alert(error.message); +} +``` + +#### Switching Plans + +To switch a subscription to a different plan, use the `subscription.upgrade` method: +```ts title="client.ts" +await authClient.subscription.upgrade({ + itemPriceId: "enterprise-USD-Monthly", // new item price id for upgrade + successUrl: "/dashboard", + cancelUrl: "/pricing", +}); +``` +This ensures that the user only pays for the new plan, and not both. + +#### Canceling a Subscription + +To cancel a subscription: + + +```ts +type cancelSubscription = { + /** + * Reference id of the subscription to cancel. Defaults based on customerType. + */ + referenceId?: string = 'org_123' + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * The id of the subscription to cancel. + */ + subscriptionId?: string = 'sub_123' + /** + * URL to take customers to when they click on the billing portal's link to return to your website. + */ + returnUrl: string = '/account' +} +``` + + +This will redirect the user to the Chargebee Portal where they can cancel their subscription. + + +**Understanding Cancellation States** + +Chargebee supports different cancellation behaviors: + +| Field | Description | +|--------------|-------------------------------------------------------------------| +| `canceledAt` | The time when the subscription was canceled. | +| `status` | Changes to "cancelled" when the subscription has ended. | + + +#### Billing Portal Session + +For a complete self-service billing experience, you can open the Chargebee customer portal where users can manage all aspects of their billing: + + +```ts +type createPortalSession = { + /** + * Reference id of the customer. Defaults based on customerType. + */ + referenceId?: string = 'org_123' + /** + * The type of customer for billing. (Default: "user") + */ + customerType?: "user" | "organization" + /** + * URL to redirect customers to after they complete their portal session. + */ + returnUrl: string = '/account' + /** + * Disable redirect after opening portal. + */ + disableRedirect?: boolean = false +} +``` + + +**Example:** + +```ts title="client.ts" +await authClient.subscription.portal({ + returnUrl: "/account/billing", + fetchOptions: { + onSuccess: (ctx) => { + // Redirect to Chargebee portal + window.location.href = ctx.data.url; + } + } +}); +``` + +**For organization billing:** + +```ts title="client.ts" +await authClient.subscription.portal({ + referenceId: "org_123456", + customerType: "organization", + returnUrl: "/org/billing" +}); +``` + +The portal allows users to: +- Update payment methods (credit cards, bank accounts) +- View and download invoices +- Manage subscriptions (upgrade, downgrade, cancel) +- Update billing address and contact information +- View subscription history +- Apply promotional codes + + +The portal session provides a complete self-service experience and is recommended over individual operations like cancellation when you want to give users full control over their billing. + + +### Reference System + +By default, subscriptions are associated with the user ID. However, you can use a custom reference ID to associate subscriptions with other entities, such as organizations: + +```ts title="client.ts" +// Create a subscription for an organization +await authClient.subscription.upgrade({ + itemPriceId: "team-USD-Monthly", + referenceId: "org_123456", + customerType: "organization", + successUrl: "/dashboard", + cancelUrl: "/pricing", + seats: 10 // Number of seats for team plans +}); + +// List subscriptions for an organization +const { data: subscriptions } = await authClient.subscription.list({ + query: { + referenceId: "org_123456", + customerType: "organization" + } +}); +``` + +#### Team Subscriptions with Seats + +For team or organization plans, you can specify the number of seats: + +```ts +await authClient.subscription.upgrade({ + itemPriceId: "team-USD-Monthly", + referenceId: "org_123456", + customerType: "organization", + seats: 10, // 10 team members + successUrl: "/org/billing/success", + cancelUrl: "/org/billing" +}); +``` + +The `seats` parameter is passed to Chargebee as the quantity for the subscription item. You can use this value in your application logic to limit the number of members in a team or organization. + +To authorize reference IDs, implement the `authorizeReference` function: + +```ts title="auth.ts" +subscription: { + // ... other options + authorizeReference: async ({ user, session, referenceId, action }) => { + // Check if the user has permission to manage subscriptions for this reference + if (action === "upgrade-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 Handling + +The plugin automatically handles common webhook events: + +- `subscription_created`: Creates a subscription when created +- `subscription_activated`: Updates subscription when activated +- `subscription_changed`: Updates subscription when changed +- `subscription_renewed`: Updates on renewal +- `subscription_started`: Updates when trial ends and subscription starts +- `subscription_cancelled`: Marks subscription as canceled +- `subscription_cancellation_scheduled`: Updates with scheduled cancellation +- `customer_deleted`: Cleans up customer and related subscriptions + +You can also handle custom events: + +```ts title="auth.ts" +import { WebhookEventType } from "chargebee" + +chargebee({ + // ... other options + onEvent: async (event) => { + // Handle any Chargebee event + switch (event.event_type) { + case WebhookEventType.PaymentFailed: + // Handle failed payment + break; + case WebhookEventType.InvoiceGenerated: + // Handle generated invoice + break; + } + } +}) +``` + +### Subscription Lifecycle Hooks + +You can hook into various subscription lifecycle events: + +```ts title="auth.ts" +subscription: { + // ... other options + onSubscriptionComplete: async ({ subscription, chargebeeSubscription, plan }) => { + // Called when a subscription is successfully created via hosted page + await sendWelcomeEmail(subscription.referenceId, plan.name); + }, + onSubscriptionCreated: async ({ subscription, chargebeeSubscription, plan }) => { + // Called when a subscription is created + await sendSubscriptionCreatedEmail(subscription.referenceId, plan.name); + }, + onSubscriptionUpdate: async ({ subscription }) => { + // Called when a subscription is updated + console.log(`Subscription ${subscription.id} updated`); + }, + onSubscriptionDeleted: async ({ subscription, chargebeeSubscription }) => { + // Called when a subscription is deleted + await sendCancellationEmail(subscription.referenceId); + }, + onTrialStart: async ({ subscription }) => { + // Called when a trial starts + await sendTrialStartEmail(subscription.referenceId); + }, + onTrialEnd: async ({ subscription }) => { + // Called when a trial ends + await sendTrialEndEmail(subscription.referenceId); + } +} +``` + +### Trial Periods + +You can configure trial periods for your plans: + +```ts title="auth.ts" +{ + name: "pro", + itemPriceId: "pro-USD-Monthly", + type: "plan", + freeTrial: { + days: 14, // 14-day trial automatically applied + }, + limits: { + projects: 100, + storage: 500, + } +} +``` + +When a user subscribes to this plan, **the trial is automatically applied** - no need to pass `trialEnd` manually: + +```ts +// Trial is automatically calculated and applied based on plan config +await authClient.subscription.upgrade({ + itemPriceId: "pro-USD-Monthly", // Plan with 14-day trial + successUrl: "/dashboard", + cancelUrl: "/pricing", +}); +// ✅ User gets 14-day trial automatically! +``` + +The plugin calculates the trial end date as: **current date + trial days**. + +#### Prevent Duplicate Trials + +To prevent users from getting multiple trials, enable `preventDuplicateTrails`: + +```ts +subscription: { + enabled: true, + plans, + preventDuplicateTrails: true, // Users can only get one trial +} +``` + +#### Override Trial End Date (Optional) + +To set a custom trial end date, pass `trialEnd` (Unix timestamp): + +```ts +await authClient.subscription.upgrade({ + itemPriceId: "pro-USD-Monthly", + successUrl: "/dashboard", + cancelUrl: "/pricing", + trialEnd: 1735689600, // Custom trial end: Jan 1, 2025 +}); +``` + + +Trials only work for **new subscriptions**. Upgrades to existing subscriptions cannot have trials (Chargebee limitation). + + +## Schema + +The Chargebee plugin adds the following tables to your database: + + +### User + +Table Name: `user` + + + +### Organization + +Table Name: `organization` (only when `organization.enabled` is `true`) + + + +### Subscription + +Table Name: `subscription` + + + +### Subscription Item + +Table Name: `subscriptionItem` + + + +### Customizing the Schema + +To change the schema table names or fields, you can pass a `schema` option to the Chargebee plugin (if supported): + +```ts title="auth.ts" +chargebee({ + // ... other options + schema: { + subscription: { + modelName: "chargebeeSubscriptions", // map the subscription table to chargebeeSubscriptions + fields: { + referenceId: "userId" // map the referenceId field to userId + } + } + } +}) +``` + +## Options + +| Option | Type | Description | +| ------------------------- | ---------- | --------------------------------------------------------------------------------------------- | +| `chargebeeClient` | `Chargebee`| The Chargebee client instance. **Required.** | +| `webhookUsername` | `string` | Username for Basic Auth on the webhook endpoint. Recommended in production. | +| `webhookPassword` | `string` | Password for Basic Auth on the webhook endpoint. Recommended in production. | +| `createCustomerOnSignUp` | `boolean` | Whether to automatically create a Chargebee customer when a user signs up. Default: `false`. | +| `onCustomerCreate` | `function` | Callback called after a customer is created. Receives `{ chargebeeCustomer, user }`. | +| `onEvent` | `function` | Callback called for any Chargebee webhook event. Receives the event object. | +| `subscription` | `object` | Subscription configuration. See [below](#subscription-options). | +| `organization` | `object` | Enable Organization Customer support. See [below](#organization-options). | + +### Subscription Options + +| Option | Type | Description | +| -------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `enabled` | `boolean` | Whether to enable subscription functionality. **Required.** | +| `plans` | `ChargebeePlan[]` or `function` | An array of subscription plans or an async function that returns plans. **Required** if enabled. | +| `requireEmailVerification` | `boolean` | Whether to require email verification before allowing subscription upgrades. Default: `false`. | +| `preventDuplicateTrails` | `boolean` | Prevent users from getting multiple trials. Default: `false`. | +| `authorizeReference` | `function` | Authorize reference IDs. Receives `{ user, session, referenceId, action }` and context. | +| `getHostedPageParams` | `function` | Customize Chargebee Hosted Page parameters. Receives `{ user, session, plan, subscription }`, request, and context. | +| `onSubscriptionComplete` | `function` | Called when a subscription is created via hosted page. Receives `{ subscription, chargebeeSubscription, plan }`. | +| `onSubscriptionCreated` | `function` | Called when a subscription is created. Receives `{ subscription, chargebeeSubscription, plan }`. | +| `onSubscriptionUpdate` | `function` | Called when a subscription is updated. Receives `{ subscription }`. | +| `onSubscriptionDeleted` | `function` | Called when a subscription is deleted. Receives `{ subscription, chargebeeSubscription }`. | +| `onTrialStart` | `function` | Called when a trial starts. Receives `{ subscription }`. | +| `onTrialEnd` | `function` | Called when a trial ends. Receives `{ subscription }`. | + +#### Plan Configuration + +| Option | Type | Description | +| ------------------------- | ---------- | ------------------------------------------------------------ | +| `name` | `string` | The name of the plan. **Required.** | +| `itemPriceId` | `string` | The Chargebee item price ID. **Required.** | +| `itemId` | `string` | The Chargebee item ID. Optional. | +| `itemFamilyId` | `string` | The Chargebee item family ID. Optional. | +| `type` | `string` | Type: `"plan"`, `"addon"`, or `"charges"`. **Required.** | +| `limits` | `object` | Limits for plan (e.g. `{ projects: 10, storage: 5 }`). | +| `freeTrial` | `object` | Trial configuration. See [below](#free-trial-configuration). | +| `trialPeriod` | `number` | Trial period length. Optional. | +| `trialPeriodUnit` | `string` | `"day"` or `"month"`. Optional. | +| `billingCycles` | `number` | Number of billing cycles. Optional. | + +#### Free Trial Configuration + +| Option | Type | Description | +| ---------------- | ---------- | ---------------------------------------------------------------------------------------- | +| `days` | `number` | Number of trial days. **Required.** | + +### Organization Options + +| Option | Type | Description | +| ------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------ | +| `enabled` | `boolean` | Enable Organization Customer support. **Required.** | +| `getCustomerCreateParams` | `function` | Customize Chargebee customer creation parameters for organizations. Receives `organization` and context. | +| `onCustomerCreate` | `function` | Called after an organization customer is created. Receives `{ chargebeeCustomer, organization }` and context. | + +## Advanced Usage + +### Using with Organizations + +The Chargebee plugin integrates with the [organization plugin](/docs/plugins/organization) to enable organizations as Chargebee Customers. Instead of individual users, organizations become the billing entity for subscriptions. This is useful for B2B services where billing is tied to the organization rather than individual user. + + +**When Organization Customer is enabled:** + +- A Chargebee Customer is automatically created when an organization first subscribes +- Organization name changes are synced to the Chargebee Customer +- Organizations with active subscriptions cannot be deleted + + +#### Enabling Organization Customer + +To enable Organization Customer, set `organization.enabled` to `true` and ensure the organization plugin is installed: + +```ts title="auth.ts" +plugins: [ + organization(), + chargebee({ + // ... other options + subscription: { + enabled: true, + plans: [...], + }, + organization: { // [!code highlight] + enabled: true // [!code highlight] + } // [!code highlight] + }) +] +``` + +#### Creating Organization Subscriptions + +Even with Organization Customer enabled, user subscriptions remain available and are the default. To use the organization as the billing entity, pass `customerType: "organization"`: + +```ts title="client.ts" +await authClient.subscription.upgrade({ + itemPriceId: "team-USD-Monthly", + referenceId: activeOrg.id, + customerType: "organization", // [!code highlight] + seats: 10, + successUrl: "/org/billing/success", + cancelUrl: "/org/billing" +}); +``` + +#### Authorization + +Make sure to implement the `authorizeReference` function to verify that the user has permission to manage subscriptions for the organization: + +```ts title="auth.ts" +subscription: { + // ... other subscription options + authorizeReference: async ({ user, referenceId, action }) => { + const member = await db.members.findFirst({ + where: { + userId: user.id, + organizationId: referenceId + } + }); + + return member?.role === "owner" || member?.role === "admin"; + } +} +``` + +#### Organization Billing Email + +Unlike users, organization billing email is not automatically synced because organization itself doesn't have a unique email. Organizations often use a dedicated billing email separate from user accounts. +To change the billing email after checkout, update it through the Chargebee Dashboard or implement custom logic using `chargebeeClient`: + +```ts +await chargebeeClient.customer.update(organization.chargebeeCustomerId, { + email: "billing@company.com" +}); +``` + +### Custom Hosted Page Parameters + +You can customize the Chargebee Hosted Page with additional parameters: + +```ts title="auth.ts" +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" + }; +} +``` + +### Trial Period Management + +The Chargebee plugin automatically prevents users from getting multiple free trials. Once a user has used a trial period (regardless of which plan), they will not be eligible for additional trials on any plan. + +**How it works:** +- The system tracks trial usage across all plans for each user +- When a user subscribes to a plan with a trial, the system checks their subscription history +- If the user has ever had a trial (indicated by `trialStart`/`trialEnd` fields or `in_trial` status), no new trial will be offered +- This prevents abuse where users cancel subscriptions and resubscribe to get multiple free trials + +**Example scenario:** +1. User subscribes to "Starter" plan with 7-day trial +2. User cancels the subscription after the trial +3. User tries to subscribe to "Premium" plan - no trial will be offered +4. User will be charged immediately for the Premium plan + +This behavior is automatic and requires no additional configuration when `preventDuplicateTrails` is enabled. The trial eligibility is determined at the time of subscription creation and cannot be overridden through configuration. + +## Troubleshooting + +### Column/field naming errors + +If you see errors like `no such column: "chargebee_customer_id"` or `no such column: "chargebeeCustomerId"`: + +**Cause:** Mismatch between your database column names and your adapter's schema definition. + +**Solution:** + +1. Run `npx @better-auth/cli generate` to regenerate your schema with the Chargebee plugin fields +2. Apply the migration to your database +3. If manually migrating from another adapter, ensure your column names match your database adapter's conventions +4. Refer to the [Better Auth adapter documentation](https://www.better-auth.com/docs/concepts/database) for field name mapping specific to your adapter (Prisma, Drizzle, Kysely, etc.) + +### Webhook Issues + +If webhooks aren't being processed correctly: + +1. Check that your webhook URL is correctly configured in the Chargebee dashboard +2. Verify that the Basic Auth credentials (`webhookUsername` and `webhookPassword`) are correct +3. Ensure you've selected all the necessary events in the Chargebee dashboard +4. Check your server logs for any errors during webhook processing + +### Subscription Status Issues + +If subscription statuses aren't updating correctly: + +1. Make sure the webhook events are being received and processed +2. Check that the `chargebeeCustomerId` and `chargebeeSubscriptionId` fields are correctly populated +3. Verify that the reference IDs match between your application and Chargebee + +### Testing Webhooks Locally + +For local development, you can use a tunnel (e.g. ngrok) to forward webhooks to your local environment: + +```bash +ngrok http 3000 +``` + +Then configure your Chargebee webhook to point to: + +``` +https://your-ngrok-url/api/auth/chargebee/webhook +``` + +Make sure to use the same Basic Auth credentials in Chargebee and in your local environment variables. + +## Resources + +- [Chargebee Documentation](https://www.chargebee.com/docs/) +- [Better Auth Documentation](https://www.better-auth.com/)