@alexasomba/better-auth-paystack 2.2.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,21 @@
1
- import { GenericEndpointContext, Session, User } from "better-auth";
1
+ import { GenericEndpointContext, InferOptionSchema, Session, User } from "better-auth";
2
2
  import { PaystackCustomerClient, PaystackPlanClient, PaystackProductClient, PaystackSubscriptionClient, PaystackTransactionClient, PaystackWebhookEvent, components } from "@alexasomba/paystack-node";
3
+ import { BetterAuthPluginDBSchema, DBFieldAttribute } from "better-auth/db";
4
+ //#region src/schema.d.ts
5
+ type PluginSchemaTable<TableName extends string, FieldName extends string> = Record<TableName, {
6
+ fields: Record<FieldName, DBFieldAttribute>;
7
+ disableMigration?: boolean;
8
+ modelName?: string;
9
+ }>;
10
+ type TransactionsSchema = PluginSchemaTable<"paystackTransaction", "reference" | "paystackId" | "referenceId" | "userId" | "amount" | "currency" | "status" | "plan" | "product" | "metadata" | "createdAt" | "updatedAt">;
11
+ type SubscriptionsSchema = PluginSchemaTable<"subscription", "plan" | "referenceId" | "paystackCustomerCode" | "paystackSubscriptionCode" | "paystackTransactionReference" | "paystackAuthorizationCode" | "paystackEmailToken" | "status" | "periodStart" | "periodEnd" | "trialStart" | "trialEnd" | "cancelAtPeriodEnd" | "groupId" | "seats" | "pendingPlan">;
12
+ type UserSchema = PluginSchemaTable<"user", "paystackCustomerCode">;
13
+ type OrganizationSchema = PluginSchemaTable<"organization", "paystackCustomerCode" | "email">;
14
+ type ProductsSchema = PluginSchemaTable<"paystackProduct", "name" | "description" | "price" | "currency" | "quantity" | "unlimited" | "paystackId" | "slug" | "metadata" | "createdAt" | "updatedAt">;
15
+ type PlansSchema = PluginSchemaTable<"paystackPlan", "name" | "description" | "amount" | "currency" | "interval" | "planCode" | "paystackId" | "metadata" | "createdAt" | "updatedAt">;
16
+ type PaystackPluginSchema = SubscriptionsSchema & TransactionsSchema & UserSchema & OrganizationSchema & ProductsSchema & PlansSchema;
17
+ declare const getSchema: (options: PaystackOptions) => BetterAuthPluginDBSchema;
18
+ //#endregion
3
19
  //#region src/types.d.ts
4
20
  type PaystackCheckoutChannel = "card" | "bank" | "ussd" | "qr" | "mobile_money" | "bank_transfer" | "eft" | "apple_pay";
5
21
  /**
@@ -32,8 +48,8 @@ interface PaystackProduct {
32
48
  paystackId?: string;
33
49
  slug?: string;
34
50
  metadata?: string | null;
35
- createdAt: Date;
36
- updatedAt: Date;
51
+ createdAt?: Date;
52
+ updatedAt?: Date;
37
53
  }
38
54
  interface PaystackPlan {
39
55
  id?: string;
@@ -200,10 +216,7 @@ interface PaystackOptions<TPaystackClient extends PaystackClientLike = PaystackC
200
216
  /**
201
217
  * Custom database schema / model names
202
218
  */
203
- schema?: Record<string, {
204
- modelName?: string;
205
- fields?: Record<string, string>;
206
- }>;
219
+ schema?: InferOptionSchema<PaystackPluginSchema>;
207
220
  }
208
221
  interface Subscription {
209
222
  id: string;
@@ -254,5 +267,5 @@ interface PaystackClientLike {
254
267
  plan: Pick<PaystackPlanClient, "list" | "create">;
255
268
  }
256
269
  //#endregion
257
- export { PaystackOptions as a, PaystackSyncResult as c, Subscription as d, SubscriptionOptions as f, PaystackClientLike as i, PaystackTransaction as l, ChargeRecurringSubscriptionInput as n, PaystackPlan as o, ChargeRecurringSubscriptionResult as r, PaystackProduct as s, AnyPaystackOptions as t, PaystackTransactionResponse as u };
258
- //# sourceMappingURL=types-B5ZnlFrq.d.mts.map
270
+ export { PaystackOptions as a, PaystackSyncResult as c, Subscription as d, SubscriptionOptions as f, PaystackClientLike as i, PaystackTransaction as l, ChargeRecurringSubscriptionInput as n, PaystackPlan as o, getSchema as p, ChargeRecurringSubscriptionResult as r, PaystackProduct as s, AnyPaystackOptions as t, PaystackTransactionResponse as u };
271
+ //# sourceMappingURL=types-CNI2ur0p.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-CNI2ur0p.d.mts","names":["GenericEndpointContext","InferOptionSchema","Session","User","Organization","Member","PaystackPaths","PaystackResponse","PaystackWebhookEvent","PaystackClient","PaystackCustomerClient","PaystackPlanClient","PaystackProductClient","PaystackSubscriptionClient","PaystackTransactionClient","components","PaystackPluginSchema","PaystackCurrency","PaystackCheckoutChannel","PaystackTransaction","Date","id","reference","paystackId","referenceId","userId","amount","currency","status","plan","product","metadata","createdAt","updatedAt","PaystackProduct","name","description","price","quantity","unlimited","slug","InputPaystackProduct","PaystackUser","paystackCustomerCode","PaystackOrganization","PaystackPlan","Subscription","Promise","Record","interval","planCode","seatAmount","seatPriceId","seatPlanCode","invoiceLimit","freeTrial","days","onTrialStart","subscription","limits","features","PaystackWebhookPayload","PaystackTransactionResponse","PaystackPlanResponse","PaystackCustomerResponse","PaystackSubscriptionResponse","PaystackProductResponse","SubscriptionOptions","enabled","plans","autoSyncQuantity","cancelBehavior","onSubscriptionComplete","event","data","ctx","onSubscriptionCreated","onSubscriptionCancel","authorizeReference","user","session","action","requireEmailVerification","allowedPaymentChannels","PaystackOptions","TPaystackClient","PaystackClientLike","secretKey","paystackWebhookSecret","paystackClient","webhook","secret","verifyIP","trustedIPs","billingPattern","onEvent","organization","getCustomerCreateParams","email","org","onCustomerCreate","paystackCustomer","products","createCustomerOnSignUp","schema","organizationId","pendingPlan","paystackSubscriptionCode","paystackPlanCode","paystackAuthorizationCode","paystackTransactionReference","paystackEmailToken","seats","periodStart","periodEnd","cancelAtPeriodEnd","trialStart","trialEnd","groupId","ChargeRecurringSubscriptionInput","subscriptionId","ChargeRecurringSubscriptionResult","PaystackSyncResult","count","AnyPaystackOptions","Pick","transaction","customer"],"sources":["../src/schema.ts","../src/types.d.ts"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ //#region src/version.ts
2
+ const PACKAGE_VERSION = "2.4.1";
3
+ //#endregion
4
+ export { PACKAGE_VERSION as t };
5
+
6
+ //# sourceMappingURL=version-DpVME9MV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-DpVME9MV.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = \"2.4.1\"; // x-release-please-version\n"],"mappings":";AAAA,MAAa,kBAAkB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexasomba/better-auth-paystack",
3
- "version": "2.2.0",
3
+ "version": "2.4.1",
4
4
  "description": "Production-ready Paystack billing plugin for Better Auth. Supports subscriptions, one-time payments, organization billing, secure webhooks and more",
5
5
  "keywords": [
6
6
  "africa",
@@ -24,6 +24,7 @@
24
24
  "saas",
25
25
  "south-africa",
26
26
  "subscriptions",
27
+ "tanstack-intent",
27
28
  "usd",
28
29
  "zar"
29
30
  ],
@@ -44,7 +45,8 @@
44
45
  }
45
46
  ],
46
47
  "files": [
47
- "dist"
48
+ "dist",
49
+ "skills"
48
50
  ],
49
51
  "type": "module",
50
52
  "sideEffects": false,
@@ -66,38 +68,38 @@
66
68
  },
67
69
  "dependencies": {
68
70
  "@alexasomba/paystack-node": "^1.9.4",
69
- "better-call": "^1.3.5",
71
+ "better-call": "~1.3.5",
70
72
  "defu": "^6.1.7",
71
- "zod": "^4.3.6"
73
+ "zod": "^4.4.3"
72
74
  },
