@alexasomba/better-auth-paystack
Version:
Production-ready Paystack billing plugin for Better Auth. Supports subscriptions, one-time payments, organization billing, secure webhooks and more
184 lines (146 loc) • 5.28 kB
Markdown
name: paystack-catalog-limits
description: >
Configure products, Paystack-native plans, local-managed plans, free trials, seat billing, resource limits, and catalog sync in @alexasomba/better-auth-paystack. Use when tasks mention planCode, freeTrial, trial eligibility, seatAmount, seatPlanCode, limits, products, syncPaystackProducts, or syncPaystackPlans.
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/types.ts"
- "alexasomba/better-auth-paystack:src/routes.ts"
- "alexasomba/better-auth-paystack:src/operations.ts"
- "alexasomba/better-auth-paystack:src/utils.ts"
## Setup
Configure catalog data on the server plugin. Products are one-time purchasable catalog items. Plans are subscription catalog items.
```ts
import { paystack } from "@alexasomba/better-auth-paystack";
export const paystackPlugin = paystack({
secretKey: process.env.PAYSTACK_SECRET_KEY!,
products: {
products: [
{
name: "credits_50",
amount: 200_000,
currency: "NGN",
},
],
},
subscription: {
enabled: true,
plans: [
{
name: "pro",
amount: 500_000,
currency: "NGN",
interval: "monthly",
planCode: "PLN_pro_monthly",
paystackId: "1001",
freeTrial: {
days: 14,
},
limits: {
seats: 10,
teams: 5,
},
},
],
},
});
```
## Core Patterns
### Choose Paystack-native plans for simple recurring billing
Use `planCode` from the Paystack Dashboard when Paystack should manage the recurring subscription.
```ts
{
name: "pro",
amount: 500_000,
currency: "NGN",
interval: "monthly",
planCode: "PLN_pro_monthly",
paystackId: "1001",
}
```
Paystack-native plans are the right default for fixed-price recurring billing. Do not use native plans for flows that require local seat proration or locally managed renewals.
### Omit planCode for local-managed subscriptions
Local-managed plans are tracked in your database and renewed from stored Paystack authorizations by trusted backend code.
```ts
{
name: "local-team",
amount: 1_000_000,
currency: "NGN",
interval: "monthly",
seatAmount: 100_000,
limits: {
seats: 10,
teams: 3,
},
}
```
For local-managed subscriptions, no `planCode` means the plugin captures and stores the authorization code after transaction verification. Trigger renewals from server code with `chargeSubscriptionRenewal`.
### Configure trials on plans
Trials are declared per plan:
```ts
{
name: "starter",
amount: 250_000,
currency: "NGN",
interval: "monthly",
planCode: "PLN_starter",
paystackId: "1002",
freeTrial: {
days: 7,
onTrialStart: async (subscription) => {
await notifyTrialStarted(subscription.referenceId);
},
},
}
```
The plugin checks previous subscription history for the `referenceId`. If a trial was ever used, expired, or marked `trialing`, another trial is denied for that reference. Do not build UI that promises repeat trials for the same user or organization.
### Configure seats and resource limits
Use `limits` for app resource enforcement and `seatAmount` for local seat billing amounts:
```ts
{
name: "team",
amount: 1_000_000,
currency: "NGN",
interval: "monthly",
seatAmount: 100_000,
seatPlanCode: "PLN_extra_seat",
limits: {
seats: 10,
teams: 3,
},
}
```
`seatPriceId` is a deprecated alias. Use `seatAmount` in new code. `seatPlanCode` is only useful when a Paystack plan code exists for extra seats.
### Sync products and plans from trusted server jobs
Use server-only helpers to mirror Paystack catalog data into local tables:
```ts
import { syncPaystackPlans, syncPaystackProducts } from "@alexasomba/better-auth-paystack";
export async function syncCatalog(ctx: unknown, options: unknown) {
await syncPaystackProducts(ctx, options);
await syncPaystackPlans(ctx, options);
}
```
These operations are not browser client actions. Run them from cron, admin-only server functions, CI jobs, or deployment tasks.
## Common Mistakes
### Using native planCode for local seat/proration behavior
Wrong:
```ts
{
name: "team",
planCode: "PLN_team",
seatAmount: 100_000,
}
```
Correct: omit `planCode` when the plan needs local seat billing, local renewals, or prorated seat changes.
### Treating products like subscription plans
Products are one-time purchases. Plans are subscriptions. Use transaction initialization for product purchases and subscription actions for plans.
### Expecting product and plan tables to be optional
`paystackProduct` and `paystackPlan` schema tables are always included by the plugin. Do not remove them in compatibility-preserving releases.
### Trusting trial state without verification
Trial metadata is created during subscription checkout and finalized through transaction/webhook handling. Always verify the Paystack reference and rely on persisted subscription state before granting paid access.