@alexasomba/better-auth-paystack 2.4.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.
package/README.md CHANGED
@@ -15,6 +15,17 @@ A TypeScript-first plugin that integrates Paystack into [Better Auth](https://ww
15
15
 
16
16
  [**Live Demo (Tanstack Start)**](https://better-auth-paystack.gittech.workers.dev) | [**Source Code**](https://github.com/alexasomba/better-auth-paystack/tree/main/examples/tanstack)
17
17
 
18
+ ## AI Agent Skills
19
+
20
+ This package publishes [TanStack Intent](https://www.npmjs.com/package/@tanstack/intent) skills so AI coding agents can load package-specific guidance for setup, subscriptions, organization billing, and TanStack Start integration.
21
+
22
+ ```bash
23
+ npx @tanstack/intent@latest list
24
+ npx @tanstack/intent@latest load @alexasomba/better-auth-paystack#setup
25
+ ```
26
+
27
+ If you use an AI agent, run `npx @tanstack/intent@latest install` in your project so the agent knows how to discover package skills.
28
+
18
29
  ## Features
19
30
 
20
31
  - [x] **Billing Patterns**: Support for Paystack-native plans, local-managed subscriptions, and one-time payments (products/amounts).
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-C_50YiuM.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-DpVME9MV.mjs";
2
2
  //#region src/client.ts
3
3
  /**
4
4
  * Better Auth Paystack Client Plugin
package/dist/index.d.mts CHANGED
@@ -329,7 +329,7 @@ declare const getConfig: <P extends string = "/get-config">(options: AnyPaystack
329
329
  }>;
330
330
  //#endregion
331
331
  //#region src/version.d.ts
332
- declare const PACKAGE_VERSION = "2.3.0";
332
+ declare const PACKAGE_VERSION = "2.4.1";
333
333
  //#endregion
334
334
  //#region src/operations.d.ts
335
335
  declare function syncPaystackProducts(ctx: GenericEndpointContext, options: AnyPaystackOptions): Promise<PaystackSyncResult>;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-C_50YiuM.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-DpVME9MV.mjs";
2
2
  import { HIDE_METADATA, defineErrorCodes, logger } from "better-auth";
3
3
  import { defu } from "defu";
4
4
  import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
@@ -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.4.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,
@@ -77,6 +79,7 @@
77
79
  "@commitlint/config-conventional": "^21.0.0",
78
80
  "@eslint/compat": "^2.1.0",
79
81
  "@eslint/js": "^10.0.1",
82
+ "@tanstack/intent": "^0.0.40",
80
83
  "@types/node": "^24.12.3",
81
84
  "@typescript-eslint/eslint-plugin": "^8.59.2",
82
85
  "@typescript-eslint/parser": "^8.59.2",
@@ -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.
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: subscriptions-and-transactions
3
+ description: >
4
+ Build Paystack transaction and subscription flows with @alexasomba/better-auth-paystack. Use for initialize/verify transaction, create/upgrade/cancel/restore/list subscriptions, products/plans, billing portal links, webhooks, chargeSubscriptionRenewal, syncPaystackProducts, and 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/routes.ts"
11
+ - "alexasomba/better-auth-paystack:src/operations.ts"
12
+ - "alexasomba/better-auth-paystack:src/client.ts"
13
+ ---
14
+
15
+ ## Setup
16
+
17
+ Enable subscriptions with concrete Paystack plan metadata:
18
+
19
+ ```ts
20
+ import { paystack } from "@alexasomba/better-auth-paystack";
21
+
22
+ paystack({
23
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
24
+ webhook: {
25
+ secret: process.env.PAYSTACK_WEBHOOK_SECRET!,
26
+ },
27
+ subscription: {
28
+ enabled: true,
29
+ plans: [
30
+ {
31
+ name: "starter",
32
+ amount: 250_000,
33
+ currency: "NGN",
34
+ interval: "monthly",
35
+ planCode: "PLN_starter",
36
+ paystackId: "1001",
37
+ },
38
+ {
39
+ name: "team",
40
+ amount: 1_000_000,
41
+ currency: "NGN",
42
+ interval: "monthly",
43
+ planCode: "PLN_team",
44
+ paystackId: "1002",
45
+ limits: {
46
+ seats: 10,
47
+ teams: 3,
48
+ },
49
+ },
50
+ ],
51
+ },
52
+ });
53
+ ```
54
+
55
+ ## Core Patterns
56
+
57
+ ### Initialize and verify a transaction
58
+
59
+ Use the client plugin for browser-triggered checkout:
60
+
61
+ ```ts
62
+ const initialized = await authClient.transaction.initialize({
63
+ amount: 250_000,
64
+ email: "customer@example.com",
65
+ currency: "NGN",
66
+ metadata: {
67
+ product: "starter-pack",
68
+ },
69
+ });
70
+
71
+ const verified = await authClient.transaction.verify({
72
+ reference: initialized.data.reference,
73
+ });
74
+ ```
75
+
76
+ Do not trust a redirect callback alone. Always verify the Paystack reference before granting access or updating billing state.
77
+
78
+ ### Manage subscription lifecycle
79
+
80
+ Use canonical methods in new code:
81
+
82
+ ```ts
83
+ await authClient.subscription.create({
84
+ plan: "starter",
85
+ });
86
+
87
+ await authClient.subscription.upgrade({
88
+ plan: "team",
89
+ });
90
+
91
+ await authClient.subscription.cancel({
92
+ subscriptionId: "subscription_id",
93
+ });
94
+
95
+ await authClient.subscription.restore({
96
+ subscriptionId: "subscription_id",
97
+ });
98
+
99
+ const subscriptions = await authClient.subscription.list();
100
+ ```
101
+
102
+ Deprecated aliases:
103
+
104
+ - `subscription.disable` maps to `subscription.cancel`
105
+ - `subscription.enable` maps to `subscription.restore`
106
+
107
+ Keep aliases only for compatibility tests or migration examples.
108
+
109
+ ### Keep renewal and catalog sync server-side
110
+
111
+ These helpers are exported by the server package and are intentionally not client actions:
112
+
113
+ ```ts
114
+ import {
115
+ chargeSubscriptionRenewal,
116
+ syncPaystackPlans,
117
+ syncPaystackProducts,
118
+ } from "@alexasomba/better-auth-paystack";
119
+
120
+ export async function runBillingJob(ctx: unknown, options: unknown) {
121
+ await syncPaystackProducts(ctx, options);
122
+ await syncPaystackPlans(ctx, options);
123
+ await chargeSubscriptionRenewal(ctx, options, {
124
+ subscriptionId: "subscription_id",
125
+ });
126
+ }
127
+ ```
128
+
129
+ Use cron, background jobs, or trusted server functions. Do not call these from a browser component.
130
+
131
+ ## Common Mistakes
132
+
133
+ ### Mutating Paystack-managed subscriptions like local subscriptions
134
+
135
+ Seat-based or prorated subscription changes require locally managed subscription state. Paystack-managed subscriptions do not support every local mutation path.
136
+
137
+ Before implementing seat changes, check whether the target plan is local/seat-aware and whether the operation is supported by the helper being used.
138
+
139
+ ### Skipping webhook verification
140
+
141
+ Configure `webhook.secret` and let the plugin verify incoming webhook payloads. Do not process Paystack webhook bodies through an unrelated JSON route that bypasses the plugin endpoint.
142
+
143
+ ### Mixing plan name and Paystack plan code
144
+
145
+ Use `plan.name` for app-facing plan selection and `plan.planCode`/`paystackId` for Paystack identity. Do not send a Paystack plan code where the plugin expects the configured plan name.
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: tanstack-start
3
+ description: >
4
+ Integrate @alexasomba/better-auth-paystack in TanStack Start. Use for Better Auth API routes, tanstackStartCookies(), server functions, getRequestHeaders(), authClient Paystack actions, admin billing server functions, and Cloudflare Workers deployment.
5
+ type: composition
6
+ library: "@alexasomba/better-auth-paystack"
7
+ library_version: "2.4.1" # x-release-please-version
8
+ sources:
9
+ - "alexasomba/better-auth-paystack:examples/tanstack/README.md"
10
+ - "alexasomba/better-auth-paystack:examples/tanstack/src/lib/auth.ts"
11
+ - "alexasomba/better-auth-paystack:examples/tanstack/src/lib/auth-client.ts"
12
+ - "alexasomba/better-auth-paystack:examples/tanstack/src/routes/api/auth/$.ts"
13
+ ---
14
+
15
+ ## Setup
16
+
17
+ Create the Better Auth server config with Paystack and `tanstackStartCookies()` last:
18
+
19
+ ```ts
20
+ import { betterAuth } from "better-auth";
21
+ import { tanstackStartCookies } from "better-auth/tanstack-start";
22
+ import { paystack } from "@alexasomba/better-auth-paystack";
23
+
24
+ export const auth = betterAuth({
25
+ baseURL: process.env.BETTER_AUTH_URL,
26
+ plugins: [
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
+ }),
37
+ tanstackStartCookies(),
38
+ ],
39
+ });
40
+ ```
41
+
42
+ Wire the catch-all auth route:
43
+
44
+ ```ts
45
+ import { createFileRoute } from "@tanstack/react-router";
46
+ import { auth } from "../../../lib/auth";
47
+
48
+ export const Route = createFileRoute("/api/auth/$")({
49
+ server: {
50
+ handlers: {
51
+ GET: ({ request }) => auth.handler(request),
52
+ POST: ({ request }) => auth.handler(request),
53
+ },
54
+ },
55
+ });
56
+ ```
57
+
58
+ Create the client plugin:
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 Paystack client actions in client components
72
+
73
+ ```tsx
74
+ import { authClient } from "../lib/auth-client";
75
+
76
+ export function SubscribeButton() {
77
+ return (
78
+ <button
79
+ type="button"
80
+ onClick={async () => {
81
+ await authClient.subscription.create({
82
+ plan: "starter",
83
+ });
84
+ }}
85
+ >
86
+ Subscribe
87
+ </button>
88
+ );
89
+ }
90
+ ```
91
+
92
+ Use client actions for checkout and user-triggered subscription lifecycle calls. Do not import server helpers into React components.
93
+
94
+ ### Use server functions for trusted billing operations
95
+
96
+ ```ts
97
+ import { createServerFn } from "@tanstack/react-start";
98
+ import { getRequestHeaders } from "@tanstack/react-start/server";
99
+ import { auth } from "./auth";
100
+ import { syncPaystackPlans } from "@alexasomba/better-auth-paystack";
101
+
102
+ export const syncPlans = createServerFn({ method: "POST" }).handler(async () => {
103
+ const session = await auth.api.getSession({
104
+ headers: await getRequestHeaders(),
105
+ });
106
+
107
+ if (!session?.user) {
108
+ throw new Response("Unauthorized", { status: 401 });
109
+ }
110
+
111
+ await syncPaystackPlans(await auth.$context, {
112
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
113
+ });
114
+
115
+ return { ok: true };
116
+ });
117
+ ```
118
+
119
+ Pass request headers when Better Auth needs session context.
120
+
121
+ ### Keep Cloudflare Worker dependencies compatible
122
+
123
+ The TanStack example deploys to Cloudflare Workers. Keep Better Auth companion packages on compatible versions. If `@better-auth/infra` pulls in `@better-auth/sso`, avoid mixing a beta SSO package with stable `better-auth`.
124
+
125
+ The known safe pin for this package version is:
126
+
127
+ ```yaml
128
+ overrides:
129
+ "@better-auth/sso": 1.6.9
130
+ ```
131
+
132
+ ## Common Mistakes
133
+
134
+ ### Putting `tanstackStartCookies()` before Paystack
135
+
136
+ `tanstackStartCookies()` should be last in the Better Auth plugin array so cookie handling wraps the final auth behavior.
137
+
138
+ ### Omitting auth headers in server functions
139
+
140
+ Wrong:
141
+
142
+ ```ts
143
+ const session = await auth.api.getSession();
144
+ ```
145
+
146
+ Correct:
147
+
148
+ ```ts
149
+ const session = await auth.api.getSession({
150
+ headers: await getRequestHeaders(),
151
+ });
152
+ ```
153
+
154
+ ### Debugging only root package builds
155
+
156
+ The root package can pass `vp pack` while the example Worker build fails. Reproduce Worker issues with:
157
+
158
+ ```bash
159
+ pnpm --filter ./examples/tanstack build
160
+ pnpm --filter ./examples/tanstack exec wrangler deploy --dry-run
161
+ ```
@@ -1,6 +0,0 @@
1
- //#region src/version.ts
2
- const PACKAGE_VERSION = "2.3.0";
3
- //#endregion
4
- export { PACKAGE_VERSION as t };
5
-
6
- //# sourceMappingURL=version-C_50YiuM.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"version-C_50YiuM.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = \"2.3.0\";\n"],"mappings":";AAAA,MAAa,kBAAkB"}