@alexasomba/better-auth-paystack 1.2.0 → 2.1.0

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
@@ -2,6 +2,17 @@
2
2
 
3
3
  A TypeScript-first plugin that integrates Paystack into [Better Auth](https://www.better-auth.com), providing a production-ready billing system with support for subscriptions (native & local), one-time payments, trials, organization billing, and secure webhooks.
4
4
 
5
+ <div align="center">
6
+
7
+ ![npm downloads](https://img.shields.io/npm/dm/@alexasomba/better-auth-paystack.svg)
8
+ [![GitHub stars](https://img.shields.io/github/stars/alexasomba/better-auth-paystack.svg?style=social&label=Star)](https://github.com/alexasomba/better-auth-paystack/stargazers)
9
+ [![GitHub release](https://img.shields.io/github/v/release/alexasomba/better-auth-paystack)](https://github.com/alexasomba/better-auth-paystack/releases)
10
+ [![bundlephobia](https://img.shields.io/bundlephobia/minzip/@alexasomba/better-auth-paystack)](https://bundlephobia.com/result?p=@alexasomba/better-auth-paystack)
11
+ [![Follow on Twitter](https://img.shields.io/twitter/follow/alexasomba?style=social)](https://twitter.com/alexasomba)
12
+ ![GitHub License](https://img.shields.io/github/license/alexasomba/better-auth-paystack)
13
+
14
+ </div>
15
+
5
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)
6
17
 
7
18
  ## Features
@@ -13,14 +24,19 @@ A TypeScript-first plugin that integrates Paystack into [Better Auth](https://ww
13
24
  - [x] **Enforced Limits & Seats**: Automatic enforcement of member seat upgrades and resource limits (teams).
14
25
  - [x] **Scheduled Changes**: Defer subscription updates or cancellations to the end of the billing cycle.
15
26
  - [x] **Proration**: Immediate mid-cycle prorated charges for seat and plan upgrades.
16
- - [x] **Popup Modal Flow**: Optional support for Paystack's inline checkout experience via `@alexasomba/paystack-browser`.
17
- - [x] **Webhook Security**: Pre-configured signature verification (HMAC-SHA512).
27
+ - [x] **Popup Modal Flow**: Optional support for Paystack's inline checkout experience via `@alexasomba/paystack-inline`.
28
+ - [x] **Webhook Security**: Pre-configured signature verification (HMAC-SHA512) and optional IP whitelisting.
18
29
  - [x] **Transaction History**: Built-in support for listing and viewing local transaction records.
19
30
 
20
31
  ---
21
32
 
22
33
  ## Quick Start
23
34
 
35
+ ### Prerequisites
36
+
37
+ - **Node.js**: `v24.0.0` or higher.
38
+ - **Better Auth**: `v1.6.5` or higher.
39
+
24
40
  ### 1. Install Plugin & SDKs
25
41
 
26
42
  ```bash
@@ -30,7 +46,7 @@ npm install better-auth @alexasomba/better-auth-paystack @alexasomba/paystack-no
30
46
  #### Optional: Browser SDK (for Popup Modals)
31
47
 
32
48
  ```bash
33
- npm install @alexasomba/paystack-browser
49
+ npm install @alexasomba/paystack-inline
34
50
  ```
35
51
 
36
52
  ### 2. Configure Environment Variables
@@ -57,7 +73,7 @@ export const auth = betterAuth({
57
73
  plugins: [
58
74
  paystack({
59
75
  paystackClient,
60
- paystackWebhookSecret: process.env.PAYSTACK_WEBHOOK_SECRET!,
76
+ webhook: { secret: process.env.PAYSTACK_WEBHOOK_SECRET! },
61
77
  createCustomerOnSignUp: true,
62
78
  subscription: {
63
79
  enabled: true,
@@ -103,6 +119,60 @@ npx better-auth migrate
103
119
 
104
120
  ---
105
121
 
122
+ ## Migration Guide
123
+
124
+ Version `2.0.0` contains a security-focused breaking change.
125
+
126
+ - Removed public/client operator actions:
127
+ - `authClient.paystack.syncProducts()`
128
+ - `authClient.paystack.syncPlans()`
129
+ - `authClient.paystack.chargeRecurringSubscription(...)`
130
+ - Removed public Better Auth endpoints for:
131
+ - `/paystack/sync-products`
132
+ - `/paystack/sync-plans`
133
+ - `/paystack/charge-recurring`
134
+ - Added trusted server operations:
135
+ - `chargeSubscriptionRenewal`
136
+ - `syncPaystackProducts`
137
+ - `syncPaystackPlans`
138
+
139
+ ### Old
140
+
141
+ ```ts
142
+ await authClient.paystack.syncProducts();
143
+ await authClient.paystack.syncPlans();
144
+ await authClient.paystack.chargeRecurringSubscription({
145
+ subscriptionId: "sub_123",
146
+ });
147
+ ```
148
+
149
+ ### New
150
+
151
+ ```ts
152
+ import {
153
+ chargeSubscriptionRenewal,
154
+ syncPaystackPlans,
155
+ syncPaystackProducts,
156
+ } from "@alexasomba/better-auth-paystack";
157
+
158
+ const ctx = { context: await auth.$context } as any;
159
+ const paystackOptions = {
160
+ secretKey: process.env.PAYSTACK_SECRET_KEY!,
161
+ webhook: { secret: process.env.PAYSTACK_WEBHOOK_SECRET! },
162
+ paystackClient,
163
+ };
164
+
165
+ await syncPaystackProducts(ctx, paystackOptions);
166
+ await syncPaystackPlans(ctx, paystackOptions);
167
+ await chargeSubscriptionRenewal(ctx, paystackOptions, {
168
+ subscriptionId: "sub_123",
169
+ });
170
+ ```
171
+
172
+ These operations are intentionally server-only. Do not expose them through browser-triggered auth client calls.
173
+
174
+ ---
175
+
106
176
  ## Billing Patterns
107
177
 
108
178
  ### 1. Subscriptions
@@ -195,7 +265,7 @@ Enable `organization.enabled` to bill organizations instead of users.
195
265
 
196
266
  ### Inline Popup Modal
197
267
 
198
- Use `@alexasomba/paystack-browser` for a seamless UI.
268
+ Use `@alexasomba/paystack-inline` for a seamless UI.
199
269
 
200
270
  ```ts
201
271
  const { data } = await authClient.subscription.upgrade({ plan: "pro" });
@@ -203,8 +273,7 @@ if (data?.accessCode) {
203
273
  const paystack = createPaystack({ publicKey: "pk_test_..." });
204
274
  paystack.checkout({
205
275
  accessCode: data.accessCode,
206
- onSuccess: (res) =>
207
- authClient.paystack.transaction.verify({ reference: res.reference }),
276
+ onSuccess: (res) => authClient.paystack.transaction.verify({ reference: res.reference }),
208
277
  });
209
278
  }
210
279
  ```
@@ -212,6 +281,7 @@ if (data?.accessCode) {
212
281
  ### Scheduled Changes & Cancellation
213
282
 
214
283
  Defer changes to the end of the current billing cycle:
284
+
215
285
  - **Upgrades**: Pass `scheduleAtPeriodEnd: true` in `initializeTransaction()`.
216
286
  - **Cancellations**: Use `authClient.subscription.cancel({ atPeriodEnd: true })` to keep the subscription active until the period ends.
217
287
 
@@ -228,6 +298,20 @@ await authClient.paystack.transaction.initialize({
228
298
  });
229
299
  ```
230
300
 
301
+ ### Webhook Security
302
+
303
+ The plugin automatically verifies the `x-paystack-signature` header to ensure events are authentic. For an extra layer of security, you can enable **IP Whitelisting** to restrict processing to Paystack's official servers.
304
+
305
+ ```ts
306
+ paystack({
307
+ webhook: {
308
+ secret: process.env.PAYSTACK_WEBHOOK_SECRET!,
309
+ verifyIP: true, // Enable IP whitelisting (defaults to false for flexible proxy support)
310
+ trustedIPs: ["52.31.139.75", "52.49.173.169", "52.214.14.220"], // Optional: override trusted IPs
311
+ },
312
+ });
313
+ ```
314
+
231
315
  ### Trial Abuse Prevention
232
316
 
233
317
  The plugin checks the `referenceId` history. If a trial was ever used (active, expired, or trialing), it will not be granted again, preventing resubscribe-abuse.
@@ -240,9 +324,7 @@ React to billing events on the server by providing callbacks in your configurati
240
324
 
241
325
  - `onSubscriptionComplete`: Called after successful transaction verification (Native or Local).
242
326
  - `onSubscriptionCreated`: Called when a subscription record is first initialized in the DB.
243
- - `onSubscriptionUpdate`: Called whenever a subscription's status or period is updated.
244
327
  - `onSubscriptionCancel`: Called when a user or organization cancels their subscription.
245
- - `onSubscriptionDelete`: Called when a subscription record is deleted.
246
328
 
247
329
  #### Customer Hooks (`top-level` or `organization.*`)
248
330
 
@@ -252,12 +334,33 @@ React to billing events on the server by providing callbacks in your configurati
252
334
  #### Trial Hooks (`subscription.plans[].freeTrial.*`)
253
335
 
254
336
  - `onTrialStart`: Called when a new trial period begins.
255
- - `onTrialEnd`: Called when a trial period ends naturally or via manual upgrade.
256
337
 
257
338
  #### Global Hook
258
339
 
259
340
  - `onEvent`: Receives every webhook event payload sent from Paystack for custom processing.
260
341
 
342
+ ### Trusted Server Operations
343
+
344
+ Recurring renewals and Paystack catalog sync are intentionally not exposed through the browser auth client.
345
+ Invoke them from trusted backend code only:
346
+
347
+ ```ts
348
+ import {
349
+ chargeSubscriptionRenewal,
350
+ syncPaystackPlans,
351
+ syncPaystackProducts,
352
+ } from "@alexasomba/better-auth-paystack";
353
+
354
+ const ctx = { context: await auth.$context } as any;
355
+
356
+ await chargeSubscriptionRenewal(ctx, paystackOptions, {
357
+ subscriptionId: "sub_123",
358
+ });
359
+
360
+ await syncPaystackProducts(ctx, paystackOptions);
361
+ await syncPaystackPlans(ctx, paystackOptions);
362
+ ```
363
+
261
364
  ### Authorization & Security
262
365
 
263
366
  #### `authorizeReference`
@@ -428,36 +531,36 @@ The plugin extends your database with the following fields and tables.
428
531
 
429
532
  ### `paystackTransaction`
430
533
 
431
- | Field | Type | Required | Description |
432
- | :------------ | :------- | :------- | :------------------------------------------------ |
433
- | `reference` | `string` | Yes | Unique transaction reference. |
434
- | `referenceId` | `string` | Yes | Associated User ID or Organization ID. |
435
- | `userId` | `string` | Yes | The ID of the user who initiated the transaction. |
436
- | `amount` | `number` | Yes | Transaction amount in smallest currency unit. |
437
- | `currency` | `string` | Yes | Currency code (e.g., "NGN"). |
438
- | `status` | `string` | Yes | `success`, `pending`, `failed`, `abandoned`. |
439
- | `plan` | `string` | No | Name of the plan associated with the transaction. |
534
+ | Field | Type | Required | Description |
535
+ | :------------ | :------- | :------- | :--------------------------------------------------- |
536
+ | `reference` | `string` | Yes | Unique transaction reference. |
537
+ | `referenceId` | `string` | Yes | Associated User ID or Organization ID. |
538
+ | `userId` | `string` | Yes | The ID of the user who initiated the transaction. |
539
+ | `amount` | `number` | Yes | Transaction amount in smallest currency unit. |
540
+ | `currency` | `string` | Yes | Currency code (e.g., "NGN"). |
541
+ | `status` | `string` | Yes | `success`, `pending`, `failed`, `abandoned`. |
542
+ | `plan` | `string` | No | Name of the plan associated with the transaction. |
440
543
  | `product` | `string` | No | Name of the product associated with the transaction. |
441
- | `metadata` | `string` | No | JSON string of extra transaction metadata. |
442
- | `paystackId` | `string` | No | The internal Paystack ID for the transaction. |
443
- | `createdAt` | `Date` | Yes | Transaction creation timestamp. |
444
- | `updatedAt` | `Date` | Yes | Transaction last update timestamp. |
544
+ | `metadata` | `string` | No | JSON string of extra transaction metadata. |
545
+ | `paystackId` | `string` | No | The internal Paystack ID for the transaction. |
546
+ | `createdAt` | `Date` | Yes | Transaction creation timestamp. |
547
+ | `updatedAt` | `Date` | Yes | Transaction last update timestamp. |
445
548
 
446
549
  ### `paystackProduct`
447
550
 
448
- | Field | Type | Required | Description |
449
- | :------------ | :-------- | :------- | :------------------------------------------------ |
450
- | `name` | `string` | Yes | Product name. |
451
- | `description` | `string` | No | Product description. |
452
- | `price` | `number` | Yes | Price in smallest currency unit. |
453
- | `currency` | `string` | Yes | Currency code (e.g., "NGN"). |
454
- | `quantity` | `number` | No | Available stock quantity. |
455
- | `unlimited` | `boolean` | No | Whether the product has unlimited stock. |
456
- | `paystackId` | `string` | No | The internal Paystack Product ID. |
457
- | `slug` | `string` | Yes | Unique slug for the product. |
458
- | `metadata` | `string` | No | JSON string of extra product metadata. |
459
- | `createdAt` | `Date` | Yes | Product creation timestamp. |
460
- | `updatedAt` | `Date` | Yes | Product last update timestamp. |
551
+ | Field | Type | Required | Description |
552
+ | :------------ | :-------- | :------- | :--------------------------------------- |
553
+ | `name` | `string` | Yes | Product name. |
554
+ | `description` | `string` | No | Product description. |
555
+ | `price` | `number` | Yes | Price in smallest currency unit. |
556
+ | `currency` | `string` | Yes | Currency code (e.g., "NGN"). |
557
+ | `quantity` | `number` | No | Available stock quantity. |
558
+ | `unlimited` | `boolean` | No | Whether the product has unlimited stock. |
559
+ | `paystackId` | `string` | No | The internal Paystack Product ID. |
560
+ | `slug` | `string` | Yes | Unique slug for the product. |
561
+ | `metadata` | `string` | No | JSON string of extra product metadata. |
562
+ | `createdAt` | `Date` | Yes | Product creation timestamp. |
563
+ | `updatedAt` | `Date` | Yes | Product last update timestamp. |
461
564
 
462
565
  ---
463
566
 
@@ -474,6 +577,7 @@ The plugin extends your database with the following fields and tables.
474
577
  The plugin's schema definition includes recommended indexes and uniqueness constraints for performance. When you run `npx better-auth migrate`, these will be automatically applied to your database.
475
578
 
476
579
  The following fields are indexed:
580
+
477
581
  - **`paystackTransaction`**: `reference` (unique), `userId`, `referenceId`.
478
582
  - **`subscription`**: `paystackSubscriptionCode` (unique), `referenceId`, `paystackTransactionReference`, `paystackCustomerCode`, `plan`.
479
583
  - **`user` & `organization`**: `paystackCustomerCode`.
@@ -484,9 +588,11 @@ The following fields are indexed:
484
588
  The plugin provides two ways to keep your product inventory in sync with Paystack:
485
589
 
486
590
  #### 1. Automated Inventory Sync (New)
591
+
487
592
  Whenever a successful one-time payment is made (via webhook or manual verification), the plugin automatically calls **`syncProductQuantityFromPaystack`**. This fetches the real-time remaining quantity from the Paystack API and updates your local database record, ensuring your inventory is always accurate.
488
593
 
489
594
  #### 2. Manual Bulk Sync
595
+
490
596
  You can synchronize all products with your local database using the `/paystack/sync-products` endpoint.
491
597
 
492
598
  ```bash
@@ -497,20 +603,23 @@ POST /api/auth/paystack/sync-products
497
603
 
498
604
  ## 🏗️ Development & Contributing
499
605
 
500
- This repository is set up as a pnpm workspace. You can run and build examples via `--filter`.
606
+ This repository is powered by **Vite+**. You use the `vp` CLI to manage the entire workspace.
501
607
 
502
608
  ```bash
503
- # Install everything
504
- pnpm install
609
+ # Install dependencies
610
+ vp i
611
+
612
+ # Check project health (format, lint, types)
613
+ vp check --fix
505
614
 
506
615
  # Build the core library
507
- pnpm --filter "@alexasomba/better-auth-paystack" build
616
+ vp build
508
617
 
509
- # Run Next.js example (Next.js + Better Auth)
510
- pnpm --filter nextjs-better-auth-paystack dev
618
+ # Run tests
619
+ vp test
511
620
 
512
- # Run TanStack Start example (TanStack Start + Better Auth)
513
- pnpm --filter tanstack-start-better-auth-paystack dev
621
+ # Run the TanStack Start example
622
+ vp run examples/tanstack dev
514
623
  ```
515
624
 
516
625
  Contributions are welcome! Please open an issue or pull request.
@@ -526,11 +635,11 @@ Future features planned for upcoming versions:
526
635
  ### v1.1.0 - Manual Recurring Subscriptions (Available Now)
527
636
 
528
637
  - [x] **Stored Authorization Codes**: Securely store Paystack authorization codes from verified transactions.
529
- - [x] **Charge Authorization Endpoint**: Server-side endpoint (`/charge-recurring`) to charge stored cards for renewals.
638
+ - [x] **Trusted Renewal Operation**: Server-side helper to charge stored cards for renewals.
530
639
  - [ ] **Card Management UI**: Let users view/delete saved payment methods (masked card data only) - _Upcoming_
531
640
  - [ ] **Renewal Scheduler Integration**: Documentation for integrating with Cloudflare Workers Cron, Vercel Cron, etc. - _Upcoming_
532
641
 
533
- > **Note**: For local-managed subscriptions (no `planCode`), the plugin now automatically captures and stores the `authorization_code`. You can trigger renewals using `authClient.paystack.chargeRecurringSubscription({ subscriptionId })`.
642
+ > **Note**: For local-managed subscriptions (no `planCode`), the plugin automatically captures and stores the `authorization_code`. Trigger renewals from trusted backend code with `chargeSubscriptionRenewal(...)`.
534
643
 
535
644
  ### Future Considerations
536
645
 
@@ -542,7 +651,7 @@ Future features planned for upcoming versions:
542
651
 
543
652
  - GitHub Repository: [alexasomba/better-auth-paystack](https://github.com/alexasomba/better-auth-paystack)
544
653
  - Comprehensive and up-to-date Paystack Node SDK: [alexasomba/paystack-node](https://github.com/alexasomba/paystack-node)
545
- - Comprehensive and up-to-date Paystack Browser SDK: [alexasomba/paystack-browser](https://github.com/alexasomba/paystack-browser)
654
+ - Comprehensive and up-to-date Paystack Inline SDK: [alexasomba/paystack-inline](https://github.com/alexasomba/paystack-inline)
546
655
  - [TanStack Start Example Implementation](https://github.com/alexasomba/better-auth-paystack/tree/main/examples/tanstack)
547
656
  - Paystack Webhooks: https://paystack.com/docs/payments/webhooks/
548
657
  - Paystack Transaction API: https://paystack.com/docs/api/transaction/