better-auth-payu 0.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 +722 -0
- package/dist/client-fGPaKb7O.d.ts +48 -0
- package/dist/client-fGPaKb7O.d.ts.map +1 -0
- package/dist/client.js +39 -0
- package/dist/client.js.map +1 -0
- package/dist/error-codes-CZKuS8SB.js +46 -0
- package/dist/error-codes-CZKuS8SB.js.map +1 -0
- package/dist/index-B1s85S1-.d.ts +828 -0
- package/dist/index-B1s85S1-.d.ts.map +1 -0
- package/dist/index-Dnld5Msn.d.ts +2 -0
- package/dist/index.js +1814 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
# better-auth-payu(Community Plugin)
|
|
2
|
+
|
|
3
|
+
A comprehensive [PayU](https://payu.in) plugin for [Better Auth](https://better-auth.com) โ subscriptions via Standing Instructions (SI/e-Mandate), payments, refunds, webhooks, and organization billing out of the box.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ๐ **Subscription lifecycle** โ create, cancel, pause, resume, update via PayU Standing Instructions
|
|
10
|
+
- ๐ณ **Payment processing** โ initiate, verify, and check payments with hash-based security
|
|
11
|
+
- ๐ฆ **Mandate management** โ check status, modify amount, support for card/UPI/netbanking mandates
|
|
12
|
+
- ๐ฐ **Refund handling** โ initiate and track refund status
|
|
13
|
+
- ๐ **Webhook handling** โ SHA-512 hash-verified webhook processing for all events
|
|
14
|
+
- ๐ข **Organization billing** โ per-org subscriptions with seat management
|
|
15
|
+
- ๐งช **Test & production** โ built-in support for PayU sandbox and live environments
|
|
16
|
+
- ๐ **Type-safe** โ full TypeScript support with typed error codes and status helpers
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install better-auth-payu
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Server Setup
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { betterAuth } from "better-auth";
|
|
32
|
+
import { payu } from "better-auth-payu";
|
|
33
|
+
|
|
34
|
+
export const auth = betterAuth({
|
|
35
|
+
plugins: [
|
|
36
|
+
payu({
|
|
37
|
+
merchantKey: process.env.PAYU_MERCHANT_KEY!,
|
|
38
|
+
merchantSalt: process.env.PAYU_MERCHANT_SALT!,
|
|
39
|
+
apiBaseUrl: "https://test.payu.in", // Use production URL in prod
|
|
40
|
+
|
|
41
|
+
subscription: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
plans: [
|
|
44
|
+
{
|
|
45
|
+
planId: "pro-monthly",
|
|
46
|
+
name: "pro",
|
|
47
|
+
amount: "999",
|
|
48
|
+
billingCycle: "MONTHLY",
|
|
49
|
+
billingInterval: 1,
|
|
50
|
+
totalCount: 12,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
planId: "enterprise-yearly",
|
|
54
|
+
name: "enterprise",
|
|
55
|
+
amount: "9999",
|
|
56
|
+
billingCycle: "YEARLY",
|
|
57
|
+
billingInterval: 1,
|
|
58
|
+
totalCount: 5,
|
|
59
|
+
trialDays: 14,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
onSubscriptionActivated: async ({ subscription, plan, event }) => {
|
|
64
|
+
console.log(`Subscription activated: ${subscription.id}`);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
onPaymentSuccess: async ({ subscription, event }) => {
|
|
68
|
+
console.log(`Payment received for: ${subscription.id}`);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Client Setup
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createAuthClient } from "better-auth/client";
|
|
80
|
+
import { payuClient } from "better-auth-payu/client";
|
|
81
|
+
|
|
82
|
+
export const client = createAuthClient({
|
|
83
|
+
plugins: [
|
|
84
|
+
payuClient({
|
|
85
|
+
subscription: true,
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. Webhook Endpoint
|
|
92
|
+
|
|
93
|
+
Register your webhook URL in the [PayU Dashboard](https://dashboard.payu.in):
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
https://your-domain.com/api/auth/payu/webhook
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
### `PayUOptions`
|
|
104
|
+
|
|
105
|
+
| Option | Type | Required | Description |
|
|
106
|
+
| --------------- | --------------------- | -------- | ------------------------------------------ |
|
|
107
|
+
| `merchantKey` | `string` | โ
| PayU Merchant Key |
|
|
108
|
+
| `merchantSalt` | `string` | โ
| PayU Merchant Salt (V2 recommended) |
|
|
109
|
+
| `apiBaseUrl` | `string` | โ | PayU API base URL (defaults to production) |
|
|
110
|
+
| `webhookSecret` | `string` | โ | Additional webhook secret for verification |
|
|
111
|
+
| `subscription` | `SubscriptionOptions` | โ | Subscription/SI configuration |
|
|
112
|
+
| `organization` | `OrganizationOptions` | โ | Organization billing configuration |
|
|
113
|
+
| `schema` | `object` | โ | Custom schema overrides |
|
|
114
|
+
|
|
115
|
+
### `SubscriptionOptions`
|
|
116
|
+
|
|
117
|
+
Enable subscriptions by setting `subscription.enabled: true`:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
subscription: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
plans: [...],
|
|
123
|
+
|
|
124
|
+
// Optional settings
|
|
125
|
+
defaultPlan: "pro",
|
|
126
|
+
startOnConsent: false,
|
|
127
|
+
requireOrganization: false,
|
|
128
|
+
|
|
129
|
+
// Payment & subscription callbacks
|
|
130
|
+
onPaymentSuccess: async ({ subscription, plan, event }) => {},
|
|
131
|
+
onPaymentFailure: async ({ event, error }) => {},
|
|
132
|
+
onSubscriptionActivated: async ({ subscription, plan, event }) => {},
|
|
133
|
+
onSubscriptionAuthenticated: async ({ subscription, plan, event }) => {},
|
|
134
|
+
onSubscriptionCharged: async ({ subscription, plan, event }) => {},
|
|
135
|
+
onSubscriptionCancelled: async ({ subscription, plan, event }) => {},
|
|
136
|
+
onSubscriptionPaused: async ({ subscription, plan, event }) => {},
|
|
137
|
+
onSubscriptionResumed: async ({ subscription, plan, event }) => {},
|
|
138
|
+
onSubscriptionHalted: async ({ subscription, plan, event }) => {},
|
|
139
|
+
onSubscriptionPending: async ({ subscription, plan, event }) => {},
|
|
140
|
+
onSubscriptionCompleted: async ({ subscription, plan, event }) => {},
|
|
141
|
+
|
|
142
|
+
// Mandate callbacks
|
|
143
|
+
onMandateRevoked: async ({ subscription, plan, event }) => {},
|
|
144
|
+
onMandateModified: async ({ subscription, plan, event }) => {},
|
|
145
|
+
onRefundComplete: async ({ event, refund }) => {},
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Plan Configuration
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
interface PayUPlan {
|
|
153
|
+
planId: string; // Unique plan identifier
|
|
154
|
+
name: string; // Plan name used internally
|
|
155
|
+
amount: string; // Amount per billing cycle (e.g., "999")
|
|
156
|
+
billingCycle: PayUBillingCycle; // DAILY, WEEKLY, MONTHLY, etc.
|
|
157
|
+
billingInterval: number; // How many cycles between charges (1 = every cycle)
|
|
158
|
+
totalCount: number; // Total number of charges (0 = unlimited)
|
|
159
|
+
annualPlanId?: string; // Optional annual variant ID
|
|
160
|
+
trialDays?: number; // Free trial duration in days
|
|
161
|
+
metadata?: Record<string, unknown>; // Additional plan metadata
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Billing Cycle Options
|
|
166
|
+
|
|
167
|
+
| Cycle | Description |
|
|
168
|
+
| ------------- | -------------- |
|
|
169
|
+
| `DAILY` | Every day |
|
|
170
|
+
| `WEEKLY` | Every week |
|
|
171
|
+
| `FORTNIGHTLY` | Every 2 weeks |
|
|
172
|
+
| `MONTHLY` | Every month |
|
|
173
|
+
| `BIMONTHLY` | Every 2 months |
|
|
174
|
+
| `QUARTERLY` | Every 3 months |
|
|
175
|
+
| `BI_YEARLY` | Every 6 months |
|
|
176
|
+
| `YEARLY` | Every year |
|
|
177
|
+
| `ADHOC` | On-demand |
|
|
178
|
+
| `ASPRESENTED` | As presented |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## API Endpoints
|
|
183
|
+
|
|
184
|
+
### Subscription Endpoints
|
|
185
|
+
|
|
186
|
+
| Method | Path | Description |
|
|
187
|
+
| ------ | -------------------------------------- | -------------------------------------- |
|
|
188
|
+
| `POST` | `/payu/subscription/create` | Create a new subscription |
|
|
189
|
+
| `POST` | `/payu/subscription/pay-and-subscribe` | Initiate payment & create subscription |
|
|
190
|
+
| `POST` | `/payu/subscription/cancel` | Cancel an active subscription |
|
|
191
|
+
| `POST` | `/payu/subscription/pause` | Pause an active subscription |
|
|
192
|
+
| `POST` | `/payu/subscription/resume` | Resume a paused subscription |
|
|
193
|
+
| `GET` | `/payu/subscription/list` | List subscriptions for user/reference |
|
|
194
|
+
| `GET` | `/payu/subscription/get` | Get a specific subscription |
|
|
195
|
+
| `POST` | `/payu/subscription/update` | Update a subscription |
|
|
196
|
+
| `POST` | `/payu/subscription/pre-debit-notify` | Send pre-debit notification |
|
|
197
|
+
| `POST` | `/payu/subscription/charge` | Charge a subscription |
|
|
198
|
+
| `POST` | `/payu/subscription/update-si` | Update standing instruction |
|
|
199
|
+
|
|
200
|
+
### Mandate Endpoints
|
|
201
|
+
|
|
202
|
+
| Method | Path | Description |
|
|
203
|
+
| ------ | ---------------------- | --------------------- |
|
|
204
|
+
| `GET` | `/payu/mandate/status` | Check mandate status |
|
|
205
|
+
| `POST` | `/payu/mandate/modify` | Modify mandate amount |
|
|
206
|
+
|
|
207
|
+
### Payment Endpoints
|
|
208
|
+
|
|
209
|
+
| Method | Path | Description |
|
|
210
|
+
| ------ | ------------------------ | ---------------------------- |
|
|
211
|
+
| `POST` | `/payu/payment/initiate` | Initiate a payment with hash |
|
|
212
|
+
| `POST` | `/payu/payment/verify` | Verify a completed payment |
|
|
213
|
+
| `POST` | `/payu/payment/check` | Check payment status |
|
|
214
|
+
|
|
215
|
+
### Refund Endpoints
|
|
216
|
+
|
|
217
|
+
| Method | Path | Description |
|
|
218
|
+
| ------ | ----------------------- | -------------------------- |
|
|
219
|
+
| `POST` | `/payu/refund/initiate` | Initiate a refund |
|
|
220
|
+
| `GET` | `/payu/refund/status` | Check refund status |
|
|
221
|
+
| `POST` | `/payu/refund/list` | List refunds for reference |
|
|
222
|
+
|
|
223
|
+
### Transaction Endpoints
|
|
224
|
+
|
|
225
|
+
| Method | Path | Description |
|
|
226
|
+
| ------ | --------------------------- | ----------------------------- |
|
|
227
|
+
| `GET` | `/payu/transaction/info` | Get transaction info by txnid |
|
|
228
|
+
| `GET` | `/payu/transaction/details` | Get detailed transaction data |
|
|
229
|
+
|
|
230
|
+
### Utility Endpoints
|
|
231
|
+
|
|
232
|
+
| Method | Path | Description |
|
|
233
|
+
| ------ | ------------------------ | ------------------------- |
|
|
234
|
+
| `POST` | `/payu/upi/validate-vpa` | Validate a UPI VPA |
|
|
235
|
+
| `GET` | `/payu/plan/list` | List all configured plans |
|
|
236
|
+
| `GET` | `/payu/plan/get` | Fetch a specific plan |
|
|
237
|
+
|
|
238
|
+
### Webhook
|
|
239
|
+
|
|
240
|
+
| Method | Path | Description |
|
|
241
|
+
| ------ | --------------- | --------------------- |
|
|
242
|
+
| `POST` | `/payu/webhook` | PayU webhook receiver |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Usage Examples
|
|
247
|
+
|
|
248
|
+
### Create Subscription
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
// Client-side
|
|
252
|
+
const { data } = await client.payu.subscription.create({
|
|
253
|
+
plan: "pro",
|
|
254
|
+
mandateType: "upi", // "card" | "upi" | "netbanking"
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Pay and Subscribe (Combined)
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const { data } = await client.payu.subscription.payAndSubscribe({
|
|
262
|
+
plan: "pro",
|
|
263
|
+
mandateType: "card",
|
|
264
|
+
initialAmount: "999",
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Cancel Subscription
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// Cancel immediately
|
|
272
|
+
await client.payu.subscription.cancel({});
|
|
273
|
+
|
|
274
|
+
// Cancel at end of billing cycle
|
|
275
|
+
await client.payu.subscription.cancel({
|
|
276
|
+
cancelAtCycleEnd: true,
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Pause / Resume
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
await client.payu.subscription.pause({});
|
|
284
|
+
await client.payu.subscription.resume({});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### List Subscriptions
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const { data } = await client.payu.subscription.list({});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Initiate Payment
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
const { data } = await client.payu.payment.initiate({
|
|
297
|
+
txnid: "TXN_" + Date.now(),
|
|
298
|
+
amount: "500",
|
|
299
|
+
productinfo: "Premium Plan",
|
|
300
|
+
firstname: "John",
|
|
301
|
+
email: "john@example.com",
|
|
302
|
+
phone: "9876543210",
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Check Mandate Status
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const { data } = await client.payu.mandate.status({
|
|
310
|
+
subscriptionId: "sub_123",
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Organization Subscription
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const { data } = await client.payu.subscription.create({
|
|
318
|
+
plan: "enterprise",
|
|
319
|
+
referenceId: "org_123",
|
|
320
|
+
customerType: "organization",
|
|
321
|
+
mandateType: "card",
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Standing Instructions (SI) / e-Mandate
|
|
328
|
+
|
|
329
|
+
PayU uses Standing Instructions (SI) for recurring payments, which work through mandates (payment authorizations):
|
|
330
|
+
|
|
331
|
+
```mermaid
|
|
332
|
+
sequenceDiagram
|
|
333
|
+
participant User
|
|
334
|
+
participant App
|
|
335
|
+
participant PayU
|
|
336
|
+
participant Bank
|
|
337
|
+
|
|
338
|
+
User->>App: Subscribe to plan
|
|
339
|
+
App->>PayU: Create SI transaction
|
|
340
|
+
PayU->>Bank: Request mandate registration
|
|
341
|
+
Bank->>User: Authorize mandate (2FA)
|
|
342
|
+
User->>Bank: Approve
|
|
343
|
+
Bank->>PayU: Mandate registered
|
|
344
|
+
PayU->>App: Webhook (authenticated)
|
|
345
|
+
|
|
346
|
+
Note over PayU: Recurring cycle begins
|
|
347
|
+
App->>PayU: Pre-debit notification
|
|
348
|
+
PayU->>User: 24hr advance notice
|
|
349
|
+
PayU->>Bank: Debit amount
|
|
350
|
+
Bank->>PayU: Payment success
|
|
351
|
+
PayU->>App: Webhook (payment success)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Mandate Types
|
|
355
|
+
|
|
356
|
+
| Type | Description |
|
|
357
|
+
| ------------ | -------------------------------------------- |
|
|
358
|
+
| `card` | Card-based recurring payments (debit/credit) |
|
|
359
|
+
| `upi` | UPI autopay mandate |
|
|
360
|
+
| `netbanking` | Netbanking e-mandate (eNACH/eSign) |
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Organization Integration
|
|
365
|
+
|
|
366
|
+
Enable organization-level billing by adding the `organization` option. Requires the Better Auth [Organization plugin](https://www.better-auth.com/docs/plugins/organization).
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
payu({
|
|
370
|
+
merchantKey: "...",
|
|
371
|
+
merchantSalt: "...",
|
|
372
|
+
|
|
373
|
+
organization: {
|
|
374
|
+
enabled: true,
|
|
375
|
+
seatManagement: true,
|
|
376
|
+
authorizeReference: async ({ action, organizationId, userId, role }) => {
|
|
377
|
+
// Check if user has permission to manage org subscriptions
|
|
378
|
+
return role === "owner" || role === "admin";
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
subscription: {
|
|
383
|
+
enabled: true,
|
|
384
|
+
plans: [...],
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Authorization Actions
|
|
390
|
+
|
|
391
|
+
When `authorizeReference` is configured, it's called for these actions:
|
|
392
|
+
|
|
393
|
+
| Action | When |
|
|
394
|
+
| ---------------------- | ----------------------------- |
|
|
395
|
+
| `create-subscription` | Creating a new subscription |
|
|
396
|
+
| `cancel-subscription` | Cancelling a subscription |
|
|
397
|
+
| `pause-subscription` | Pausing a subscription |
|
|
398
|
+
| `resume-subscription` | Resuming a subscription |
|
|
399
|
+
| `update-subscription` | Updating subscription details |
|
|
400
|
+
| `list-subscriptions` | Listing subscriptions |
|
|
401
|
+
| `get-subscription` | Fetching a subscription |
|
|
402
|
+
| `check-mandate-status` | Checking mandate status |
|
|
403
|
+
| `modify-mandate` | Modifying mandate amount |
|
|
404
|
+
| `initiate-payment` | Initiating a payment |
|
|
405
|
+
| `verify-payment` | Verifying a payment |
|
|
406
|
+
| `initiate-refund` | Creating a refund |
|
|
407
|
+
| `check-refund-status` | Checking refund status |
|
|
408
|
+
| `list-refunds` | Listing refunds |
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Database Schema
|
|
413
|
+
|
|
414
|
+
The plugin automatically extends your database with the following tables/fields:
|
|
415
|
+
|
|
416
|
+
### `user` table (extended)
|
|
417
|
+
|
|
418
|
+
| Field | Type | Description |
|
|
419
|
+
| ---------------- | --------- | ------------------------ |
|
|
420
|
+
| `payuCustomerId` | `string?` | PayU customer identifier |
|
|
421
|
+
|
|
422
|
+
### `subscription` table (new)
|
|
423
|
+
|
|
424
|
+
| Field | Type | Description |
|
|
425
|
+
| -------------------- | ---------- | ------------------------------------------ |
|
|
426
|
+
| `id` | `string` | Primary key |
|
|
427
|
+
| `plan` | `string` | Plan name |
|
|
428
|
+
| `referenceId` | `string` | User ID or organization ID |
|
|
429
|
+
| `payuCustomerId` | `string?` | PayU customer ID |
|
|
430
|
+
| `payuSubscriptionId` | `string?` | PayU subscription ID |
|
|
431
|
+
| `payuMandateType` | `string?` | Mandate type (`card`, `upi`, `netbanking`) |
|
|
432
|
+
| `payuTransactionId` | `string?` | PayU transaction ID |
|
|
433
|
+
| `payuMihpayid` | `string?` | PayU payment ID |
|
|
434
|
+
| `status` | `string` | Subscription status (default: `"created"`) |
|
|
435
|
+
| `currentStart` | `date?` | Current billing cycle start |
|
|
436
|
+
| `currentEnd` | `date?` | Current billing cycle end |
|
|
437
|
+
| `endedAt` | `date?` | When subscription ended |
|
|
438
|
+
| `quantity` | `number?` | Billing quantity (default: `1`) |
|
|
439
|
+
| `totalCount` | `number?` | Total billing cycles |
|
|
440
|
+
| `paidCount` | `number?` | Completed billing cycles (default: `0`) |
|
|
441
|
+
| `remainingCount` | `number?` | Remaining billing cycles |
|
|
442
|
+
| `cancelledAt` | `date?` | Cancellation timestamp |
|
|
443
|
+
| `pausedAt` | `date?` | Pause timestamp |
|
|
444
|
+
| `cancelAtCycleEnd` | `boolean?` | Scheduled cancellation flag |
|
|
445
|
+
| `billingPeriod` | `string?` | Billing period |
|
|
446
|
+
| `seats` | `number?` | Number of seats |
|
|
447
|
+
| `trialStart` | `date?` | Trial start date |
|
|
448
|
+
| `trialEnd` | `date?` | Trial end date |
|
|
449
|
+
| `metadata` | `string?` | JSON metadata |
|
|
450
|
+
|
|
451
|
+
### `organization` table (extended, when enabled)
|
|
452
|
+
|
|
453
|
+
| Field | Type | Description |
|
|
454
|
+
| ---------------- | --------- | ------------------------ |
|
|
455
|
+
| `payuCustomerId` | `string?` | PayU customer identifier |
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Subscription Status Lifecycle
|
|
460
|
+
|
|
461
|
+
```mermaid
|
|
462
|
+
stateDiagram-v2
|
|
463
|
+
[*] --> created
|
|
464
|
+
created --> authenticated : Mandate authorized
|
|
465
|
+
authenticated --> active : First payment success
|
|
466
|
+
active --> paused : Pause requested
|
|
467
|
+
paused --> active : Resume requested
|
|
468
|
+
active --> pending : Payment retry in progress
|
|
469
|
+
pending --> active : Retry succeeds
|
|
470
|
+
pending --> halted : All retries exhausted
|
|
471
|
+
halted --> active : Manual resolution
|
|
472
|
+
active --> cancelled : Cancellation / Mandate revoked
|
|
473
|
+
active --> completed : All cycles done
|
|
474
|
+
cancelled --> [*]
|
|
475
|
+
completed --> [*]
|
|
476
|
+
expired --> [*]
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
| Status | Description |
|
|
480
|
+
| --------------- | ------------------------------------------------ |
|
|
481
|
+
| `created` | Subscription created, awaiting SI authentication |
|
|
482
|
+
| `authenticated` | Mandate registered, awaiting first charge |
|
|
483
|
+
| `active` | Active and charging on schedule |
|
|
484
|
+
| `pending` | Payment retry in progress |
|
|
485
|
+
| `halted` | All payment retries exhausted |
|
|
486
|
+
| `paused` | Temporarily paused |
|
|
487
|
+
| `cancelled` | Cancelled or mandate revoked |
|
|
488
|
+
| `completed` | All billing cycles completed |
|
|
489
|
+
| `expired` | Subscription expired |
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Webhook Events
|
|
494
|
+
|
|
495
|
+
The plugin handles these webhook events, routed by the `status` and `notificationType` fields:
|
|
496
|
+
|
|
497
|
+
| Event Condition | Handler | Description |
|
|
498
|
+
| --------------------------------------------------------- | ---------------------------------------------- | ---------------------- |
|
|
499
|
+
| `status = "success"` | `onPaymentSuccess` + `onSubscriptionActivated` | Payment successful |
|
|
500
|
+
| `status = "failure"` | `onPaymentFailure` | Payment failed |
|
|
501
|
+
| `status = "pending"` | `onSubscriptionPending` | Payment pending |
|
|
502
|
+
| `status = "halted"` | `onSubscriptionHalted` | Retries exhausted |
|
|
503
|
+
| `status = "cancelled"` | `onSubscriptionCancelled` | Subscription cancelled |
|
|
504
|
+
| `status = "completed"` | `onSubscriptionCompleted` | All cycles complete |
|
|
505
|
+
| `status = "paused"` | `onSubscriptionPaused` | Subscription paused |
|
|
506
|
+
| `status = "resumed"` / `"active"` | `onSubscriptionResumed` | Subscription resumed |
|
|
507
|
+
| `notificationType = "mandate_revoked"` / `"si_cancelled"` | `onMandateRevoked` | Mandate revoked |
|
|
508
|
+
| `notificationType = "mandate_modified"` / `"si_modified"` | `onMandateModified` | Mandate modified |
|
|
509
|
+
|
|
510
|
+
Each handler automatically:
|
|
511
|
+
|
|
512
|
+
1. Verifies the webhook hash (SHA-512)
|
|
513
|
+
2. Finds the corresponding local subscription by `txnid` or UDF fields
|
|
514
|
+
3. Updates the subscription status in your database
|
|
515
|
+
4. Calls your optional lifecycle callback
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## PayU Hash Security
|
|
520
|
+
|
|
521
|
+
PayU uses SHA-512 hash verification for payment security. The plugin handles hash generation and verification automatically:
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
hash = SHA-512(key|txnid|amount|productinfo|firstname|email|udf1|...|udf10||salt)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
For webhook verification (reverse hash):
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
hash = SHA-512(salt|status||||||udf10|udf9|...|udf1|email|firstname|productinfo|amount|txnid|key)
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## UDF Field Mapping
|
|
536
|
+
|
|
537
|
+
PayU uses User Defined Fields (udf1โudf10) for metadata. The plugin reserves the first 5 fields for internal tracking:
|
|
538
|
+
|
|
539
|
+
| UDF Field | Internal Usage |
|
|
540
|
+
| ------------ | -------------------------------------------- |
|
|
541
|
+
| `udf1` | Customer type (`"user"` or `"organization"`) |
|
|
542
|
+
| `udf2` | User ID |
|
|
543
|
+
| `udf3` | Organization ID (if applicable) |
|
|
544
|
+
| `udf4` | Subscription ID |
|
|
545
|
+
| `udf5` | Reference ID |
|
|
546
|
+
| `udf6โudf10` | Available for custom metadata |
|
|
547
|
+
|
|
548
|
+
### UDF Helpers
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
import {
|
|
552
|
+
customerUdf,
|
|
553
|
+
subscriptionUdf,
|
|
554
|
+
udfToParams,
|
|
555
|
+
paramsToUdf,
|
|
556
|
+
} from "better-auth-payu";
|
|
557
|
+
|
|
558
|
+
// Set customer UDF
|
|
559
|
+
const udf = customerUdf.set({ customerType: "user", userId: "user_123" });
|
|
560
|
+
|
|
561
|
+
// Set subscription UDF
|
|
562
|
+
const udf = subscriptionUdf.set({
|
|
563
|
+
userId: "user_123",
|
|
564
|
+
subscriptionId: "sub_456",
|
|
565
|
+
referenceId: "user_123",
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Convert UDF object to PayU API params
|
|
569
|
+
const params = udfToParams(udf);
|
|
570
|
+
// โ { udf1: "user", udf2: "user_123", ... }
|
|
571
|
+
|
|
572
|
+
// Extract UDF from PayU response
|
|
573
|
+
const udf = paramsToUdf(webhookBody);
|
|
574
|
+
const { userId, subscriptionId } = subscriptionUdf.get(udf);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Utility Functions
|
|
580
|
+
|
|
581
|
+
Import these helpers for subscription status checks:
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
import {
|
|
585
|
+
isActive,
|
|
586
|
+
isAuthenticated,
|
|
587
|
+
isPaused,
|
|
588
|
+
isCancelled,
|
|
589
|
+
isTerminal,
|
|
590
|
+
isUsable,
|
|
591
|
+
hasPaymentIssue,
|
|
592
|
+
toSubscriptionStatus,
|
|
593
|
+
timestampToDate,
|
|
594
|
+
dateStringToDate,
|
|
595
|
+
generatePayUHash,
|
|
596
|
+
generateCommandHash,
|
|
597
|
+
verifyPayUHash,
|
|
598
|
+
} from "better-auth-payu";
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Status Helpers
|
|
602
|
+
|
|
603
|
+
| Function | Description |
|
|
604
|
+
| ---------------------- | ------------------------------------------------------ |
|
|
605
|
+
| `isActive(sub)` | Status is `"active"` |
|
|
606
|
+
| `isAuthenticated(sub)` | Status is `"authenticated"` |
|
|
607
|
+
| `isPaused(sub)` | Status is `"paused"` |
|
|
608
|
+
| `isCancelled(sub)` | Status is `"cancelled"` |
|
|
609
|
+
| `isTerminal(sub)` | Status is `"cancelled"`, `"completed"`, or `"expired"` |
|
|
610
|
+
| `isUsable(sub)` | Status is `"active"` or `"authenticated"` |
|
|
611
|
+
| `hasPaymentIssue(sub)` | Status is `"pending"` or `"halted"` |
|
|
612
|
+
|
|
613
|
+
### Hash Helpers
|
|
614
|
+
|
|
615
|
+
| Function | Description |
|
|
616
|
+
| ----------------------------------------------- | -------------------------------------------- |
|
|
617
|
+
| `generatePayUHash(params, salt)` | Generate SHA-512 hash for payment initiation |
|
|
618
|
+
| `generateCommandHash(key, command, var1, salt)` | Generate hash for PayU API commands |
|
|
619
|
+
| `verifyPayUHash(params, salt, hash)` | Verify a reverse hash from PayU webhook |
|
|
620
|
+
|
|
621
|
+
### Date Helpers
|
|
622
|
+
|
|
623
|
+
| Function | Description |
|
|
624
|
+
| --------------------------- | ------------------------------------------------ |
|
|
625
|
+
| `timestampToDate(ts)` | Convert Unix timestamp (seconds) to `Date` |
|
|
626
|
+
| `dateStringToDate(str)` | Convert PayU date string to `Date` |
|
|
627
|
+
| `toSubscriptionStatus(str)` | Convert string to typed `PayUSubscriptionStatus` |
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Error Codes
|
|
632
|
+
|
|
633
|
+
All error codes are exported for client-side matching:
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
import { PAYU_ERROR_CODES } from "better-auth-payu/client";
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
<details>
|
|
640
|
+
<summary>All error codes</summary>
|
|
641
|
+
|
|
642
|
+
| Category | Code | Message |
|
|
643
|
+
| ---------------- | ------------------------------------- | ------------------------------- |
|
|
644
|
+
| **Auth** | `UNAUTHORIZED` | Unauthorized access |
|
|
645
|
+
| | `INVALID_REQUEST_BODY` | Invalid request body |
|
|
646
|
+
| **Subscription** | `SUBSCRIPTION_NOT_FOUND` | Subscription not found |
|
|
647
|
+
| | `SUBSCRIPTION_PLAN_NOT_FOUND` | Subscription plan not found |
|
|
648
|
+
| | `ALREADY_SUBSCRIBED_PLAN` | Already subscribed to this plan |
|
|
649
|
+
| | `REFERENCE_ID_NOT_ALLOWED` | Reference id is not allowed |
|
|
650
|
+
| | `SUBSCRIPTION_NOT_ACTIVE` | Subscription is not active |
|
|
651
|
+
| | `SUBSCRIPTION_ALREADY_CANCELLED` | Already cancelled |
|
|
652
|
+
| | `SUBSCRIPTION_ALREADY_PAUSED` | Already paused |
|
|
653
|
+
| | `SUBSCRIPTION_NOT_PAUSED` | Not paused, cannot resume |
|
|
654
|
+
| | `SUBSCRIPTION_IN_TERMINAL_STATE` | In terminal state |
|
|
655
|
+
| **Customer** | `CUSTOMER_NOT_FOUND` | PayU customer not found |
|
|
656
|
+
| | `UNABLE_TO_CREATE_CUSTOMER` | Unable to create customer |
|
|
657
|
+
| **Webhook** | `WEBHOOK_HASH_NOT_FOUND` | Webhook hash not found |
|
|
658
|
+
| | `WEBHOOK_SECRET_NOT_FOUND` | Webhook secret not found |
|
|
659
|
+
| | `WEBHOOK_ERROR` | Webhook error |
|
|
660
|
+
| | `FAILED_TO_VERIFY_WEBHOOK` | Failed to verify hash |
|
|
661
|
+
| **Hash** | `HASH_GENERATION_FAILED` | Failed to generate hash |
|
|
662
|
+
| | `HASH_VERIFICATION_FAILED` | Hash verification failed |
|
|
663
|
+
| **Mandate** | `MANDATE_NOT_FOUND` | Mandate not found |
|
|
664
|
+
| | `MANDATE_REVOKE_FAILED` | Failed to revoke mandate |
|
|
665
|
+
| | `MANDATE_MODIFY_FAILED` | Failed to modify mandate |
|
|
666
|
+
| | `MANDATE_STATUS_CHECK_FAILED` | Failed to check status |
|
|
667
|
+
| **Payment** | `PAYMENT_INITIATION_FAILED` | Failed to initiate payment |
|
|
668
|
+
| | `PAYMENT_VERIFICATION_FAILED` | Failed to verify payment |
|
|
669
|
+
| | `PAYMENT_NOT_FOUND` | Payment not found |
|
|
670
|
+
| **Refund** | `REFUND_INITIATION_FAILED` | Failed to initiate refund |
|
|
671
|
+
| | `REFUND_STATUS_CHECK_FAILED` | Failed to check status |
|
|
672
|
+
| **Transaction** | `TRANSACTION_NOT_FOUND` | Transaction not found |
|
|
673
|
+
| | `TRANSACTION_DETAILS_FAILED` | Failed to get details |
|
|
674
|
+
| **Pre-Debit** | `PRE_DEBIT_NOTIFICATION_FAILED` | Failed to send notification |
|
|
675
|
+
| **Organization** | `ORGANIZATION_ON_ACTIVE_SUBSCRIPTION` | Org has active subscription |
|
|
676
|
+
| | `ORGANIZATION_NOT_FOUND` | Organization not found |
|
|
677
|
+
| **SI** | `SI_UPDATE_FAILED` | Failed to update SI |
|
|
678
|
+
| | `INVALID_SI_PARAMS` | Invalid SI parameters |
|
|
679
|
+
| **VPA** | `VPA_VALIDATION_FAILED` | Failed to validate VPA |
|
|
680
|
+
| | `INVALID_VPA` | Invalid VPA address |
|
|
681
|
+
|
|
682
|
+
</details>
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## Environment Variables
|
|
687
|
+
|
|
688
|
+
```env
|
|
689
|
+
PAYU_MERCHANT_KEY=your_merchant_key
|
|
690
|
+
PAYU_MERCHANT_SALT=your_merchant_salt_v2
|
|
691
|
+
PAYU_WEBHOOK_SECRET=your_webhook_secret # Optional
|
|
692
|
+
PAYU_API_BASE_URL=https://test.payu.in # Use https://info.payu.in for production
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### PayU API URLs
|
|
696
|
+
|
|
697
|
+
| Environment | Base URL | Payment URL |
|
|
698
|
+
| -------------- | ---------------------- | --------------------------------- |
|
|
699
|
+
| **Test** | `https://test.payu.in` | `https://test.payu.in/_payment` |
|
|
700
|
+
| **Production** | `https://info.payu.in` | `https://secure.payu.in/_payment` |
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## Types
|
|
705
|
+
|
|
706
|
+
All types are exported for your use:
|
|
707
|
+
|
|
708
|
+
```ts
|
|
709
|
+
import type {
|
|
710
|
+
PayUTransactionResponse,
|
|
711
|
+
PayUWebhookEvent,
|
|
712
|
+
Subscription,
|
|
713
|
+
SubscriptionCallbackData,
|
|
714
|
+
SubscriptionOptions,
|
|
715
|
+
} from "better-auth-payu";
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## License
|
|
721
|
+
|
|
722
|
+
MIT
|