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 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