@chargebee/better-auth Plugin Issues
Tested with @chargebee/better-auth + better-auth@1.0.0-beta, organization-scoped billing (organization.enabled: true).
Problem 1: user.chargebeeCustomerId column always required even in org mode
File: src/schema.ts
userSchema is always merged into the plugin schema regardless of whether organization.enabled: true:
export function getSchema(options: ChargebeeOptions) {
return {
...userSchema, // ← always included, even in org mode
...subscriptionSchema,
...subscriptionItemSchema,
...(options.organization?.enabled ? orgSchema : {}), // ← conditional
};
}
When organization.enabled: true, billing is org-scoped and user.chargebeeCustomerId serves no purpose. But the plugin still declares it in the schema, causing Better Auth's adapter to query it. If the column doesn't exist in the DB, every webhook call crashes:
ERROR: column user.chargebee_customer_id does not exist
Expected: userSchema should be excluded when organization.enabled: true.
Problem 2: handleCustomerDeletion always queries user table as fallback
File: src/webhook-handler.ts
Even when customerType === "organization" is correctly identified and handled, the code unconditionally runs a fallback query against the user table:
// Runs for EVERY customer deletion, even org-type customers
try {
const users = await ctx.adapter.findMany({
model: "user",
where: [{ field: "chargebeeCustomerId", value: customer.id }],
});
for (const user of users) {
await ctx.adapter.update({
model: "user",
update: { chargebeeCustomerId: null },
where: [{ field: "id", value: user.id }],
});
}
} catch (e) {
ctx.logger.error("Error clearing chargebeeCustomerId from users:", e);
}
This crashes if user.chargebeeCustomerId doesn't exist (see Problem 1). It should be guarded by org mode:
// Only run user fallback if NOT in org mode
if (!_options.organization?.enabled) {
// fallback user query
}
Problem 3: /subscription/success redirect endpoint is never registered
File: src/index.ts
The plugin builds redirect_url pointing to {baseURL}/subscription/success in both checkoutNewForItems and checkoutExistingForItems, but this endpoint is never registered in the plugin's endpoints export:
endpoints: {
chargebeeWebhook: getWebhookEndpoint(options),
upgradeSubscription: upgradeSubscription(options),
cancelSubscription: cancelSubscription(options),
cancelSubscriptionCallback: cancelSubscriptionCallback(options),
createPortalSession: createPortalSession(options),
// /subscription/success is missing
},
After Chargebee checkout, the browser is redirected to {baseURL}/subscription/success?callbackURL=...&subscriptionId=...&state=succeeded. Since the endpoint doesn't exist, it returns 404.
Expected: Either register the endpoint in the plugin, or clearly document that developers must handle it manually.
Workaround: Manually register a handler before the Better Auth catch-all:
app.get('/api/auth/subscription/success', (req, res) => {
const callbackURL = req.query.callbackURL;
if (callbackURL) return res.redirect(callbackURL);
return res.redirect('/');
});
Problem 4: fieldName uses camelCase instead of snake_case
File: src/schema.ts
The plugin declares fieldName: "chargebeeCustomerId" (camelCase) for both user and organization:
const userSchema = {
user: {
fields: {
chargebeeCustomerId: {
type: "string",
required: false,
unique: true,
fieldName: "chargebeeCustomerId", // ← camelCase
},
},
},
};
When using PostgreSQL with snake_case column names (standard convention), Better Auth's adapter queries "chargebeeCustomerId" as the column name instead of "chargebee_customer_id", causing:
ERROR: column user.chargebee_customer_id does not exist
Expected: fieldName should be "chargebee_customer_id" to match PostgreSQL snake_case convention, or the plugin should rely on Better Auth's built-in camelCase→snake_case mapping without an explicit fieldName.
Workaround: Override in Better Auth's schema config:
schema: {
user: {
fields: {
chargebeeCustomerId: {
type: 'string',
required: false,
unique: true,
fieldName: 'chargebee_customer_id',
},
},
},
organization: {
fields: {
chargebeeCustomerId: {
type: 'string',
required: false,
unique: true,
fieldName: 'chargebee_customer_id',
},
},
},
},
@chargebee/better-auth Plugin Issues
Tested with
@chargebee/better-auth+better-auth@1.0.0-beta, organization-scoped billing (organization.enabled: true).Problem 1:
user.chargebeeCustomerIdcolumn always required even in org modeFile:
src/schema.tsuserSchemais always merged into the plugin schema regardless of whetherorganization.enabled: true:When
organization.enabled: true, billing is org-scoped anduser.chargebeeCustomerIdserves no purpose. But the plugin still declares it in the schema, causing Better Auth's adapter to query it. If the column doesn't exist in the DB, every webhook call crashes:Expected:
userSchemashould be excluded whenorganization.enabled: true.Problem 2:
handleCustomerDeletionalways queriesusertable as fallbackFile:
src/webhook-handler.tsEven when
customerType === "organization"is correctly identified and handled, the code unconditionally runs a fallback query against theusertable:This crashes if
user.chargebeeCustomerIddoesn't exist (see Problem 1). It should be guarded by org mode:Problem 3:
/subscription/successredirect endpoint is never registeredFile:
src/index.tsThe plugin builds
redirect_urlpointing to{baseURL}/subscription/successin bothcheckoutNewForItemsandcheckoutExistingForItems, but this endpoint is never registered in the plugin'sendpointsexport:After Chargebee checkout, the browser is redirected to
{baseURL}/subscription/success?callbackURL=...&subscriptionId=...&state=succeeded. Since the endpoint doesn't exist, it returns 404.Expected: Either register the endpoint in the plugin, or clearly document that developers must handle it manually.
Workaround: Manually register a handler before the Better Auth catch-all:
Problem 4:
fieldNameuses camelCase instead of snake_caseFile:
src/schema.tsThe plugin declares
fieldName: "chargebeeCustomerId"(camelCase) for bothuserandorganization:When using PostgreSQL with snake_case column names (standard convention), Better Auth's adapter queries
"chargebeeCustomerId"as the column name instead of"chargebee_customer_id", causing:Expected:
fieldNameshould be"chargebee_customer_id"to match PostgreSQL snake_case convention, or the plugin should rely on Better Auth's built-in camelCase→snake_case mapping without an explicitfieldName.Workaround: Override in Better Auth's
schemaconfig: