Skip to content

4 issues to fix #26

@t19j79

Description

@t19j79

@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',
      },
    },
  },
},

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions