UNPKG

@alexasomba/better-auth-paystack

Version:

Production-ready Paystack billing plugin for Better Auth. Supports subscriptions, one-time payments, organization billing, secure webhooks and more

160 lines (130 loc) 5.48 kB
--- name: paystack-organization-billing description: > Configure organization billing in @alexasomba/better-auth-paystack. Use for organization.enabled, Better Auth organization plugin setup, owner/admin default billing authorization, organization.billingRoles, subscription.authorizeReference, organization Paystack customers, seats, invitations, members, and team limits. type: core library: "@alexasomba/better-auth-paystack" library_version: "3.1.1" # x-release-please-version license: "MIT" compatibility: "Node.js >=22.0.0; better-auth ^1.6.9; @alexasomba/paystack-node 1.10.x; @alexasomba/better-auth-paystack >=3.0.0 <4.0.0" sources: - "alexasomba/better-auth-paystack:README.md" - "alexasomba/better-auth-paystack:src/index.ts" - "alexasomba/better-auth-paystack:src/utils.ts" - "alexasomba/better-auth-paystack:src/limits.ts" --- ## Setup Install the Better Auth organization plugin and enable Paystack organization billing: ```ts import { betterAuth } from "better-auth"; import { organization } from "better-auth/plugins/organization"; import { paystack } from "@alexasomba/better-auth-paystack"; export const auth = betterAuth({ plugins: [ organization(), paystack({ secretKey: process.env.PAYSTACK_SECRET_KEY!, subscription: { enabled: true, plans: [ { name: "team", amount: 1_000_000, currency: "NGN", interval: "monthly", planCode: "PLN_team", paystackId: "1002", limits: { seats: 10, teams: 3, }, }, ], }, organization: { enabled: true, billingRoles: ["owner", "admin", "billing"], }, }), ], }); ``` ## Core Patterns ### Rely on the safe default for billing authorization When `subscription.authorizeReference` is not supplied, organization billing actions require membership role `owner` or `admin`. Ordinary members are rejected by default. Do not write UI or tests that assume any member can create, upgrade, cancel, or restore organization subscriptions. ### Add trusted billing roles without replacing the default Use `organization.billingRoles` when a product needs a role such as `billing`, `finance`, or `accounting` to manage organization billing while preserving the built-in user self-access, organization membership lookup, and action scoping. ```ts paystack({ secretKey: process.env.PAYSTACK_SECRET_KEY!, subscription: { enabled: true, plans: [], }, organization: { enabled: true, billingRoles: ["owner", "admin", "billing"], }, }); ``` Role matching supports Better Auth string roles, comma-separated role strings like `"member,billing"`, and array roles like `["member", "billing"]`. ### Override authorization explicitly for custom workflows Use `subscription.authorizeReference` when a product needs lower-level policy checks that cannot be represented as organization roles: ```ts paystack({ secretKey: process.env.PAYSTACK_SECRET_KEY!, subscription: { enabled: true, plans: [], authorizeReference: async ({ user, referenceId }, ctx) => { if (referenceId === user.id) return true; const memberships = await ctx.context.adapter.findMany({ model: "member", where: [ { field: "userId", value: user.id }, { field: "organizationId", value: referenceId }, ], }); return memberships.some((membership) => { const role = (membership as { role?: string }).role; return role === "owner" || role === "admin" || role === "billing"; }); }, }, organization: { enabled: true, }, }); ``` When supplied, `authorizeReference` is authoritative. Include all user and organization cases you want to allow. ### Create organization Paystack customers When organization billing is enabled and the organization plugin is present, the plugin wires organization creation hooks. It tries to create a Paystack customer for the organization and stores `paystackCustomerCode`. Customize creation params when needed: ```ts organization: { enabled: true, getCustomerCreateParams: async (org) => ({ metadata: JSON.stringify({ organizationId: org.id, billingSource: "better-auth-paystack", }), }), onCustomerCreate: async ({ paystackCustomer, organization }, ctx) => { await ctx.context.adapter.update({ model: "organization", where: [{ field: "id", value: organization.id }], update: { paystackCustomerCode: paystackCustomer.customer_code, }, }); }, } ``` ## Common Mistakes ### Enabling organization billing without the organization plugin If `organization.enabled` is true but Better Auth's organization plugin is missing, Paystack logs a clear error and skips organization hook wiring. Add `organization()` before relying on organization customer creation, members, invitations, seats, or team hooks. ### Assuming member limits apply without subscription plans Seat and team limits come from subscription plan limits. If a plan has no relevant `limits` value, the plugin cannot enforce that limit. ### Forgetting seat sync after membership changes The plugin hooks member/invitation lifecycle events when the organization plugin is present. If you implement custom membership mutation routes outside Better Auth's adapter hooks, explicitly re-check or sync seat state in that custom path.