@alexasomba/better-auth-paystack
Version:
Production-ready Paystack billing plugin for Better Auth. Supports subscriptions, one-time payments, organization billing, secure webhooks and more
145 lines (110 loc) • 4.59 kB
Markdown
---
name: better-auth-paystack-setup
description: >
Configure @alexasomba/better-auth-paystack with Better Auth. Use when adding the paystack() server plugin, paystackClient() client plugin, schema overrides, products/plans, webhook verification, or canonical authClient.paystack/subscription/transaction actions.
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/client.ts"
- "alexasomba/better-auth-paystack:src/schema.ts"
---
## Setup
Install the package alongside Better Auth and a Paystack client:
```ts
import { betterAuth } from "better-auth";
import { createPaystack } from "@alexasomba/paystack-node";
import { paystack } from "@alexasomba/better-auth-paystack";
const paystackSdk = createPaystack({
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
export const auth = betterAuth({
database: {
provider: "sqlite",
url: process.env.DATABASE_URL!,
},
plugins: [
paystack({
paystackClient: paystackSdk,
secretKey: process.env.PAYSTACK_SECRET_KEY!,
subscription: {
enabled: true,
plans: [
{
name: "pro",
amount: 500_000,
currency: "NGN",
interval: "monthly",
planCode: "PLN_pro_monthly",
paystackId: "123456",
},
],
},
}),
],
});
```
Add the client plugin in browser-safe code:
```ts
import { createAuthClient } from "better-auth/client";
import { paystackClient } from "@alexasomba/better-auth-paystack/client";
export const authClient = createAuthClient({
plugins: [paystackClient()],
});
```
## Core Patterns
### Use canonical client namespaces
The client plugin exposes these namespaces:
```ts
await authClient.paystack.config();
await authClient.transaction.initialize({
amount: 500_000,
email: "user@example.com",
});
await authClient.transaction.verify({ reference: "trx_ref" });
await authClient.transaction.list();
await authClient.subscription.create({ plan: "pro" });
await authClient.subscription.upgrade({ plan: "team" });
await authClient.subscription.cancel({ subscriptionId: "sub_id" });
await authClient.subscription.restore({ subscriptionId: "sub_id" });
await authClient.subscription.list();
await authClient.subscription.billingPortal();
```
`subscription.disable` and `subscription.enable` are legacy compatibility aliases. Prefer `cancel` and `restore` in new code.
### Keep schema behavior stable
The plugin always contributes Paystack product and plan tables:
- `paystackProduct`
- `paystackPlan`
Subscription tables are included when `subscription.enabled` is true. User and transaction tables are always included. Organization fields are included when `organization.enabled` is true.
Use Better Auth-style schema overrides only to rename models or fields. Do not remove the Paystack product/plan tables unless you are making a breaking major release.
### Use public Better Auth imports in package code
Runtime code should import from public Better Auth entrypoints:
```ts
import { betterAuth } from "better-auth";
import { createAuthClient } from "better-auth/client";
import type { BetterAuthPluginDBSchema } from "better-auth/db";
```
Do not add runtime imports from `@better-auth/core/*` in this package. Tests can use internals only if no public API covers the case.
## Common Mistakes
### Calling server-only helpers from the browser
Wrong:
```ts
import { syncPaystackPlans } from "@alexasomba/better-auth-paystack";
await syncPaystackPlans(auth.$context, options);
```
Correct: call admin helpers from server jobs, cron handlers, CLI scripts, or trusted server routes only.
### Inventing a separate webhook secret
Paystack signs `x-paystack-signature` with the same secret key used for API authentication:
```ts
paystack({
secretKey: process.env.PAYSTACK_SECRET_KEY!,
});
```
Do not introduce `PAYSTACK_WEBHOOK_SECRET`, `webhook.secret`, or `paystackWebhookSecret`. The two
options remain deprecated source-compatibility fields and are ignored.
### Treating plans as just display data
Plans are used to validate billing operations and map Paystack plan codes. Include stable `name`, `amount`, `currency`, `interval`, `planCode`, and `paystackId` values when subscriptions are enabled.