73
75
  "devDependencies": {
74
76
  "@arethetypeswrong/cli": "^0.18.2",
75
- "@better-auth/core": "1.7.0-beta.1",
76
77
  "@better-fetch/fetch": "^1.1.21",
77
- "@commitlint/cli": "^20.5.0",
78
- "@commitlint/config-conventional": "^20.5.0",
79
- "@eslint/compat": "^2.0.5",
78
+ "@commitlint/cli": "^21.0.0",
79
+ "@commitlint/config-conventional": "^21.0.0",
80
+ "@eslint/compat": "^2.1.0",
80
81
  "@eslint/js": "^10.0.1",
81
- "@types/node": "^24.12.2",
82
- "@typescript-eslint/eslint-plugin": "^8.58.2",
83
- "@typescript-eslint/parser": "^8.58.2",
84
- "@vitest/coverage-v8": "^4.1.4",
85
- "better-auth": "1.7.0-beta.1",
82
+ "@tanstack/intent": "^0.0.40",
83
+ "@types/node": "^24.12.3",
84
+ "@typescript-eslint/eslint-plugin": "^8.59.2",
85
+ "@typescript-eslint/parser": "^8.59.2",
86
+ "@vitest/coverage-v8": "^4.1.5",
87
+ "better-auth": "^1.6.9",
86
88
  "eslint-plugin-import-x": "^4.16.2",
87
- "eslint-plugin-promise": "^7.2.1",
89
+ "eslint-plugin-promise": "^7.3.0",
88
90
  "eslint-plugin-react-hooks": "^7.1.1",
89
91
  "eslint-plugin-unicorn": "^64.0.0",
90
- "publint": "^0.3.18",
91
- "typescript": "^5.9.3",
92
- "typescript-eslint": "^8.58.2",
93
- "vite-plus": "^0.1.18",
94
- "vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.18"
92
+ "publint": "^0.3.20",
93
+ "typescript": "^6.0.3",
94
+ "typescript-eslint": "^8.59.2",
95
+ "vite-plus": "^0.1.20",
96
+ "vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.20"
95
97
  },
96
98
  "peerDependencies": {
97
- "better-auth": "^1.6.5"
99
+ "better-auth": "^1.6.9"
98
100
  },
99
101
  "optionalDependencies": {
100
- "@rollup/rollup-linux-x64-gnu": "^4.60.2"
102
+ "@rollup/rollup-linux-x64-gnu": "^4.60.3"
101
103
  },
