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/dist/index.js ADDED
@@ -0,0 +1,1814 @@
1
+ import { t as PAYU_ERROR_CODES } from "./error-codes-CZKuS8SB.js";
2
+ import { APIError, createEndpoint, createMiddleware } from "better-call";
3
+ import { z } from "zod";
4
+ import { defu } from "defu";
5
+ import { createHmac } from "crypto";
6
+ import { mergeSchema } from "better-auth/db";
7
+
8
+ //#region node_modules/@better-auth/core/dist/context/global.mjs
9
+ const symbol = Symbol.for("better-auth:global");
10
+ let bind = null;
11
+ const __context = {};
12
+ const __betterAuthVersion = "1.4.18";
13
+ /**
14
+ * We store context instance in the globalThis.
15
+ *
16
+ * The reason we do this is that some bundlers, web framework, or package managers might
17
+ * create multiple copies of BetterAuth in the same process intentionally or unintentionally.
18
+ *
19
+ * For example, yarn v1, Next.js, SSR, Vite...
20
+ *
21
+ * @internal
22
+ */
23
+ function __getBetterAuthGlobal() {
24
+ if (!globalThis[symbol]) {
25
+ globalThis[symbol] = {
26
+ version: __betterAuthVersion,
27
+ epoch: 1,
28
+ context: __context
29
+ };
30
+ bind = globalThis[symbol];
31
+ }
32
+ bind = globalThis[symbol];
33
+ if (bind.version !== __betterAuthVersion) {
34
+ bind.version = __betterAuthVersion;
35
+ bind.epoch++;
36
+ }
37
+ return globalThis[symbol];
38
+ }
39
+
40
+ //#endregion
41
+ //#region node_modules/@better-auth/core/dist/async_hooks/index.mjs
42
+ const AsyncLocalStoragePromise = import(
43
+ /* @vite-ignore */
44
+ /* webpackIgnore: true */
45
+ "node:async_hooks"
46
+ ).then((mod) => mod.AsyncLocalStorage).catch((err) => {
47
+ if ("AsyncLocalStorage" in globalThis) return globalThis.AsyncLocalStorage;
48
+ if (typeof window !== "undefined") return null;
49
+ console.warn("[better-auth] Warning: AsyncLocalStorage is not available in this environment. Some features may not work as expected.");
50
+ console.warn("[better-auth] Please read more about this warning at https://better-auth.com/docs/installation#mount-handler");
51
+ console.warn("[better-auth] If you are using Cloudflare Workers, please see: https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag");
52
+ throw err;
53
+ });
54
+ async function getAsyncLocalStorage() {
55
+ const mod = await AsyncLocalStoragePromise;
56
+ if (mod === null) throw new Error("getAsyncLocalStorage is only available in server code");
57
+ else return mod;
58
+ }
59
+
60
+ //#endregion
61
+ //#region node_modules/@better-auth/core/dist/context/endpoint-context.mjs
62
+ const ensureAsyncStorage = async () => {
63
+ const betterAuthGlobal = __getBetterAuthGlobal();
64
+ if (!betterAuthGlobal.context.endpointContextAsyncStorage) {
65
+ const AsyncLocalStorage$1 = await getAsyncLocalStorage();
66
+ betterAuthGlobal.context.endpointContextAsyncStorage = new AsyncLocalStorage$1();
67
+ }
68
+ return betterAuthGlobal.context.endpointContextAsyncStorage;
69
+ };
70
+ async function runWithEndpointContext(context, fn) {
71
+ return (await ensureAsyncStorage()).run(context, fn);
72
+ }
73
+
74
+ //#endregion
75
+ //#region node_modules/@better-auth/core/dist/api/index.mjs
76
+ const optionsMiddleware = createMiddleware(async () => {
77
+ /**
78
+ * This will be passed on the instance of
79
+ * the context. Used to infer the type
80
+ * here.
81
+ */
82
+ return {};
83
+ });
84
+ const createAuthMiddleware = createMiddleware.create({ use: [optionsMiddleware, createMiddleware(async () => {
85
+ return {};
86
+ })] });
87
+ const use = [optionsMiddleware];
88
+ function createAuthEndpoint(pathOrOptions, handlerOrOptions, handlerOrNever) {
89
+ const path = typeof pathOrOptions === "string" ? pathOrOptions : void 0;
90
+ const options = typeof handlerOrOptions === "object" ? handlerOrOptions : pathOrOptions;
91
+ const handler = typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever;
92
+ if (path) return createEndpoint(path, {
93
+ ...options,
94
+ use: [...options?.use || [], ...use]
95
+ }, async (ctx) => runWithEndpointContext(ctx, () => handler(ctx)));
96
+ return createEndpoint({
97
+ ...options,
98
+ use: [...options?.use || [], ...use]
99
+ }, async (ctx) => runWithEndpointContext(ctx, () => handler(ctx)));
100
+ }
101
+
102
+ //#endregion
103
+ //#region src/metadata.ts
104
+ const customerUdf = {
105
+ keys: [
106
+ "customerType",
107
+ "userId",
108
+ "organizationId"
109
+ ],
110
+ set(internalFields, userUdf) {
111
+ return defu({
112
+ udf1: internalFields.customerType,
113
+ udf2: "userId" in internalFields ? internalFields.userId : void 0,
114
+ udf3: "organizationId" in internalFields ? internalFields.organizationId : void 0
115
+ }, userUdf || {});
116
+ },
117
+ get(udf) {
118
+ if (!udf) return {
119
+ userId: void 0,
120
+ organizationId: void 0,
121
+ customerType: void 0
122
+ };
123
+ return {
124
+ userId: udf.udf2,
125
+ organizationId: udf.udf3,
126
+ customerType: udf.udf1
127
+ };
128
+ }
129
+ };
130
+ const subscriptionUdf = {
131
+ keys: [
132
+ "userId",
133
+ "subscriptionId",
134
+ "referenceId"
135
+ ],
136
+ set(internalFields, userUdf) {
137
+ return defu({
138
+ udf2: internalFields.userId,
139
+ udf4: internalFields.subscriptionId,
140
+ udf5: internalFields.referenceId
141
+ }, userUdf || {});
142
+ },
143
+ get(udf) {
144
+ if (!udf) return {
145
+ userId: void 0,
146
+ subscriptionId: void 0,
147
+ referenceId: void 0
148
+ };
149
+ return {
150
+ userId: udf.udf2,
151
+ subscriptionId: udf.udf4,
152
+ referenceId: udf.udf5
153
+ };
154
+ }
155
+ };
156
+ /**
157
+ * Convert a PayUUdf object to individual udf params for API calls.
158
+ */
159
+ function udfToParams(udf) {
160
+ const params = {};
161
+ for (let i = 1; i <= 10; i++) {
162
+ const key = `udf${i}`;
163
+ if (udf[key]) params[key] = udf[key];
164
+ }
165
+ return params;
166
+ }
167
+ /**
168
+ * Extract UDF fields from a PayU response/webhook into a PayUUdf object.
169
+ */
170
+ function paramsToUdf(params) {
171
+ const udf = {};
172
+ for (let i = 1; i <= 10; i++) {
173
+ const key = `udf${i}`;
174
+ if (params[key]) udf[key] = params[key];
175
+ }
176
+ return udf;
177
+ }
178
+
179
+ //#endregion
180
+ //#region src/hooks.ts
181
+ async function findSubscriptionByTxnId(adapter, txnid) {
182
+ return await adapter.findOne({
183
+ model: "subscription",
184
+ where: [{
185
+ field: "payuTransactionId",
186
+ value: txnid
187
+ }]
188
+ });
189
+ }
190
+ async function findSubscriptionByUdf(adapter, event) {
191
+ const udf = paramsToUdf(event);
192
+ const subUdf = subscriptionUdf.get(udf);
193
+ if (subUdf.subscriptionId) return await adapter.findOne({
194
+ model: "subscription",
195
+ where: [{
196
+ field: "id",
197
+ value: subUdf.subscriptionId
198
+ }]
199
+ });
200
+ return null;
201
+ }
202
+ async function findSubscription(adapter, event) {
203
+ const byTxn = await findSubscriptionByTxnId(adapter, event.txnid);
204
+ if (byTxn) return byTxn;
205
+ return findSubscriptionByUdf(adapter, event);
206
+ }
207
+ async function onPaymentSuccess(ctx, options, event) {
208
+ if (!options.subscription?.enabled) return;
209
+ if (!event.txnid) return;
210
+ try {
211
+ const sub = await findSubscription(ctx.context.adapter, event);
212
+ if (sub) {
213
+ await ctx.context.adapter.update({
214
+ model: "subscription",
215
+ where: [{
216
+ field: "id",
217
+ value: sub.id
218
+ }],
219
+ update: {
220
+ status: "active",
221
+ payuMihpayid: event.mihpayid,
222
+ paidCount: (sub.paidCount || 0) + 1,
223
+ remainingCount: sub.totalCount != null ? Math.max((sub.totalCount || 0) - ((sub.paidCount || 0) + 1), 0) : null,
224
+ updatedAt: /* @__PURE__ */ new Date()
225
+ }
226
+ });
227
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
228
+ await options.subscription.onPaymentSuccess?.({
229
+ subscription: sub,
230
+ plan,
231
+ event
232
+ });
233
+ } else ctx.context.logger.warn(`PayU: payment success for txnid ${event.txnid} but subscription not found`);
234
+ } catch (err) {
235
+ ctx.context.logger.error(`PayU onPaymentSuccess error: ${err instanceof Error ? err.message : String(err)}`);
236
+ }
237
+ }
238
+ async function onPaymentFailure(ctx, options, event) {
239
+ if (!options.subscription?.enabled) return;
240
+ try {
241
+ const sub = await findSubscription(ctx.context.adapter, event);
242
+ if (sub) await ctx.context.adapter.update({
243
+ model: "subscription",
244
+ where: [{
245
+ field: "id",
246
+ value: sub.id
247
+ }],
248
+ update: {
249
+ status: "pending",
250
+ updatedAt: /* @__PURE__ */ new Date()
251
+ }
252
+ });
253
+ await options.subscription.onPaymentFailure?.({
254
+ event,
255
+ error: event.error || event.field9 || "Payment failed"
256
+ });
257
+ } catch (err) {
258
+ ctx.context.logger.error(`PayU onPaymentFailure error: ${err instanceof Error ? err.message : String(err)}`);
259
+ }
260
+ }
261
+ async function onSubscriptionActivated(ctx, options, event) {
262
+ if (!options.subscription?.enabled) return;
263
+ if (!event.txnid) return;
264
+ try {
265
+ const sub = await findSubscription(ctx.context.adapter, event);
266
+ if (sub) {
267
+ await ctx.context.adapter.update({
268
+ model: "subscription",
269
+ where: [{
270
+ field: "id",
271
+ value: sub.id
272
+ }],
273
+ update: {
274
+ status: "active",
275
+ payuMihpayid: event.mihpayid,
276
+ currentStart: /* @__PURE__ */ new Date(),
277
+ updatedAt: /* @__PURE__ */ new Date()
278
+ }
279
+ });
280
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
281
+ await options.subscription.onSubscriptionActivated?.({
282
+ subscription: sub,
283
+ plan,
284
+ event
285
+ });
286
+ } else ctx.context.logger.warn(`PayU: subscription activated for txnid ${event.txnid} but subscription not found`);
287
+ } catch (err) {
288
+ ctx.context.logger.error(`PayU onSubscriptionActivated error: ${err instanceof Error ? err.message : String(err)}`);
289
+ }
290
+ }
291
+ async function onSubscriptionPending(ctx, options, event) {
292
+ if (!options.subscription?.enabled) return;
293
+ try {
294
+ const sub = await findSubscription(ctx.context.adapter, event);
295
+ if (sub) {
296
+ await ctx.context.adapter.update({
297
+ model: "subscription",
298
+ where: [{
299
+ field: "id",
300
+ value: sub.id
301
+ }],
302
+ update: {
303
+ status: "pending",
304
+ updatedAt: /* @__PURE__ */ new Date()
305
+ }
306
+ });
307
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
308
+ await options.subscription.onSubscriptionPending?.({
309
+ subscription: sub,
310
+ plan,
311
+ event
312
+ });
313
+ }
314
+ } catch (err) {
315
+ ctx.context.logger.error(`PayU onSubscriptionPending error: ${err instanceof Error ? err.message : String(err)}`);
316
+ }
317
+ }
318
+ async function onSubscriptionHalted(ctx, options, event) {
319
+ if (!options.subscription?.enabled) return;
320
+ try {
321
+ const sub = await findSubscription(ctx.context.adapter, event);
322
+ if (sub) {
323
+ await ctx.context.adapter.update({
324
+ model: "subscription",
325
+ where: [{
326
+ field: "id",
327
+ value: sub.id
328
+ }],
329
+ update: {
330
+ status: "halted",
331
+ updatedAt: /* @__PURE__ */ new Date()
332
+ }
333
+ });
334
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
335
+ await options.subscription.onSubscriptionHalted?.({
336
+ subscription: sub,
337
+ plan,
338
+ event
339
+ });
340
+ }
341
+ } catch (err) {
342
+ ctx.context.logger.error(`PayU onSubscriptionHalted error: ${err instanceof Error ? err.message : String(err)}`);
343
+ }
344
+ }
345
+ async function onSubscriptionCompleted(ctx, options, event) {
346
+ if (!options.subscription?.enabled) return;
347
+ try {
348
+ const sub = await findSubscription(ctx.context.adapter, event);
349
+ if (sub) {
350
+ await ctx.context.adapter.update({
351
+ model: "subscription",
352
+ where: [{
353
+ field: "id",
354
+ value: sub.id
355
+ }],
356
+ update: {
357
+ status: "completed",
358
+ endedAt: /* @__PURE__ */ new Date(),
359
+ remainingCount: 0,
360
+ updatedAt: /* @__PURE__ */ new Date()
361
+ }
362
+ });
363
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
364
+ await options.subscription.onSubscriptionCompleted?.({
365
+ subscription: sub,
366
+ plan,
367
+ event
368
+ });
369
+ }
370
+ } catch (err) {
371
+ ctx.context.logger.error(`PayU onSubscriptionCompleted error: ${err instanceof Error ? err.message : String(err)}`);
372
+ }
373
+ }
374
+ async function onSubscriptionCancelled(ctx, options, event) {
375
+ if (!options.subscription?.enabled) return;
376
+ try {
377
+ const sub = await findSubscription(ctx.context.adapter, event);
378
+ if (sub) {
379
+ await ctx.context.adapter.update({
380
+ model: "subscription",
381
+ where: [{
382
+ field: "id",
383
+ value: sub.id
384
+ }],
385
+ update: {
386
+ status: "cancelled",
387
+ cancelledAt: /* @__PURE__ */ new Date(),
388
+ endedAt: /* @__PURE__ */ new Date(),
389
+ updatedAt: /* @__PURE__ */ new Date()
390
+ }
391
+ });
392
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
393
+ await options.subscription.onSubscriptionCancelled?.({
394
+ subscription: sub,
395
+ plan,
396
+ event
397
+ });
398
+ } else ctx.context.logger.warn(`PayU: subscription cancelled for txnid ${event.txnid} but subscription not found`);
399
+ } catch (err) {
400
+ ctx.context.logger.error(`PayU onSubscriptionCancelled error: ${err instanceof Error ? err.message : String(err)}`);
401
+ }
402
+ }
403
+ async function onSubscriptionPaused(ctx, options, event) {
404
+ if (!options.subscription?.enabled) return;
405
+ try {
406
+ const sub = await findSubscription(ctx.context.adapter, event);
407
+ if (sub) {
408
+ await ctx.context.adapter.update({
409
+ model: "subscription",
410
+ where: [{
411
+ field: "id",
412
+ value: sub.id
413
+ }],
414
+ update: {
415
+ status: "paused",
416
+ pausedAt: /* @__PURE__ */ new Date(),
417
+ updatedAt: /* @__PURE__ */ new Date()
418
+ }
419
+ });
420
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
421
+ await options.subscription.onSubscriptionPaused?.({
422
+ subscription: sub,
423
+ plan,
424
+ event
425
+ });
426
+ }
427
+ } catch (err) {
428
+ ctx.context.logger.error(`PayU onSubscriptionPaused error: ${err instanceof Error ? err.message : String(err)}`);
429
+ }
430
+ }
431
+ async function onSubscriptionResumed(ctx, options, event) {
432
+ if (!options.subscription?.enabled) return;
433
+ try {
434
+ const sub = await findSubscription(ctx.context.adapter, event);
435
+ if (sub) {
436
+ await ctx.context.adapter.update({
437
+ model: "subscription",
438
+ where: [{
439
+ field: "id",
440
+ value: sub.id
441
+ }],
442
+ update: {
443
+ status: "active",
444
+ pausedAt: null,
445
+ updatedAt: /* @__PURE__ */ new Date()
446
+ }
447
+ });
448
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
449
+ await options.subscription.onSubscriptionResumed?.({
450
+ subscription: sub,
451
+ plan,
452
+ event
453
+ });
454
+ }
455
+ } catch (err) {
456
+ ctx.context.logger.error(`PayU onSubscriptionResumed error: ${err instanceof Error ? err.message : String(err)}`);
457
+ }
458
+ }
459
+ async function onMandateRevoked(ctx, options, event) {
460
+ if (!options.subscription?.enabled) return;
461
+ try {
462
+ const sub = await findSubscription(ctx.context.adapter, event);
463
+ if (sub) {
464
+ await ctx.context.adapter.update({
465
+ model: "subscription",
466
+ where: [{
467
+ field: "id",
468
+ value: sub.id
469
+ }],
470
+ update: {
471
+ status: "cancelled",
472
+ cancelledAt: /* @__PURE__ */ new Date(),
473
+ endedAt: /* @__PURE__ */ new Date(),
474
+ updatedAt: /* @__PURE__ */ new Date()
475
+ }
476
+ });
477
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
478
+ await options.subscription.onMandateRevoked?.({
479
+ subscription: sub,
480
+ plan,
481
+ event
482
+ });
483
+ }
484
+ } catch (err) {
485
+ ctx.context.logger.error(`PayU onMandateRevoked error: ${err instanceof Error ? err.message : String(err)}`);
486
+ }
487
+ }
488
+ async function onMandateModified(ctx, options, event) {
489
+ if (!options.subscription?.enabled) return;
490
+ try {
491
+ const sub = await findSubscription(ctx.context.adapter, event);
492
+ if (sub) {
493
+ await ctx.context.adapter.update({
494
+ model: "subscription",
495
+ where: [{
496
+ field: "id",
497
+ value: sub.id
498
+ }],
499
+ update: { updatedAt: /* @__PURE__ */ new Date() }
500
+ });
501
+ const plan = options.subscription.plans ? (typeof options.subscription.plans === "function" ? await options.subscription.plans() : options.subscription.plans).find((p) => p.name === sub.plan) : void 0;
502
+ await options.subscription.onMandateModified?.({
503
+ subscription: sub,
504
+ plan,
505
+ event
506
+ });
507
+ }
508
+ } catch (err) {
509
+ ctx.context.logger.error(`PayU onMandateModified error: ${err instanceof Error ? err.message : String(err)}`);
510
+ }
511
+ }
512
+
513
+ //#endregion
514
+ //#region src/utils.ts
515
+ function createAPIError(status, message) {
516
+ return new APIError(status, { body: {
517
+ message,
518
+ code: message
519
+ } });
520
+ }
521
+ /**
522
+ * Generate PayU hash using HMAC-SHA256 with merchant salt.
523
+ * Hash formula: sha512(key|txnid|amount|productinfo|firstname|email|udf1|...|udf10||salt)
524
+ */
525
+ function generatePayUHash(params, salt) {
526
+ const hashString = [
527
+ params.key,
528
+ params.txnid,
529
+ params.amount,
530
+ params.productinfo,
531
+ params.firstname,
532
+ params.email,
533
+ params.udf1 || "",
534
+ params.udf2 || "",
535
+ params.udf3 || "",
536
+ params.udf4 || "",
537
+ params.udf5 || "",
538
+ params.udf6 || "",
539
+ params.udf7 || "",
540
+ params.udf8 || "",
541
+ params.udf9 || "",
542
+ params.udf10 || "",
543
+ salt
544
+ ].join("|");
545
+ return createHmac("sha512", "").update(hashString).digest("hex");
546
+ }
547
+ /**
548
+ * Verify PayU webhook/response hash.
549
+ * Reverse hash: sha512(salt|status||||||||||udf10|udf9|...|udf1|email|firstname|productinfo|amount|txnid|key)
550
+ */
551
+ function verifyPayUHash(params, salt, receivedHash) {
552
+ const reverseHashString = [
553
+ salt,
554
+ params.status,
555
+ "",
556
+ "",
557
+ "",
558
+ "",
559
+ "",
560
+ "",
561
+ "",
562
+ "",
563
+ "",
564
+ params.udf10 || "",
565
+ params.udf9 || "",
566
+ params.udf8 || "",
567
+ params.udf7 || "",
568
+ params.udf6 || "",
569
+ params.udf5 || "",
570
+ params.udf4 || "",
571
+ params.udf3 || "",
572
+ params.udf2 || "",
573
+ params.udf1 || "",
574
+ params.email,
575
+ params.firstname,
576
+ params.productinfo,
577
+ params.amount,
578
+ params.txnid,
579
+ params.key
580
+ ].join("|");
581
+ return createHmac("sha512", "").update(reverseHashString).digest("hex") === receivedHash;
582
+ }
583
+ async function getPlans(subscriptionOptions) {
584
+ if (subscriptionOptions?.enabled) return typeof subscriptionOptions.plans === "function" ? await subscriptionOptions.plans() : subscriptionOptions.plans || [];
585
+ throw new Error("Subscriptions are not enabled in the PayU options.");
586
+ }
587
+ async function getPlanByName(options, name) {
588
+ return await getPlans(options.subscription).then((res) => res.find((plan) => plan.name.toLowerCase() === name.toLowerCase()));
589
+ }
590
+ async function getPlanByPlanId(options, planId) {
591
+ return await getPlans(options.subscription).then((res) => res.find((plan) => plan.planId === planId || plan.annualPlanId === planId));
592
+ }
593
+ function isActive(sub) {
594
+ return sub.status === "active";
595
+ }
596
+ function isAuthenticated(sub) {
597
+ return sub.status === "authenticated";
598
+ }
599
+ function isPaused(sub) {
600
+ return sub.status === "paused";
601
+ }
602
+ function isCancelled(sub) {
603
+ return sub.status === "cancelled";
604
+ }
605
+ function isTerminal(sub) {
606
+ return sub.status === "cancelled" || sub.status === "completed" || sub.status === "expired";
607
+ }
608
+ function isUsable(sub) {
609
+ return sub.status === "active" || sub.status === "authenticated";
610
+ }
611
+ function hasPaymentIssue(sub) {
612
+ return sub.status === "pending" || sub.status === "halted";
613
+ }
614
+ function timestampToDate(timestamp) {
615
+ return timestamp ? /* @__PURE__ */ new Date(timestamp * 1e3) : void 0;
616
+ }
617
+ function dateStringToDate(dateStr) {
618
+ if (!dateStr) return void 0;
619
+ const d = new Date(dateStr);
620
+ return isNaN(d.getTime()) ? void 0 : d;
621
+ }
622
+ function toSubscriptionStatus(status) {
623
+ if ([
624
+ "created",
625
+ "authenticated",
626
+ "active",
627
+ "pending",
628
+ "halted",
629
+ "cancelled",
630
+ "completed",
631
+ "expired",
632
+ "paused"
633
+ ].includes(status)) return status;
634
+ return "created";
635
+ }
636
+ /**
637
+ * Generate hash for PayU API commands (verify_payment, cancel_refund, etc.)
638
+ * Hash formula: sha512(key|command|var1|salt)
639
+ */
640
+ function generateCommandHash(key, command, var1, salt) {
641
+ const hashString = `${key}|${command}|${var1}|${salt}`;
642
+ return createHmac("sha512", "").update(hashString).digest("hex");
643
+ }
644
+
645
+ //#endregion
646
+ //#region src/middleware.ts
647
+ /**
648
+ * Middleware to extract and validate the authenticated session.
649
+ * Returns the user from the session or throws UNAUTHORIZED.
650
+ */
651
+ function payuSessionMiddleware(ctx) {
652
+ const user = ctx.context.session?.user;
653
+ if (!user) throw createAPIError("UNAUTHORIZED", PAYU_ERROR_CODES.UNAUTHORIZED);
654
+ return user;
655
+ }
656
+ /**
657
+ * Middleware factory to authorize a reference (user or organization) for
658
+ * subscription actions. Supports org-based subscriptions.
659
+ */
660
+ function referenceMiddleware(options) {
661
+ return async (ctx) => {
662
+ const user = payuSessionMiddleware(ctx);
663
+ const referenceId = ctx.body?.referenceId ?? ctx.query?.referenceId ?? user.id;
664
+ if (referenceId === user.id) return {
665
+ referenceId,
666
+ type: "user",
667
+ userId: user.id
668
+ };
669
+ if (options.organization?.enabled) {
670
+ const authorizeReference = options.organization.authorizeReference;
671
+ if (authorizeReference) {
672
+ if (!await authorizeReference({
673
+ action: "list-subscriptions",
674
+ organizationId: referenceId,
675
+ userId: user.id,
676
+ role: void 0
677
+ })) throw createAPIError("FORBIDDEN", PAYU_ERROR_CODES.REFERENCE_ID_NOT_ALLOWED);
678
+ }
679
+ return {
680
+ referenceId,
681
+ type: "organization",
682
+ userId: user.id
683
+ };
684
+ }
685
+ throw createAPIError("FORBIDDEN", PAYU_ERROR_CODES.REFERENCE_ID_NOT_ALLOWED);
686
+ };
687
+ }
688
+
689
+ //#endregion
690
+ //#region src/routes.ts
691
+ const createSubscriptionBodySchema = z.object({
692
+ plan: z.string(),
693
+ referenceId: z.string().optional(),
694
+ customerType: z.enum(["user", "organization"]).optional(),
695
+ mandateType: z.enum([
696
+ "card",
697
+ "upi",
698
+ "netbanking"
699
+ ]).optional()
700
+ });
701
+ const cancelSubscriptionBodySchema = z.object({
702
+ referenceId: z.string().optional(),
703
+ customerType: z.enum(["user", "organization"]).optional(),
704
+ cancelAtCycleEnd: z.boolean().optional()
705
+ });
706
+ const pauseSubscriptionBodySchema = z.object({
707
+ referenceId: z.string().optional(),
708
+ customerType: z.enum(["user", "organization"]).optional()
709
+ });
710
+ const resumeSubscriptionBodySchema = z.object({
711
+ referenceId: z.string().optional(),
712
+ customerType: z.enum(["user", "organization"]).optional()
713
+ });
714
+ const listSubscriptionsQuerySchema = z.object({
715
+ referenceId: z.string().optional(),
716
+ customerType: z.enum(["user", "organization"]).optional()
717
+ });
718
+ const updateSubscriptionBodySchema = z.object({
719
+ referenceId: z.string().optional(),
720
+ customerType: z.enum(["user", "organization"]).optional(),
721
+ plan: z.string().optional(),
722
+ quantity: z.number().optional()
723
+ });
724
+ const chargeSubscriptionBodySchema = z.object({
725
+ subscriptionId: z.string(),
726
+ amount: z.string(),
727
+ txnid: z.string()
728
+ });
729
+ const preDebitNotifyBodySchema = z.object({
730
+ subscriptionId: z.string(),
731
+ amount: z.string(),
732
+ txnid: z.string(),
733
+ debitDate: z.string()
734
+ });
735
+ const fetchSubscriptionQuerySchema = z.object({ subscriptionId: z.string() });
736
+ const mandateStatusQuerySchema = z.object({
737
+ subscriptionId: z.string(),
738
+ mandateType: z.enum([
739
+ "card",
740
+ "upi",
741
+ "netbanking"
742
+ ]).optional()
743
+ });
744
+ const mandateModifyBodySchema = z.object({
745
+ subscriptionId: z.string(),
746
+ amount: z.string(),
747
+ mandateType: z.enum([
748
+ "card",
749
+ "upi",
750
+ "netbanking"
751
+ ]).optional()
752
+ });
753
+ const initiatePaymentBodySchema = z.object({
754
+ txnid: z.string(),
755
+ amount: z.string(),
756
+ productinfo: z.string(),
757
+ firstname: z.string(),
758
+ email: z.string(),
759
+ phone: z.string(),
760
+ referenceId: z.string().optional()
761
+ });
762
+ const verifyPaymentBodySchema = z.object({ txnid: z.string() });
763
+ const initiateRefundBodySchema = z.object({
764
+ mihpayid: z.string(),
765
+ amount: z.string(),
766
+ tokenId: z.string()
767
+ });
768
+ const refundStatusQuerySchema = z.object({
769
+ requestId: z.string().optional(),
770
+ mihpayid: z.string().optional()
771
+ });
772
+ const transactionInfoQuerySchema = z.object({ txnid: z.string() });
773
+ const transactionDetailsQuerySchema = z.object({
774
+ startDate: z.string(),
775
+ endDate: z.string()
776
+ });
777
+ const validateVpaBodySchema = z.object({ vpa: z.string() });
778
+ const updateSIBodySchema = z.object({
779
+ subscriptionId: z.string(),
780
+ billingAmount: z.string().optional(),
781
+ billingCycle: z.string().optional(),
782
+ billingInterval: z.number().optional(),
783
+ paymentEndDate: z.string().optional()
784
+ });
785
+ const fetchPlanQuerySchema = z.object({ planId: z.string() });
786
+ const payAndSubscribeBodySchema = z.object({
787
+ plan: z.string(),
788
+ referenceId: z.string().optional(),
789
+ customerType: z.enum(["user", "organization"]).optional(),
790
+ mandateType: z.enum([
791
+ "card",
792
+ "upi",
793
+ "netbanking"
794
+ ]).optional(),
795
+ initialAmount: z.string().optional()
796
+ });
797
+ function createRoutes(options) {
798
+ const getReference = referenceMiddleware(options);
799
+ return {
800
+ createSubscription: createAuthEndpoint("/payu/subscription/create", {
801
+ method: "POST",
802
+ body: createSubscriptionBodySchema
803
+ }, async (ctx) => {
804
+ const user = payuSessionMiddleware(ctx);
805
+ const ref = await getReference(ctx);
806
+ const body = ctx.body;
807
+ if (!options.subscription?.enabled) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.INVALID_REQUEST_BODY);
808
+ const plan = await getPlanByName(options, body.plan);
809
+ if (!plan) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND);
810
+ const existing = await ctx.context.adapter.findOne({
811
+ model: "subscription",
812
+ where: [{
813
+ field: "referenceId",
814
+ value: ref.referenceId
815
+ }, {
816
+ field: "status",
817
+ operator: "in",
818
+ value: ["active", "authenticated"]
819
+ }]
820
+ });
821
+ if (existing && existing.plan === body.plan) throw createAPIError("CONFLICT", PAYU_ERROR_CODES.ALREADY_SUBSCRIBED_PLAN);
822
+ const subscription = await ctx.context.adapter.create({
823
+ model: "subscription",
824
+ data: {
825
+ plan: plan.name,
826
+ referenceId: ref.referenceId,
827
+ payuCustomerId: null,
828
+ payuSubscriptionId: `payu_sub_${Date.now()}`,
829
+ payuMandateType: body.mandateType || "card",
830
+ payuTransactionId: null,
831
+ payuMihpayid: null,
832
+ status: "created",
833
+ currentStart: null,
834
+ currentEnd: null,
835
+ endedAt: null,
836
+ quantity: 1,
837
+ totalCount: plan.totalCount,
838
+ paidCount: 0,
839
+ remainingCount: plan.totalCount,
840
+ cancelledAt: null,
841
+ pausedAt: null,
842
+ cancelAtCycleEnd: false,
843
+ billingPeriod: plan.billingCycle,
844
+ seats: null,
845
+ trialStart: null,
846
+ trialEnd: null,
847
+ metadata: null,
848
+ createdAt: /* @__PURE__ */ new Date(),
849
+ updatedAt: /* @__PURE__ */ new Date()
850
+ }
851
+ });
852
+ const txnid = `txn_${Date.now()}_${subscription.id}`;
853
+ const hash = generatePayUHash({
854
+ key: options.merchantKey,
855
+ txnid,
856
+ amount: plan.amount,
857
+ productinfo: plan.name,
858
+ firstname: user.id,
859
+ email: "",
860
+ ...subscriptionUdf.set({
861
+ userId: user.id,
862
+ subscriptionId: subscription.id,
863
+ referenceId: ref.referenceId
864
+ })
865
+ }, options.merchantSalt);
866
+ return ctx.json({
867
+ subscription,
868
+ paymentParams: {
869
+ key: options.merchantKey,
870
+ txnid,
871
+ amount: plan.amount,
872
+ productinfo: plan.name,
873
+ hash,
874
+ mandateType: body.mandateType || "card"
875
+ }
876
+ });
877
+ }),
878
+ payAndSubscribe: createAuthEndpoint("/payu/subscription/pay-and-subscribe", {
879
+ method: "POST",
880
+ body: payAndSubscribeBodySchema
881
+ }, async (ctx) => {
882
+ const user = payuSessionMiddleware(ctx);
883
+ const ref = await getReference(ctx);
884
+ const body = ctx.body;
885
+ if (!options.subscription?.enabled) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.INVALID_REQUEST_BODY);
886
+ const plan = await getPlanByName(options, body.plan);
887
+ if (!plan) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND);
888
+ const subscription = await ctx.context.adapter.create({
889
+ model: "subscription",
890
+ data: {
891
+ plan: plan.name,
892
+ referenceId: ref.referenceId,
893
+ payuSubscriptionId: `payu_sub_${Date.now()}`,
894
+ payuMandateType: body.mandateType || "card",
895
+ status: "created",
896
+ quantity: 1,
897
+ totalCount: plan.totalCount,
898
+ paidCount: 0,
899
+ remainingCount: plan.totalCount,
900
+ billingPeriod: plan.billingCycle,
901
+ createdAt: /* @__PURE__ */ new Date(),
902
+ updatedAt: /* @__PURE__ */ new Date()
903
+ }
904
+ });
905
+ const txnid = `txn_${Date.now()}_${subscription.id}`;
906
+ const initialAmount = body.initialAmount || plan.amount;
907
+ const hash = generatePayUHash({
908
+ key: options.merchantKey,
909
+ txnid,
910
+ amount: initialAmount,
911
+ productinfo: plan.name,
912
+ firstname: user.id,
913
+ email: ""
914
+ }, options.merchantSalt);
915
+ return ctx.json({
916
+ subscription,
917
+ paymentParams: {
918
+ key: options.merchantKey,
919
+ txnid,
920
+ amount: initialAmount,
921
+ productinfo: plan.name,
922
+ hash,
923
+ mandateType: body.mandateType || "card"
924
+ }
925
+ });
926
+ }),
927
+ cancelSubscription: createAuthEndpoint("/payu/subscription/cancel", {
928
+ method: "POST",
929
+ body: cancelSubscriptionBodySchema
930
+ }, async (ctx) => {
931
+ const ref = await getReference(ctx);
932
+ const subscription = await ctx.context.adapter.findOne({
933
+ model: "subscription",
934
+ where: [{
935
+ field: "referenceId",
936
+ value: ref.referenceId
937
+ }, {
938
+ field: "status",
939
+ operator: "in",
940
+ value: ["active", "authenticated"]
941
+ }]
942
+ });
943
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
944
+ if (isCancelled(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.SUBSCRIPTION_ALREADY_CANCELLED);
945
+ if (isTerminal(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.SUBSCRIPTION_IN_TERMINAL_STATE);
946
+ if (ctx.body.cancelAtCycleEnd) {
947
+ await ctx.context.adapter.update({
948
+ model: "subscription",
949
+ where: [{
950
+ field: "id",
951
+ value: subscription.id
952
+ }],
953
+ update: {
954
+ cancelAtCycleEnd: true,
955
+ updatedAt: /* @__PURE__ */ new Date()
956
+ }
957
+ });
958
+ return ctx.json({
959
+ success: true,
960
+ cancelAtCycleEnd: true
961
+ });
962
+ }
963
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
964
+ const command = subscription.payuMandateType === "upi" ? "upi_mandate_revoke" : "mandate_revoke";
965
+ const hash = generateCommandHash(options.merchantKey, command, subscription.payuTransactionId || "", options.merchantSalt);
966
+ const result = await (await fetch(`${apiUrl}/merchant/${command}`, {
967
+ method: "POST",
968
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
969
+ body: new URLSearchParams({
970
+ key: options.merchantKey,
971
+ command,
972
+ var1: subscription.payuTransactionId || "",
973
+ hash
974
+ })
975
+ })).json();
976
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.MANDATE_REVOKE_FAILED);
977
+ await ctx.context.adapter.update({
978
+ model: "subscription",
979
+ where: [{
980
+ field: "id",
981
+ value: subscription.id
982
+ }],
983
+ update: {
984
+ status: "cancelled",
985
+ cancelledAt: /* @__PURE__ */ new Date(),
986
+ endedAt: /* @__PURE__ */ new Date(),
987
+ updatedAt: /* @__PURE__ */ new Date()
988
+ }
989
+ });
990
+ return ctx.json({ success: true });
991
+ }),
992
+ pauseSubscription: createAuthEndpoint("/payu/subscription/pause", {
993
+ method: "POST",
994
+ body: pauseSubscriptionBodySchema
995
+ }, async (ctx) => {
996
+ const ref = await getReference(ctx);
997
+ const subscription = await ctx.context.adapter.findOne({
998
+ model: "subscription",
999
+ where: [{
1000
+ field: "referenceId",
1001
+ value: ref.referenceId
1002
+ }, {
1003
+ field: "status",
1004
+ value: "active"
1005
+ }]
1006
+ });
1007
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1008
+ if (!isActive(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE);
1009
+ if (isPaused(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.SUBSCRIPTION_ALREADY_PAUSED);
1010
+ await ctx.context.adapter.update({
1011
+ model: "subscription",
1012
+ where: [{
1013
+ field: "id",
1014
+ value: subscription.id
1015
+ }],
1016
+ update: {
1017
+ status: "paused",
1018
+ pausedAt: /* @__PURE__ */ new Date(),
1019
+ updatedAt: /* @__PURE__ */ new Date()
1020
+ }
1021
+ });
1022
+ return ctx.json({ success: true });
1023
+ }),
1024
+ resumeSubscription: createAuthEndpoint("/payu/subscription/resume", {
1025
+ method: "POST",
1026
+ body: resumeSubscriptionBodySchema
1027
+ }, async (ctx) => {
1028
+ const ref = await getReference(ctx);
1029
+ const subscription = await ctx.context.adapter.findOne({
1030
+ model: "subscription",
1031
+ where: [{
1032
+ field: "referenceId",
1033
+ value: ref.referenceId
1034
+ }, {
1035
+ field: "status",
1036
+ value: "paused"
1037
+ }]
1038
+ });
1039
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1040
+ if (!isPaused(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_PAUSED);
1041
+ await ctx.context.adapter.update({
1042
+ model: "subscription",
1043
+ where: [{
1044
+ field: "id",
1045
+ value: subscription.id
1046
+ }],
1047
+ update: {
1048
+ status: "active",
1049
+ pausedAt: null,
1050
+ updatedAt: /* @__PURE__ */ new Date()
1051
+ }
1052
+ });
1053
+ return ctx.json({ success: true });
1054
+ }),
1055
+ listSubscriptions: createAuthEndpoint("/payu/subscription/list", {
1056
+ method: "GET",
1057
+ query: listSubscriptionsQuerySchema
1058
+ }, async (ctx) => {
1059
+ const ref = await getReference(ctx);
1060
+ const subscriptions = await ctx.context.adapter.findOne({
1061
+ model: "subscription",
1062
+ where: [{
1063
+ field: "referenceId",
1064
+ value: ref.referenceId
1065
+ }]
1066
+ });
1067
+ return ctx.json({ subscriptions: subscriptions ? [subscriptions] : [] });
1068
+ }),
1069
+ getSubscription: createAuthEndpoint("/payu/subscription/get", {
1070
+ method: "GET",
1071
+ query: fetchSubscriptionQuerySchema
1072
+ }, async (ctx) => {
1073
+ payuSessionMiddleware(ctx);
1074
+ const { subscriptionId } = ctx.query;
1075
+ const subscription = await ctx.context.adapter.findOne({
1076
+ model: "subscription",
1077
+ where: [{
1078
+ field: "id",
1079
+ value: subscriptionId
1080
+ }]
1081
+ });
1082
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1083
+ return ctx.json({ subscription });
1084
+ }),
1085
+ updateSubscription: createAuthEndpoint("/payu/subscription/update", {
1086
+ method: "POST",
1087
+ body: updateSubscriptionBodySchema
1088
+ }, async (ctx) => {
1089
+ const ref = await getReference(ctx);
1090
+ const body = ctx.body;
1091
+ const subscription = await ctx.context.adapter.findOne({
1092
+ model: "subscription",
1093
+ where: [{
1094
+ field: "referenceId",
1095
+ value: ref.referenceId
1096
+ }, {
1097
+ field: "status",
1098
+ operator: "in",
1099
+ value: ["active", "authenticated"]
1100
+ }]
1101
+ });
1102
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1103
+ const updateData = { updatedAt: /* @__PURE__ */ new Date() };
1104
+ if (body.plan) {
1105
+ const plan = await getPlanByName(options, body.plan);
1106
+ if (!plan) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND);
1107
+ updateData.plan = plan.name;
1108
+ updateData.billingPeriod = plan.billingCycle;
1109
+ updateData.totalCount = plan.totalCount;
1110
+ }
1111
+ if (body.quantity !== void 0) updateData.quantity = body.quantity;
1112
+ await ctx.context.adapter.update({
1113
+ model: "subscription",
1114
+ where: [{
1115
+ field: "id",
1116
+ value: subscription.id
1117
+ }],
1118
+ update: updateData
1119
+ });
1120
+ return ctx.json({ success: true });
1121
+ }),
1122
+ preDebitNotify: createAuthEndpoint("/payu/subscription/pre-debit-notify", {
1123
+ method: "POST",
1124
+ body: preDebitNotifyBodySchema
1125
+ }, async (ctx) => {
1126
+ payuSessionMiddleware(ctx);
1127
+ const body = ctx.body;
1128
+ if (!await ctx.context.adapter.findOne({
1129
+ model: "subscription",
1130
+ where: [{
1131
+ field: "id",
1132
+ value: body.subscriptionId
1133
+ }]
1134
+ })) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1135
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1136
+ const hash = generateCommandHash(options.merchantKey, "pre_debit_SI", body.txnid, options.merchantSalt);
1137
+ const result = await (await fetch(`${apiUrl}/merchant/pre_debit_SI`, {
1138
+ method: "POST",
1139
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1140
+ body: new URLSearchParams({
1141
+ key: options.merchantKey,
1142
+ command: "pre_debit_SI",
1143
+ var1: body.txnid,
1144
+ var2: body.amount,
1145
+ var3: body.debitDate,
1146
+ hash
1147
+ })
1148
+ })).json();
1149
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.PRE_DEBIT_NOTIFICATION_FAILED);
1150
+ return ctx.json({
1151
+ success: true,
1152
+ result
1153
+ });
1154
+ }),
1155
+ chargeSubscription: createAuthEndpoint("/payu/subscription/charge", {
1156
+ method: "POST",
1157
+ body: chargeSubscriptionBodySchema
1158
+ }, async (ctx) => {
1159
+ payuSessionMiddleware(ctx);
1160
+ const body = ctx.body;
1161
+ const subscription = await ctx.context.adapter.findOne({
1162
+ model: "subscription",
1163
+ where: [{
1164
+ field: "id",
1165
+ value: body.subscriptionId
1166
+ }]
1167
+ });
1168
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1169
+ if (!isActive(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_ACTIVE);
1170
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1171
+ const hash = generateCommandHash(options.merchantKey, "si_transaction", body.txnid, options.merchantSalt);
1172
+ const result = await (await fetch(`${apiUrl}/merchant/si_transaction`, {
1173
+ method: "POST",
1174
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1175
+ body: new URLSearchParams({
1176
+ key: options.merchantKey,
1177
+ command: "si_transaction",
1178
+ var1: body.txnid,
1179
+ var2: body.amount,
1180
+ hash
1181
+ })
1182
+ })).json();
1183
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.PAYMENT_INITIATION_FAILED);
1184
+ return ctx.json({
1185
+ success: true,
1186
+ result
1187
+ });
1188
+ }),
1189
+ updateSI: createAuthEndpoint("/payu/subscription/update-si", {
1190
+ method: "POST",
1191
+ body: updateSIBodySchema
1192
+ }, async (ctx) => {
1193
+ payuSessionMiddleware(ctx);
1194
+ const body = ctx.body;
1195
+ const subscription = await ctx.context.adapter.findOne({
1196
+ model: "subscription",
1197
+ where: [{
1198
+ field: "id",
1199
+ value: body.subscriptionId
1200
+ }]
1201
+ });
1202
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1203
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1204
+ const hash = generateCommandHash(options.merchantKey, "update_si", subscription.payuTransactionId || "", options.merchantSalt);
1205
+ const params = {
1206
+ key: options.merchantKey,
1207
+ command: "update_si",
1208
+ var1: subscription.payuTransactionId || "",
1209
+ hash
1210
+ };
1211
+ if (body.billingAmount) params.var2 = body.billingAmount;
1212
+ if (body.billingCycle) params.var3 = body.billingCycle;
1213
+ if (body.billingInterval !== void 0) params.var4 = String(body.billingInterval);
1214
+ if (body.paymentEndDate) params.var5 = body.paymentEndDate;
1215
+ const result = await (await fetch(`${apiUrl}/merchant/update_si`, {
1216
+ method: "POST",
1217
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1218
+ body: new URLSearchParams(params)
1219
+ })).json();
1220
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.SI_UPDATE_FAILED);
1221
+ return ctx.json({
1222
+ success: true,
1223
+ result
1224
+ });
1225
+ }),
1226
+ mandateStatus: createAuthEndpoint("/payu/mandate/status", {
1227
+ method: "GET",
1228
+ query: mandateStatusQuerySchema
1229
+ }, async (ctx) => {
1230
+ payuSessionMiddleware(ctx);
1231
+ const { subscriptionId, mandateType } = ctx.query;
1232
+ const subscription = await ctx.context.adapter.findOne({
1233
+ model: "subscription",
1234
+ where: [{
1235
+ field: "id",
1236
+ value: subscriptionId
1237
+ }]
1238
+ });
1239
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1240
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1241
+ const mType = mandateType || subscription.payuMandateType || "card";
1242
+ let command;
1243
+ if (mType === "upi") command = "upi_mandate_status";
1244
+ else if (mType === "netbanking") command = "net_banking_mandate_status";
1245
+ else command = "check_mandate_status";
1246
+ const hash = generateCommandHash(options.merchantKey, command, subscription.payuTransactionId || "", options.merchantSalt);
1247
+ const result = await (await fetch(`${apiUrl}/merchant/${command}`, {
1248
+ method: "POST",
1249
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1250
+ body: new URLSearchParams({
1251
+ key: options.merchantKey,
1252
+ command,
1253
+ var1: subscription.payuTransactionId || "",
1254
+ hash
1255
+ })
1256
+ })).json();
1257
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.MANDATE_STATUS_CHECK_FAILED);
1258
+ return ctx.json({ mandate: result });
1259
+ }),
1260
+ mandateModify: createAuthEndpoint("/payu/mandate/modify", {
1261
+ method: "POST",
1262
+ body: mandateModifyBodySchema
1263
+ }, async (ctx) => {
1264
+ payuSessionMiddleware(ctx);
1265
+ const body = ctx.body;
1266
+ const subscription = await ctx.context.adapter.findOne({
1267
+ model: "subscription",
1268
+ where: [{
1269
+ field: "id",
1270
+ value: body.subscriptionId
1271
+ }]
1272
+ });
1273
+ if (!subscription) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_NOT_FOUND);
1274
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1275
+ const mType = body.mandateType || subscription.payuMandateType || "card";
1276
+ let command;
1277
+ if (mType === "upi") command = "upi_mandate_modify";
1278
+ else command = "mandate_modify";
1279
+ const hash = generateCommandHash(options.merchantKey, command, subscription.payuTransactionId || "", options.merchantSalt);
1280
+ const result = await (await fetch(`${apiUrl}/merchant/${command}`, {
1281
+ method: "POST",
1282
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1283
+ body: new URLSearchParams({
1284
+ key: options.merchantKey,
1285
+ command,
1286
+ var1: subscription.payuTransactionId || "",
1287
+ var2: body.amount,
1288
+ hash
1289
+ })
1290
+ })).json();
1291
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.MANDATE_MODIFY_FAILED);
1292
+ return ctx.json({
1293
+ success: true,
1294
+ result
1295
+ });
1296
+ }),
1297
+ initiatePayment: createAuthEndpoint("/payu/payment/initiate", {
1298
+ method: "POST",
1299
+ body: initiatePaymentBodySchema
1300
+ }, async (ctx) => {
1301
+ payuSessionMiddleware(ctx);
1302
+ const body = ctx.body;
1303
+ const hash = generatePayUHash({
1304
+ key: options.merchantKey,
1305
+ txnid: body.txnid,
1306
+ amount: body.amount,
1307
+ productinfo: body.productinfo,
1308
+ firstname: body.firstname,
1309
+ email: body.email
1310
+ }, options.merchantSalt);
1311
+ return ctx.json({ paymentParams: {
1312
+ key: options.merchantKey,
1313
+ txnid: body.txnid,
1314
+ amount: body.amount,
1315
+ productinfo: body.productinfo,
1316
+ firstname: body.firstname,
1317
+ email: body.email,
1318
+ phone: body.phone,
1319
+ hash,
1320
+ surl: `${options.apiBaseUrl || ""}/payu/webhook`,
1321
+ furl: `${options.apiBaseUrl || ""}/payu/webhook`
1322
+ } });
1323
+ }),
1324
+ verifyPayment: createAuthEndpoint("/payu/payment/verify", {
1325
+ method: "POST",
1326
+ body: verifyPaymentBodySchema
1327
+ }, async (ctx) => {
1328
+ payuSessionMiddleware(ctx);
1329
+ const { txnid } = ctx.body;
1330
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1331
+ const hash = generateCommandHash(options.merchantKey, "verify_payment", txnid, options.merchantSalt);
1332
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1333
+ method: "POST",
1334
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1335
+ body: new URLSearchParams({
1336
+ key: options.merchantKey,
1337
+ command: "verify_payment",
1338
+ var1: txnid,
1339
+ hash
1340
+ })
1341
+ })).json();
1342
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.PAYMENT_VERIFICATION_FAILED);
1343
+ return ctx.json({ transaction: result });
1344
+ }),
1345
+ checkPayment: createAuthEndpoint("/payu/payment/check", {
1346
+ method: "POST",
1347
+ body: z.object({ mihpayid: z.string() })
1348
+ }, async (ctx) => {
1349
+ payuSessionMiddleware(ctx);
1350
+ const { mihpayid } = ctx.body;
1351
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1352
+ const hash = generateCommandHash(options.merchantKey, "check_payment", mihpayid, options.merchantSalt);
1353
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1354
+ method: "POST",
1355
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1356
+ body: new URLSearchParams({
1357
+ key: options.merchantKey,
1358
+ command: "check_payment",
1359
+ var1: mihpayid,
1360
+ hash
1361
+ })
1362
+ })).json();
1363
+ return ctx.json({ transaction: result });
1364
+ }),
1365
+ initiateRefund: createAuthEndpoint("/payu/refund/initiate", {
1366
+ method: "POST",
1367
+ body: initiateRefundBodySchema
1368
+ }, async (ctx) => {
1369
+ payuSessionMiddleware(ctx);
1370
+ const body = ctx.body;
1371
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1372
+ const hash = generateCommandHash(options.merchantKey, "cancel_refund_transaction", body.mihpayid, options.merchantSalt);
1373
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1374
+ method: "POST",
1375
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1376
+ body: new URLSearchParams({
1377
+ key: options.merchantKey,
1378
+ command: "cancel_refund_transaction",
1379
+ var1: body.mihpayid,
1380
+ var2: body.tokenId,
1381
+ var3: body.amount,
1382
+ hash
1383
+ })
1384
+ })).json();
1385
+ if (result.status === 0 || result.error) throw createAPIError("INTERNAL_SERVER_ERROR", PAYU_ERROR_CODES.REFUND_INITIATION_FAILED);
1386
+ return ctx.json({
1387
+ success: true,
1388
+ refund: result
1389
+ });
1390
+ }),
1391
+ refundStatus: createAuthEndpoint("/payu/refund/status", {
1392
+ method: "GET",
1393
+ query: refundStatusQuerySchema
1394
+ }, async (ctx) => {
1395
+ payuSessionMiddleware(ctx);
1396
+ const { requestId, mihpayid } = ctx.query;
1397
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1398
+ let command;
1399
+ let var1;
1400
+ if (requestId) {
1401
+ command = "check_action_status";
1402
+ var1 = requestId;
1403
+ } else if (mihpayid) {
1404
+ command = "check_action_status";
1405
+ var1 = mihpayid;
1406
+ } else throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.INVALID_REQUEST_BODY);
1407
+ const hash = generateCommandHash(options.merchantKey, command, var1, options.merchantSalt);
1408
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1409
+ method: "POST",
1410
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1411
+ body: new URLSearchParams({
1412
+ key: options.merchantKey,
1413
+ command,
1414
+ var1,
1415
+ hash
1416
+ })
1417
+ })).json();
1418
+ return ctx.json({ refundStatus: result });
1419
+ }),
1420
+ listRefunds: createAuthEndpoint("/payu/refund/list", {
1421
+ method: "POST",
1422
+ body: z.object({ mihpayids: z.array(z.string()) })
1423
+ }, async (ctx) => {
1424
+ payuSessionMiddleware(ctx);
1425
+ const { mihpayids } = ctx.body;
1426
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1427
+ const var1 = mihpayids.join("|");
1428
+ const hash = generateCommandHash(options.merchantKey, "get_all_refunds_from_transaction_ids", var1, options.merchantSalt);
1429
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1430
+ method: "POST",
1431
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1432
+ body: new URLSearchParams({
1433
+ key: options.merchantKey,
1434
+ command: "get_all_refunds_from_transaction_ids",
1435
+ var1,
1436
+ hash
1437
+ })
1438
+ })).json();
1439
+ return ctx.json({ refunds: result });
1440
+ }),
1441
+ transactionInfo: createAuthEndpoint("/payu/transaction/info", {
1442
+ method: "GET",
1443
+ query: transactionInfoQuerySchema
1444
+ }, async (ctx) => {
1445
+ payuSessionMiddleware(ctx);
1446
+ const { txnid } = ctx.query;
1447
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1448
+ const hash = generateCommandHash(options.merchantKey, "get_transaction_info", txnid, options.merchantSalt);
1449
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1450
+ method: "POST",
1451
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1452
+ body: new URLSearchParams({
1453
+ key: options.merchantKey,
1454
+ command: "get_transaction_info",
1455
+ var1: txnid,
1456
+ hash
1457
+ })
1458
+ })).json();
1459
+ return ctx.json({ transaction: result });
1460
+ }),
1461
+ transactionDetails: createAuthEndpoint("/payu/transaction/details", {
1462
+ method: "GET",
1463
+ query: transactionDetailsQuerySchema
1464
+ }, async (ctx) => {
1465
+ payuSessionMiddleware(ctx);
1466
+ const { startDate, endDate } = ctx.query;
1467
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1468
+ const hash = generateCommandHash(options.merchantKey, "get_Transaction_Details", startDate, options.merchantSalt);
1469
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1470
+ method: "POST",
1471
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1472
+ body: new URLSearchParams({
1473
+ key: options.merchantKey,
1474
+ command: "get_Transaction_Details",
1475
+ var1: startDate,
1476
+ var2: endDate,
1477
+ hash
1478
+ })
1479
+ })).json();
1480
+ return ctx.json({ transactions: result });
1481
+ }),
1482
+ validateVpa: createAuthEndpoint("/payu/upi/validate-vpa", {
1483
+ method: "POST",
1484
+ body: validateVpaBodySchema
1485
+ }, async (ctx) => {
1486
+ payuSessionMiddleware(ctx);
1487
+ const { vpa } = ctx.body;
1488
+ const apiUrl = options.apiBaseUrl || "https://info.payu.in";
1489
+ const hash = generateCommandHash(options.merchantKey, "validateVPA", vpa, options.merchantSalt);
1490
+ const result = await (await fetch(`${apiUrl}/merchant/postservice?form=2`, {
1491
+ method: "POST",
1492
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1493
+ body: new URLSearchParams({
1494
+ key: options.merchantKey,
1495
+ command: "validateVPA",
1496
+ var1: vpa,
1497
+ hash
1498
+ })
1499
+ })).json();
1500
+ if (result.status === 0 || result.isVPAValid === 0) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.INVALID_VPA);
1501
+ return ctx.json({
1502
+ valid: true,
1503
+ result
1504
+ });
1505
+ }),
1506
+ listPlans: createAuthEndpoint("/payu/plan/list", { method: "GET" }, async (ctx) => {
1507
+ payuSessionMiddleware(ctx);
1508
+ if (!options.subscription?.enabled) return ctx.json({ plans: [] });
1509
+ const plans = await getPlans(options.subscription);
1510
+ return ctx.json({ plans });
1511
+ }),
1512
+ getPlan: createAuthEndpoint("/payu/plan/get", {
1513
+ method: "GET",
1514
+ query: fetchPlanQuerySchema
1515
+ }, async (ctx) => {
1516
+ payuSessionMiddleware(ctx);
1517
+ const { planId } = ctx.query;
1518
+ const plan = await getPlanByPlanId(options, planId);
1519
+ if (!plan) throw createAPIError("NOT_FOUND", PAYU_ERROR_CODES.SUBSCRIPTION_PLAN_NOT_FOUND);
1520
+ return ctx.json({ plan });
1521
+ }),
1522
+ webhook: createAuthEndpoint("/payu/webhook", { method: "POST" }, async (ctx) => {
1523
+ const body = ctx.body;
1524
+ if (!body || !body.hash) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.WEBHOOK_HASH_NOT_FOUND);
1525
+ if (!verifyPayUHash({
1526
+ key: body.key || options.merchantKey,
1527
+ txnid: body.txnid || "",
1528
+ amount: body.amount || "",
1529
+ productinfo: body.productinfo || "",
1530
+ firstname: body.firstname || "",
1531
+ email: body.email || "",
1532
+ status: body.status || "",
1533
+ udf1: body.udf1,
1534
+ udf2: body.udf2,
1535
+ udf3: body.udf3,
1536
+ udf4: body.udf4,
1537
+ udf5: body.udf5,
1538
+ udf6: body.udf6,
1539
+ udf7: body.udf7,
1540
+ udf8: body.udf8,
1541
+ udf9: body.udf9,
1542
+ udf10: body.udf10
1543
+ }, options.merchantSalt, body.hash)) throw createAPIError("UNAUTHORIZED", PAYU_ERROR_CODES.FAILED_TO_VERIFY_WEBHOOK);
1544
+ const event = {
1545
+ mihpayid: body.mihpayid || "",
1546
+ status: body.status || "",
1547
+ txnid: body.txnid || "",
1548
+ amount: body.amount || "",
1549
+ productinfo: body.productinfo || "",
1550
+ firstname: body.firstname || "",
1551
+ email: body.email || "",
1552
+ phone: body.phone || "",
1553
+ hash: body.hash,
1554
+ key: body.key || "",
1555
+ mode: body.mode || "",
1556
+ unmappedstatus: body.unmappedstatus || "",
1557
+ field9: body.field9 || "",
1558
+ error: body.error_Message || body.error || "",
1559
+ bank_ref_num: body.bank_ref_num || "",
1560
+ addedon: body.addedon || "",
1561
+ payment_source: body.payment_source || "",
1562
+ udf1: body.udf1,
1563
+ udf2: body.udf2,
1564
+ udf3: body.udf3,
1565
+ udf4: body.udf4,
1566
+ udf5: body.udf5,
1567
+ udf6: body.udf6,
1568
+ udf7: body.udf7,
1569
+ udf8: body.udf8,
1570
+ udf9: body.udf9,
1571
+ udf10: body.udf10,
1572
+ notificationType: body.notificationType
1573
+ };
1574
+ const status = body.status?.toLowerCase();
1575
+ const notifType = body.notificationType?.toLowerCase();
1576
+ if (notifType === "mandate_revoked" || notifType === "si_cancelled") await onMandateRevoked(ctx, options, event);
1577
+ else if (notifType === "mandate_modified" || notifType === "si_modified") await onMandateModified(ctx, options, event);
1578
+ else if (status === "success" || status === "captured") {
1579
+ await onPaymentSuccess(ctx, options, event);
1580
+ await onSubscriptionActivated(ctx, options, event);
1581
+ } else if (status === "failure" || status === "failed") await onPaymentFailure(ctx, options, event);
1582
+ else if (status === "pending") await onSubscriptionPending(ctx, options, event);
1583
+ else if (status === "cancelled") await onSubscriptionCancelled(ctx, options, event);
1584
+ else if (status === "halted") await onSubscriptionHalted(ctx, options, event);
1585
+ else if (status === "completed") await onSubscriptionCompleted(ctx, options, event);
1586
+ else if (status === "paused") await onSubscriptionPaused(ctx, options, event);
1587
+ else if (status === "resumed" || status === "active") await onSubscriptionResumed(ctx, options, event);
1588
+ return ctx.json({ success: true });
1589
+ })
1590
+ };
1591
+ }
1592
+
1593
+ //#endregion
1594
+ //#region src/schema.ts
1595
+ const subscriptions = { subscription: { fields: {
1596
+ plan: {
1597
+ type: "string",
1598
+ required: true
1599
+ },
1600
+ referenceId: {
1601
+ type: "string",
1602
+ required: true
1603
+ },
1604
+ payuCustomerId: {
1605
+ type: "string",
1606
+ required: false
1607
+ },
1608
+ payuSubscriptionId: {
1609
+ type: "string",
1610
+ required: false
1611
+ },
1612
+ payuMandateType: {
1613
+ type: "string",
1614
+ required: false
1615
+ },
1616
+ payuTransactionId: {
1617
+ type: "string",
1618
+ required: false
1619
+ },
1620
+ payuMihpayid: {
1621
+ type: "string",
1622
+ required: false
1623
+ },
1624
+ status: {
1625
+ type: "string",
1626
+ defaultValue: "created"
1627
+ },
1628
+ currentStart: {
1629
+ type: "date",
1630
+ required: false
1631
+ },
1632
+ currentEnd: {
1633
+ type: "date",
1634
+ required: false
1635
+ },
1636
+ endedAt: {
1637
+ type: "date",
1638
+ required: false
1639
+ },
1640
+ quantity: {
1641
+ type: "number",
1642
+ required: false,
1643
+ defaultValue: 1
1644
+ },
1645
+ totalCount: {
1646
+ type: "number",
1647
+ required: false
1648
+ },
1649
+ paidCount: {
1650
+ type: "number",
1651
+ required: false,
1652
+ defaultValue: 0
1653
+ },
1654
+ remainingCount: {
1655
+ type: "number",
1656
+ required: false
1657
+ },
1658
+ cancelledAt: {
1659
+ type: "date",
1660
+ required: false
1661
+ },
1662
+ pausedAt: {
1663
+ type: "date",
1664
+ required: false
1665
+ },
1666
+ cancelAtCycleEnd: {
1667
+ type: "boolean",
1668
+ required: false,
1669
+ defaultValue: false
1670
+ },
1671
+ billingPeriod: {
1672
+ type: "string",
1673
+ required: false
1674
+ },
1675
+ seats: {
1676
+ type: "number",
1677
+ required: false
1678
+ },
1679
+ trialStart: {
1680
+ type: "date",
1681
+ required: false
1682
+ },
1683
+ trialEnd: {
1684
+ type: "date",
1685
+ required: false
1686
+ },
1687
+ metadata: {
1688
+ type: "string",
1689
+ required: false
1690
+ }
1691
+ } } };
1692
+ const user = { user: { fields: { payuCustomerId: {
1693
+ type: "string",
1694
+ required: false
1695
+ } } } };
1696
+ const organization = { organization: { fields: { payuCustomerId: {
1697
+ type: "string",
1698
+ required: false
1699
+ } } } };
1700
+ const getSchema = (options) => {
1701
+ let baseSchema = {};
1702
+ if (options.subscription?.enabled) baseSchema = {
1703
+ ...subscriptions,
1704
+ ...user
1705
+ };
1706
+ else baseSchema = { ...user };
1707
+ if (options.organization?.enabled) baseSchema = {
1708
+ ...baseSchema,
1709
+ ...organization
1710
+ };
1711
+ if (options.schema && !options.subscription?.enabled && "subscription" in options.schema) {
1712
+ const { subscription: _subscription, ...restSchema } = options.schema;
1713
+ return mergeSchema(baseSchema, restSchema);
1714
+ }
1715
+ return mergeSchema(baseSchema, options.schema);
1716
+ };
1717
+
1718
+ //#endregion
1719
+ //#region src/index.ts
1720
+ const payu = (options) => {
1721
+ const routes = createRoutes(options);
1722
+ const subscriptionEndpoints = {
1723
+ payuCreateSubscription: routes.createSubscription,
1724
+ payuPayAndSubscribe: routes.payAndSubscribe,
1725
+ payuCancelSubscription: routes.cancelSubscription,
1726
+ payuPauseSubscription: routes.pauseSubscription,
1727
+ payuResumeSubscription: routes.resumeSubscription,
1728
+ payuListSubscriptions: routes.listSubscriptions,
1729
+ payuGetSubscription: routes.getSubscription,
1730
+ payuUpdateSubscription: routes.updateSubscription,
1731
+ payuPreDebitNotify: routes.preDebitNotify,
1732
+ payuChargeSubscription: routes.chargeSubscription,
1733
+ payuUpdateSI: routes.updateSI
1734
+ };
1735
+ const mandateEndpoints = {
1736
+ payuMandateStatus: routes.mandateStatus,
1737
+ payuMandateModify: routes.mandateModify
1738
+ };
1739
+ const paymentEndpoints = {
1740
+ payuInitiatePayment: routes.initiatePayment,
1741
+ payuVerifyPayment: routes.verifyPayment,
1742
+ payuCheckPayment: routes.checkPayment
1743
+ };
1744
+ const refundEndpoints = {
1745
+ payuInitiateRefund: routes.initiateRefund,
1746
+ payuRefundStatus: routes.refundStatus,
1747
+ payuListRefunds: routes.listRefunds
1748
+ };
1749
+ const transactionEndpoints = {
1750
+ payuTransactionInfo: routes.transactionInfo,
1751
+ payuTransactionDetails: routes.transactionDetails
1752
+ };
1753
+ const utilityEndpoints = {
1754
+ payuValidateVpa: routes.validateVpa,
1755
+ payuListPlans: routes.listPlans,
1756
+ payuGetPlan: routes.getPlan
1757
+ };
1758
+ return {
1759
+ id: "payu",
1760
+ endpoints: {
1761
+ payuWebhook: routes.webhook,
1762
+ ...paymentEndpoints,
1763
+ ...refundEndpoints,
1764
+ ...transactionEndpoints,
1765
+ ...utilityEndpoints,
1766
+ ...mandateEndpoints,
1767
+ ...options.subscription?.enabled ? subscriptionEndpoints : {}
1768
+ },
1769
+ init(ctx) {
1770
+ if (options.organization?.enabled) {
1771
+ const orgPlugin = ctx.getPlugin("organization");
1772
+ if (!orgPlugin) {
1773
+ ctx.logger.error(`Organization plugin not found`);
1774
+ return;
1775
+ }
1776
+ const existingHooks = orgPlugin.options?.organizationHooks ?? {};
1777
+ /**
1778
+ * Block organization deletion when there's an active subscription
1779
+ */
1780
+ const beforeDeletePayUOrg = async (data) => {
1781
+ const subscription = await ctx.adapter.findOne({
1782
+ model: "subscription",
1783
+ where: [{
1784
+ field: "referenceId",
1785
+ value: data.organization.id
1786
+ }]
1787
+ });
1788
+ if (subscription && isActive(subscription)) throw createAPIError("BAD_REQUEST", PAYU_ERROR_CODES.ORGANIZATION_ON_ACTIVE_SUBSCRIPTION);
1789
+ };
1790
+ orgPlugin.options = {
1791
+ ...orgPlugin.options,
1792
+ organizationHooks: {
1793
+ ...existingHooks,
1794
+ beforeDeleteOrganization: async (data) => {
1795
+ if (existingHooks.beforeDeleteOrganization) await existingHooks.beforeDeleteOrganization(data);
1796
+ await beforeDeletePayUOrg(data);
1797
+ }
1798
+ }
1799
+ };
1800
+ }
1801
+ },
1802
+ schema: getSchema(options),
1803
+ hooks: { after: [{
1804
+ matcher(context) {
1805
+ return context.path === "/user/update";
1806
+ },
1807
+ async handler(ctx) {}
1808
+ }] }
1809
+ };
1810
+ };
1811
+
1812
+ //#endregion
1813
+ export { PAYU_ERROR_CODES, customerUdf, dateStringToDate, generateCommandHash, generatePayUHash, hasPaymentIssue, isActive, isAuthenticated, isCancelled, isPaused, isTerminal, isUsable, paramsToUdf, payu, subscriptionUdf, timestampToDate, toSubscriptionStatus, udfToParams, verifyPayUHash };
1814
+ //# sourceMappingURL=index.js.map