102
104
  "engines": {
103
105
  "node": ">=24.0.0"
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: billing-catalog-and-limits
3
+ description: >
4
+ 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.
5
+ type: core
6
+ library: "@alexasomba/better-auth-paystack"
7
+ library_version: "2.4.1" # x-release-please-version
8
+ sources:
9
+ - "alexasomba/better-auth-paystack:README.md"
10
+ - "alexasomba/better-auth-paystack:src/types.ts"
11
+ - "alexasomba/better-auth-paystack:src/routes.ts"
12
+ - "alexasomba/better-auth-paystack:src/operations.ts"
13
+ - "alexasomba/better-auth-paystack:src/utils.ts"
14
+ ---
15
+
16
+ ## Setup
17
+
18
+ Configure catalog data on the server plugin. Products are one-time purchasable catalog items. Plans are subscription catalog items.
19
+
20
+ ```ts
21
+ import { paystack } from "@alexasomba/better-auth-paystack";
22
+
23
+ export const paystackPlugin = paystack({
24
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
25
+ webhook: {
26
+ secret: process.env.PAYSTACK_WEBHOOK_SECRET!,
27
+ },
28
+ products: {
29
+ products: [
30
+ {
31
+ name: "credits_50",
32
+ amount: 200_000,
33
+ currency: "NGN",
34
+ },
35
+ ],
36
+ },
37
+ subscription: {
38
+ enabled: true,
39
+ plans: [
40
+ {
41
+ name: "pro",
42
+ amount: 500_000,
43
+ currency: "NGN",
44
+ interval: "monthly",
45
+ planCode: "PLN_pro_monthly",
46
+ paystackId: "1001",
47
+ freeTrial: {
48
+ days: 14,
49
+ },
50
+ limits: {
51
+ seats: 10,
52
+ teams: 5,
53
+ },
54
+ },
55
+ ],
56
+ },
57
+ });
58
+ ```
59
+
60
+ ## Core Patterns
61
+
62
+ ### Choose Paystack-native plans for simple recurring billing
63
+
64
+ Use `planCode` from the Paystack Dashboard when Paystack should manage the recurring subscription.
65
+
66
+ ```ts
67
+ {
68
+ name: "pro",
69
+ amount: 500_000,
70
+ currency: "NGN",
71
+ interval: "monthly",
72
+ planCode: "PLN_pro_monthly",
73
+ paystackId: "1001",
74
+ }
75
+ ```
76
+
77
+ 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.
78
+
79
+ ### Omit planCode for local-managed subscriptions
80
+
81
+ Local-managed plans are tracked in your database and renewed from stored Paystack authorizations by trusted backend code.
82
+
83
+ ```ts
84
+ {
85
+ name: "local-team",
86
+ amount: 1_000_000,
87
+ currency: "NGN",
88
+ interval: "monthly",
89
+ seatAmount: 100_000,
90
+ limits: {
91
+ seats: 10,
92
+ teams: 3,
93
+ },
94
+ }
95
+ ```
96
+
97
+ 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`.
98
+
99
+ ### Configure trials on plans
100
+
101
+ Trials are declared per plan:
102
+
103
+ ```ts
104
+ {
105
+ name: "starter",
106
+ amount: 250_000,
107
+ currency: "NGN",
108
+ interval: "monthly",
109
+ planCode: "PLN_starter",
110
+ paystackId: "1002",
111
+ freeTrial: {
112
+ days: 7,
113
+ onTrialStart: async (subscription) => {
114
+ await notifyTrialStarted(subscription.referenceId);
115
+ },
116
+ },
117
+ }
118
+ ```
119
+
120
+ 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.
121
+
122
+ ### Configure seats and resource limits
123
+
124
+ Use `limits` for app resource enforcement and `seatAmount` for local seat billing amounts:
125
+
126
+ ```ts
127
+ {
128
+ name: "team",
129
+ amount: 1_000_000,
130
+ currency: "NGN",
131
+ interval: "monthly",
132
+ seatAmount: 100_000,
133
+ seatPlanCode: "PLN_extra_seat",
134
+ limits: {
135
+ seats: 10,
136
+ teams: 3,
137
+ },
138
+ }
139
+ ```
140
+
141
+ `seatPriceId` is a deprecated alias. Use `seatAmount` in new code. `seatPlanCode` is only useful when a Paystack plan code exists for extra seats.
142
+
143
+ ### Sync products and plans from trusted server jobs
144
+
145
+ Use server-only helpers to mirror Paystack catalog data into local tables:
146
+
147
+ ```ts
148
+ import { syncPaystackPlans, syncPaystackProducts } from "@alexasomba/better-auth-paystack";
149
+
150
+ export async function syncCatalog(ctx: unknown, options: unknown) {
151
+ await syncPaystackProducts(ctx, options);
152
+ await syncPaystackPlans(ctx, options);
153
+ }
154
+ ```
155
+
156
+ These operations are not browser client actions. Run them from cron, admin-only server functions, CI jobs, or deployment tasks.
157
+
158
+ ## Common Mistakes
159
+
160
+ ### Using native planCode for local seat/proration behavior
161
+
162
+ Wrong:
163
+
164
+ ```ts
165
+ {
166
+ name: "team",
167
+ planCode: "PLN_team",
168
+ seatAmount: 100_000,
169
+ }
170
+ ```
171
+
172
+ Correct: omit `planCode` when the plan needs local seat billing, local renewals, or prorated seat changes.
173
+
174
+ ### Treating products like subscription plans
175
+
176
+ Products are one-time purchases. Plans are subscriptions. Use transaction initialization for product purchases and subscription actions for plans.
177
+
178
+ ### Expecting product and plan tables to be optional
179
+
180
+ `paystackProduct` and `paystackPlan` schema tables are always included by the plugin. Do not remove them in compatibility-preserving releases.
181
+
182
+ ### Trusting trial state without verification
183
+
184
+ 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.
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: organization-billing
3
+ description: >
4
+ Configure organization billing in @alexasomba/better-auth-paystack. Use for organization.enabled, Better Auth organization plugin setup, owner/admin default billing authorization, subscription.authorizeReference, organization Paystack customers, seats, invitations, members, and team limits.
5
+ type: core
6
+ library: "@alexasomba/better-auth-paystack"
7
+ library_version: "2.4.1" # x-release-please-version
8
+ sources:
9
+ - "alexasomba/better-auth-paystack:README.md"
10
+ - "alexasomba/better-auth-paystack:src/index.ts"
11
+ - "alexasomba/better-auth-paystack:src/utils.ts"
12
+ - "alexasomba/better-auth-paystack:src/limits.ts"
13
+ ---
14
+
15
+ ## Setup
16
+
17
+ Install the Better Auth organization plugin and enable Paystack organization billing:
18
+
19
+ ```ts
20
+ import { betterAuth } from "better-auth";
21
+ import { organization } from "better-auth/plugins/organization";
22
+ import { paystack } from "@alexasomba/better-auth-paystack";
23
+
24
+ export const auth = betterAuth({
25
+ plugins: [
26
+ organization(),
27
+ paystack({
28
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
29
+ webhook: {
30
+ secret: process.env.PAYSTACK_WEBHOOK_SECRET!,
31
+ },
32
+ subscription: {
33
+ enabled: true,
34
+ plans: [
35
+ {
36
+ name: "team",
37
+ amount: 1_000_000,
38
+ currency: "NGN",
39
+ interval: "monthly",
40
+ planCode: "PLN_team",
41
+ paystackId: "1002",
42
+ limits: {
43
+ seats: 10,
44
+ teams: 3,
45
+ },
46
+ },
47
+ ],
48
+ },
49
+ organization: {
50
+ enabled: true,
51
+ },
52
+ }),
53
+ ],
54
+ });
55
+ ```
56
+
57
+ ## Core Patterns
58
+
59
+ ### Rely on the safe default for billing authorization
60
+
61
+ When `subscription.authorizeReference` is not supplied, organization billing actions require membership role `owner` or `admin`.
62
+
63
+ Ordinary members are rejected by default. Do not write UI or tests that assume any member can create, upgrade, cancel, or restore organization subscriptions.
64
+
65
+ ### Override authorization explicitly for custom workflows
66
+
67
+ Use `subscription.authorizeReference` when a product intentionally allows non-owner/admin billing access:
68
+
69
+ ```ts
70
+ paystack({
71
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
72
+ subscription: {
73
+ enabled: true,
74
+ plans: [],
75
+ authorizeReference: async ({ user, referenceId }, ctx) => {
76
+ if (referenceId === user.id) return true;
77
+
78
+ const memberships = await ctx.context.adapter.findMany({
79
+ model: "member",
80
+ where: [
81
+ { field: "userId", value: user.id },
82
+ { field: "organizationId", value: referenceId },
83
+ ],
84
+ });
85
+
86
+ return memberships.some((membership) => {
87
+ const role = (membership as { role?: string }).role;
88
+ return role === "owner" || role === "admin" || role === "billing";
89
+ });
90
+ },
91
+ },
92
+ organization: {
93
+ enabled: true,
94
+ },
95
+ });
96
+ ```
97
+
98
+ When supplied, `authorizeReference` is authoritative. Include all user and organization cases you want to allow.
99
+
100
+ ### Create organization Paystack customers
101
+
102
+ 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`.
103
+
104
+ Customize creation params when needed:
105
+
106
+ ```ts
107
+ organization: {
108
+ enabled: true,
109
+ getCustomerCreateParams: async (org) => ({
110
+ metadata: JSON.stringify({
111
+ organizationId: org.id,
112
+ billingSource: "better-auth-paystack",
113
+ }),
114
+ }),
115
+ onCustomerCreate: async ({ paystackCustomer, organization }, ctx) => {
116
+ await ctx.context.adapter.update({
117
+ model: "organization",
118
+ where: [{ field: "id", value: organization.id }],
119
+ update: {
120
+ paystackCustomerCode: paystackCustomer.customer_code,
121
+ },
122
+ });
123
+ },
124
+ }
125
+ ```
126
+
127
+ ## Common Mistakes
128
+
129
+ ### Enabling organization billing without the organization plugin
130
+
131
+ 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.
132
+
133
+ ### Assuming member limits apply without subscription plans
134
+
135
+ Seat and team limits come from subscription plan limits. If a plan has no relevant `limits` value, the plugin cannot enforce that limit.
136
+
137
+ ### Forgetting seat sync after membership changes
138
+
139
+ 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.
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: setup
3
+ description: >
4
+ Configure @alexasomba/better-auth-paystack with Better Auth. Use when adding the paystack() server plugin, paystackClient() client plugin, schema overrides, products/plans, webhook secrets, or canonical authClient.paystack/subscription/transaction actions.
5
+ type: core
6
+ library: "@alexasomba/better-auth-paystack"
7
+ library_version: "2.4.1" # x-release-please-version
8
+ sources:
9
+ - "alexasomba/better-auth-paystack:README.md"
10
+ - "alexasomba/better-auth-paystack:src/index.ts"
11
+ - "alexasomba/better-auth-paystack:src/client.ts"
12
+ - "alexasomba/better-auth-paystack:src/schema.ts"
13
+ ---
14
+
15
+ ## Setup
16
+
17
+ Install the package alongside Better Auth and a Paystack client:
18
+
19
+ ```ts
20
+ import { betterAuth } from "better-auth";
21
+ import { createPaystack } from "@alexasomba/paystack-node";
22
+ import { paystack } from "@alexasomba/better-auth-paystack";
23
+
24
+ const paystackSdk = createPaystack({
25
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
26
+ });
27
+
28
+ export const auth = betterAuth({
29
+ database: {
30
+ provider: "sqlite",
31
+ url: process.env.DATABASE_URL!,
32
+ },
33
+ plugins: [
34
+ paystack({
35
+ paystackClient: paystackSdk,
36
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
37
+ webhook: {
38
+ secret: process.env.PAYSTACK_WEBHOOK_SECRET!,
39
+ },
40
+ subscription: {
41
+ enabled: true,
42
+ plans: [
43
+ {
44
+ name: "pro",
45
+ amount: 500_000,
46
+ currency: "NGN",
47
+ interval: "monthly",
48
+ planCode: "PLN_pro_monthly",
49
+ paystackId: "123456",
50
+ },
51
+ ],
52
+ },
53
+ }),
54
+ ],
55
+ });
56
+ ```
57
+
58
+ Add the client plugin in browser-safe code:
59
+
60
+ ```ts
61
+ import { createAuthClient } from "better-auth/client";
62
+ import { paystackClient } from "@alexasomba/better-auth-paystack/client";
63
+
64
+ export const authClient = createAuthClient({
65
+ plugins: [paystackClient()],
66
+ });
67
+ ```
68
+
69
+ ## Core Patterns
70
+
71
+ ### Use canonical client namespaces
72
+
73
+ The client plugin exposes these namespaces:
74
+
75
+ ```ts
76
+ await authClient.paystack.getConfig();
77
+ await authClient.transaction.initialize({ amount: 500_000, email: "user@example.com" });
78
+ await authClient.transaction.verify({ reference: "trx_ref" });
79
+ await authClient.transaction.list();
80
+ await authClient.subscription.create({ plan: "pro" });
81
+ await authClient.subscription.upgrade({ plan: "team" });
82
+ await authClient.subscription.cancel({ subscriptionId: "sub_id" });
83
+ await authClient.subscription.restore({ subscriptionId: "sub_id" });
84
+ await authClient.subscription.list();
85
+ await authClient.subscription.billingPortal();
86
+ ```
87
+
88
+ `subscription.disable` and `subscription.enable` still exist as deprecated aliases in the 2.x line. Prefer `cancel` and `restore` in new code.
89
+
90
+ ### Keep schema behavior stable
91
+
92
+ The plugin always contributes Paystack product and plan tables:
93
+
94
+ - `paystackProduct`
95
+ - `paystackPlan`
96
+
97
+ 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.
98
+
99
+ 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.
100
+
101
+ ### Use public Better Auth imports in package code
102
+
103
+ Runtime code should import from public Better Auth entrypoints:
104
+
105
+ ```ts
106
+ import { betterAuth } from "better-auth";
107
+ import { createAuthClient } from "better-auth/client";
108
+ import type { BetterAuthPluginDBSchema } from "better-auth/db";
109
+ ```
110
+
111
+ Do not add runtime imports from `@better-auth/core/*` in this package. Tests can use internals only if no public API covers the case.
112
+
113
+ ## Common Mistakes
114
+
115
+ ### Calling server-only helpers from the browser
116
+
117
+ Wrong:
118
+
119
+ ```ts
120
+ import { syncPaystackPlans } from "@alexasomba/better-auth-paystack";
121
+
122
+ await syncPaystackPlans(auth.$context, options);
123
+ ```
124
+
125
+ Correct: call admin helpers from server jobs, cron handlers, CLI scripts, or trusted server routes only.
126
+
127
+ ### Forgetting webhook secret normalization
128
+
129
+ Prefer the `webhook.secret` option:
130
+
131
+ ```ts
132
+ paystack({
133
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
134
+ webhook: {
135
+ secret: process.env.PAYSTACK_WEBHOOK_SECRET!,
136
+ },
137
+ });
138
+ ```
139
+
140
+ `paystackWebhookSecret` is a compatibility alias. New code should not introduce it.
141
+
142
+ ### Treating plans as just display data
143
+
144
+ 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.