@boostengine/payments 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -103
- package/dist/index.cjs +182 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +20 -148
- package/dist/index.d.ts +20 -148
- package/dist/index.mjs +182 -24
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +173 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.mts +29 -0
- package/dist/react.d.ts +29 -0
- package/dist/react.mjs +167 -0
- package/dist/react.mjs.map +1 -0
- package/dist/types-D3bPaYhQ.d.mts +194 -0
- package/dist/types-D3bPaYhQ.d.ts +194 -0
- package/package.json +29 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,150 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
id?: string;
|
|
4
|
-
name: string;
|
|
5
|
-
email: string;
|
|
6
|
-
phone: string;
|
|
7
|
-
}
|
|
8
|
-
interface UnifiedOrderItem {
|
|
9
|
-
name: string;
|
|
10
|
-
quantity: number;
|
|
11
|
-
price: number;
|
|
12
|
-
sku?: string;
|
|
13
|
-
}
|
|
14
|
-
interface UnifiedCreateOrderOptions {
|
|
15
|
-
/** Amount in standard currency units (e.g. 1499.00 for INR, 25.00 for USD) */
|
|
16
|
-
amount: number;
|
|
17
|
-
/** 3-letter ISO currency code (e.g. 'INR', 'USD', 'EUR') */
|
|
18
|
-
currency: string;
|
|
19
|
-
/** Merchant order or receipt reference ID */
|
|
20
|
-
receipt: string;
|
|
21
|
-
/** Customer details */
|
|
22
|
-
customer: UnifiedCustomer;
|
|
23
|
-
/** Line items */
|
|
24
|
-
items?: UnifiedOrderItem[];
|
|
25
|
-
/** Merchant notes/metadata */
|
|
26
|
-
notes?: Record<string, any>;
|
|
27
|
-
/** Customer return/redirect URL after payment */
|
|
28
|
-
redirectUrl?: string;
|
|
29
|
-
/** Server-to-server webhook callback URL */
|
|
30
|
-
callbackUrl?: string;
|
|
31
|
-
/** Explicitly override gateway for this transaction */
|
|
32
|
-
gateway?: GatewayName;
|
|
33
|
-
}
|
|
34
|
-
interface UnifiedOrderResult {
|
|
35
|
-
gateway: GatewayName;
|
|
36
|
-
orderId: string;
|
|
37
|
-
gatewayOrderId: string;
|
|
38
|
-
amount: number;
|
|
39
|
-
currency: string;
|
|
40
|
-
status: 'CREATED' | 'PENDING' | 'PAID' | 'FAILED';
|
|
41
|
-
/** Cashfree payment_session_id for Drop-in UI or SDK */
|
|
42
|
-
paymentSessionId?: string;
|
|
43
|
-
/** Redirect or hosted pay URL (PhonePe, Stripe, Cashfree) */
|
|
44
|
-
redirectUrl?: string;
|
|
45
|
-
/** Paytm checkout txnToken */
|
|
46
|
-
txnToken?: string;
|
|
47
|
-
/** Raw response object from gateway */
|
|
48
|
-
rawResponse: any;
|
|
49
|
-
}
|
|
50
|
-
interface UnifiedPaymentVerificationOptions {
|
|
51
|
-
gateway: GatewayName;
|
|
52
|
-
orderId: string;
|
|
53
|
-
paymentId?: string;
|
|
54
|
-
signature?: string;
|
|
55
|
-
rawPayload?: any;
|
|
56
|
-
}
|
|
57
|
-
interface UnifiedPaymentVerificationResult {
|
|
58
|
-
gateway: GatewayName;
|
|
59
|
-
isSuccessful: boolean;
|
|
60
|
-
paymentId: string;
|
|
61
|
-
orderId: string;
|
|
62
|
-
amount: number;
|
|
63
|
-
currency: string;
|
|
64
|
-
paymentMethod?: string;
|
|
65
|
-
rawResponse: any;
|
|
66
|
-
}
|
|
67
|
-
interface UnifiedRefundOptions {
|
|
68
|
-
gateway: GatewayName;
|
|
69
|
-
paymentId: string;
|
|
70
|
-
orderId?: string;
|
|
71
|
-
/** Partial or full refund amount in currency units */
|
|
72
|
-
amount?: number;
|
|
73
|
-
reason?: string;
|
|
74
|
-
}
|
|
75
|
-
interface UnifiedRefundResult {
|
|
76
|
-
gateway: GatewayName;
|
|
77
|
-
refundId: string;
|
|
78
|
-
paymentId: string;
|
|
79
|
-
amount: number;
|
|
80
|
-
status: 'SUCCESS' | 'PENDING' | 'FAILED';
|
|
81
|
-
rawResponse: any;
|
|
82
|
-
}
|
|
83
|
-
interface WebhookVerificationOptions {
|
|
84
|
-
gateway: GatewayName;
|
|
85
|
-
rawBody: string | Buffer;
|
|
86
|
-
headers: Record<string, string | string[] | undefined>;
|
|
87
|
-
webhookSecret?: string;
|
|
88
|
-
}
|
|
89
|
-
interface WebhookVerificationResult {
|
|
90
|
-
isValid: boolean;
|
|
91
|
-
event?: string;
|
|
92
|
-
gateway: GatewayName;
|
|
93
|
-
data?: any;
|
|
94
|
-
error?: string;
|
|
95
|
-
}
|
|
96
|
-
interface RazorpayConfig {
|
|
97
|
-
keyId: string;
|
|
98
|
-
keySecret: string;
|
|
99
|
-
webhookSecret?: string;
|
|
100
|
-
}
|
|
101
|
-
interface CashfreeConfig {
|
|
102
|
-
appId: string;
|
|
103
|
-
secretKey: string;
|
|
104
|
-
env?: 'SANDBOX' | 'PRODUCTION';
|
|
105
|
-
apiVersion?: string;
|
|
106
|
-
}
|
|
107
|
-
interface PhonePeConfig {
|
|
108
|
-
merchantId: string;
|
|
109
|
-
saltKey: string;
|
|
110
|
-
saltIndex?: string;
|
|
111
|
-
env?: 'UAT' | 'PRODUCTION';
|
|
112
|
-
}
|
|
113
|
-
interface PaytmConfig {
|
|
114
|
-
mid: string;
|
|
115
|
-
merchantKey: string;
|
|
116
|
-
website?: string;
|
|
117
|
-
env?: 'STAGE' | 'PRODUCTION';
|
|
118
|
-
}
|
|
119
|
-
interface StripeConfig {
|
|
120
|
-
secretKey: string;
|
|
121
|
-
webhookSecret?: string;
|
|
122
|
-
}
|
|
123
|
-
interface CODConfig {
|
|
124
|
-
minOrderValue?: number;
|
|
125
|
-
maxOrderValue?: number;
|
|
126
|
-
extraFee?: number;
|
|
127
|
-
allowedPincodes?: string[];
|
|
128
|
-
}
|
|
129
|
-
interface GatewayConfigs {
|
|
130
|
-
razorpay?: RazorpayConfig;
|
|
131
|
-
cashfree?: CashfreeConfig;
|
|
132
|
-
phonepe?: PhonePeConfig;
|
|
133
|
-
paytm?: PaytmConfig;
|
|
134
|
-
stripe?: StripeConfig;
|
|
135
|
-
cod?: CODConfig;
|
|
136
|
-
}
|
|
137
|
-
interface SmartRoutingConfig {
|
|
138
|
-
/** Map currency to default gateway, e.g. { 'USD': 'stripe', 'INR': 'cashfree' } */
|
|
139
|
-
currencyMap?: Record<string, GatewayName>;
|
|
140
|
-
/** Automatic fallback order if primary gateway fails, e.g. ['razorpay', 'cashfree', 'phonepe'] */
|
|
141
|
-
fallbackChain?: GatewayName[];
|
|
142
|
-
}
|
|
143
|
-
interface PaymentManagerOptions {
|
|
144
|
-
defaultGateway?: GatewayName;
|
|
145
|
-
gateways: GatewayConfigs;
|
|
146
|
-
smartRouting?: SmartRoutingConfig;
|
|
147
|
-
}
|
|
1
|
+
import { G as GatewayName, U as UnifiedCreateOrderOptions, a as UnifiedOrderResult, b as UnifiedPaymentVerificationOptions, c as UnifiedPaymentVerificationResult, d as UnifiedRefundOptions, e as UnifiedRefundResult, W as WebhookVerificationOptions, f as WebhookVerificationResult, P as PaymentManagerOptions, R as RazorpayConfig, C as CashfreeConfig, g as PhonePeConfig, h as PaytmConfig, S as StripeConfig, i as CODConfig } from './types-D3bPaYhQ.js';
|
|
2
|
+
export { B as BoostPaymentOpenOptions, j as CreateOrderOptions, k as GatewayConfigs, N as NormalizedWebhookEvent, l as PaymentOrderResult, m as RefundResult, n as SmartRoutingConfig, o as SupportedGateway, p as UnifiedCustomer, q as UnifiedOrderItem, V as VerificationResult, r as WebhookResult } from './types-D3bPaYhQ.js';
|
|
148
3
|
|
|
149
4
|
declare abstract class BasePaymentAdapter {
|
|
150
5
|
abstract readonly name: GatewayName;
|
|
@@ -221,6 +76,23 @@ declare class PaymentManager {
|
|
|
221
76
|
* Verifies incoming webhook authenticity and decodes payload.
|
|
222
77
|
*/
|
|
223
78
|
verifyWebhook(options: WebhookVerificationOptions): Promise<WebhookVerificationResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Ready-made Next.js 13/14/15 App Router Route Handler Webhook Authenticator.
|
|
81
|
+
* Directly consumes the standard web Request object with raw stream body handling:
|
|
82
|
+
*
|
|
83
|
+
* ```typescript
|
|
84
|
+
* export async function POST(req: Request) {
|
|
85
|
+
* const result = await payments.verifyNextJsWebhook(req, { gateway: 'razorpay' });
|
|
86
|
+
* if (!result.isValid) return new Response('Invalid Signature', { status: 400 });
|
|
87
|
+
* console.log('Event:', result.normalizedEvent, result.orderId);
|
|
88
|
+
* return new Response('OK');
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
verifyNextJsWebhook(request: Request | any, options: {
|
|
93
|
+
gateway: GatewayName;
|
|
94
|
+
webhookSecret?: string;
|
|
95
|
+
}): Promise<WebhookVerificationResult>;
|
|
224
96
|
}
|
|
225
97
|
/**
|
|
226
98
|
* Factory function to instantiate a PaymentManager.
|
|
@@ -336,4 +208,4 @@ declare class CODAdapter extends BasePaymentAdapter {
|
|
|
336
208
|
verifyWebhook(_options: WebhookVerificationOptions): Promise<WebhookVerificationResult>;
|
|
337
209
|
}
|
|
338
210
|
|
|
339
|
-
export { BasePaymentAdapter, CODAdapter,
|
|
211
|
+
export { BasePaymentAdapter, CODAdapter, CODConfig, CashfreeAdapter, CashfreeConfig, GatewayName, GatewayNotConfiguredError, PaymentError, PaymentManager, PaymentManagerOptions, PaytmAdapter, PaytmConfig, PhonePeAdapter, PhonePeConfig, RazorpayAdapter, RazorpayConfig, SignatureVerificationError, StripeAdapter, StripeConfig, UnifiedCreateOrderOptions, UnifiedOrderResult, UnifiedPaymentVerificationOptions, UnifiedPaymentVerificationResult, UnifiedRefundOptions, UnifiedRefundResult, WebhookVerificationOptions, WebhookVerificationResult, base64Decode, base64Encode, createPaymentManager, hmacSha256, safeCompare, sha256 };
|
package/dist/index.mjs
CHANGED
|
@@ -172,6 +172,7 @@ var RazorpayAdapter = class extends BasePaymentAdapter {
|
|
|
172
172
|
paymentId: payment.id,
|
|
173
173
|
orderId: options.orderId,
|
|
174
174
|
amount: payment.amount / 100,
|
|
175
|
+
// Normalized back to standard currency units
|
|
175
176
|
currency: payment.currency,
|
|
176
177
|
paymentMethod: payment.method,
|
|
177
178
|
rawResponse: payment
|
|
@@ -196,6 +197,7 @@ var RazorpayAdapter = class extends BasePaymentAdapter {
|
|
|
196
197
|
refundId: res.id,
|
|
197
198
|
paymentId: options.paymentId,
|
|
198
199
|
amount: res.amount / 100,
|
|
200
|
+
// Normalized to standard currency units
|
|
199
201
|
status: res.status === "processed" ? "SUCCESS" : "PENDING",
|
|
200
202
|
rawResponse: res
|
|
201
203
|
};
|
|
@@ -205,32 +207,58 @@ var RazorpayAdapter = class extends BasePaymentAdapter {
|
|
|
205
207
|
if (!secret) {
|
|
206
208
|
return {
|
|
207
209
|
isValid: false,
|
|
210
|
+
normalizedEvent: "UNKNOWN",
|
|
208
211
|
gateway: "razorpay",
|
|
209
212
|
error: "Razorpay webhookSecret is not configured."
|
|
210
213
|
};
|
|
211
214
|
}
|
|
212
215
|
const signature = this.getHeader(options.headers, "x-razorpay-signature");
|
|
213
|
-
if (!signature
|
|
216
|
+
if (!signature) {
|
|
214
217
|
return {
|
|
215
218
|
isValid: false,
|
|
219
|
+
normalizedEvent: "UNKNOWN",
|
|
216
220
|
gateway: "razorpay",
|
|
217
|
-
error: "Missing
|
|
221
|
+
error: "Missing x-razorpay-signature header."
|
|
218
222
|
};
|
|
219
223
|
}
|
|
220
224
|
const rawString = typeof options.rawBody === "string" ? options.rawBody : options.rawBody.toString("utf8");
|
|
221
225
|
const expectedSignature = hmacSha256(rawString, secret);
|
|
222
226
|
const isValid = safeCompare(expectedSignature, signature);
|
|
223
|
-
let
|
|
227
|
+
let parsed;
|
|
224
228
|
try {
|
|
225
|
-
|
|
229
|
+
parsed = JSON.parse(rawString);
|
|
226
230
|
} catch {
|
|
227
|
-
|
|
231
|
+
parsed = null;
|
|
228
232
|
}
|
|
233
|
+
const rawEvent = parsed?.event || "";
|
|
234
|
+
let normalizedEvent = "UNKNOWN";
|
|
235
|
+
if (rawEvent === "order.paid" || rawEvent === "payment.captured" || rawEvent === "payment.authorized") {
|
|
236
|
+
normalizedEvent = "PAYMENT_SUCCESS";
|
|
237
|
+
} else if (rawEvent === "payment.failed") {
|
|
238
|
+
normalizedEvent = "PAYMENT_FAILED";
|
|
239
|
+
} else if (rawEvent === "refund.processed" || rawEvent === "refund.created") {
|
|
240
|
+
normalizedEvent = "REFUND_PROCESSED";
|
|
241
|
+
} else if (rawEvent === "refund.failed") {
|
|
242
|
+
normalizedEvent = "REFUND_FAILED";
|
|
243
|
+
} else if (rawEvent?.includes("dispute")) {
|
|
244
|
+
normalizedEvent = "DISPUTE_CREATED";
|
|
245
|
+
}
|
|
246
|
+
const paymentEntity = parsed?.payload?.payment?.entity;
|
|
247
|
+
const orderEntity = parsed?.payload?.order?.entity;
|
|
248
|
+
const orderId = orderEntity?.receipt || paymentEntity?.order_id || orderEntity?.id;
|
|
249
|
+
const paymentId = paymentEntity?.id;
|
|
250
|
+
const amount = paymentEntity?.amount ? paymentEntity.amount / 100 : orderEntity?.amount ? orderEntity.amount / 100 : void 0;
|
|
251
|
+
const currency = paymentEntity?.currency || orderEntity?.currency;
|
|
229
252
|
return {
|
|
230
253
|
isValid,
|
|
254
|
+
normalizedEvent,
|
|
255
|
+
rawEvent,
|
|
231
256
|
gateway: "razorpay",
|
|
232
|
-
|
|
233
|
-
|
|
257
|
+
orderId,
|
|
258
|
+
paymentId,
|
|
259
|
+
amount,
|
|
260
|
+
currency,
|
|
261
|
+
data: parsed?.payload
|
|
234
262
|
};
|
|
235
263
|
}
|
|
236
264
|
};
|
|
@@ -363,17 +391,41 @@ var CashfreeAdapter = class extends BasePaymentAdapter {
|
|
|
363
391
|
).toString("base64");
|
|
364
392
|
isValid = safeCompare(expectedSignature, signature);
|
|
365
393
|
}
|
|
366
|
-
let
|
|
394
|
+
let parsed;
|
|
367
395
|
try {
|
|
368
|
-
|
|
396
|
+
parsed = JSON.parse(rawString);
|
|
369
397
|
} catch {
|
|
370
|
-
|
|
398
|
+
parsed = null;
|
|
399
|
+
}
|
|
400
|
+
const rawEvent = parsed?.type || "";
|
|
401
|
+
let normalizedEvent = "UNKNOWN";
|
|
402
|
+
if (rawEvent === "PAYMENT_SUCCESS_WEBHOOK") {
|
|
403
|
+
normalizedEvent = "PAYMENT_SUCCESS";
|
|
404
|
+
} else if (rawEvent === "PAYMENT_FAILED_WEBHOOK" || rawEvent === "USER_DROPPED_WEBHOOK") {
|
|
405
|
+
normalizedEvent = "PAYMENT_FAILED";
|
|
406
|
+
} else if (rawEvent === "REFUND_STATUS_WEBHOOK") {
|
|
407
|
+
const refundStatus = parsed?.data?.refund?.refund_status;
|
|
408
|
+
normalizedEvent = refundStatus === "SUCCESS" ? "REFUND_PROCESSED" : "REFUND_FAILED";
|
|
409
|
+
} else if (rawEvent?.includes("DISPUTE")) {
|
|
410
|
+
normalizedEvent = "DISPUTE_CREATED";
|
|
371
411
|
}
|
|
412
|
+
const orderData = parsed?.data?.order;
|
|
413
|
+
const paymentData = parsed?.data?.payment;
|
|
414
|
+
const refundData = parsed?.data?.refund;
|
|
415
|
+
const orderId = orderData?.order_id || refundData?.order_id;
|
|
416
|
+
const paymentId = paymentData ? String(paymentData.cf_payment_id) : refundData ? String(refundData.cf_payment_id) : void 0;
|
|
417
|
+
const amount = paymentData?.payment_amount ?? orderData?.order_amount ?? refundData?.refund_amount;
|
|
418
|
+
const currency = paymentData?.payment_currency || orderData?.order_currency;
|
|
372
419
|
return {
|
|
373
420
|
isValid,
|
|
421
|
+
normalizedEvent,
|
|
422
|
+
rawEvent,
|
|
374
423
|
gateway: "cashfree",
|
|
375
|
-
|
|
376
|
-
|
|
424
|
+
orderId,
|
|
425
|
+
paymentId,
|
|
426
|
+
amount,
|
|
427
|
+
currency,
|
|
428
|
+
data: parsed?.data
|
|
377
429
|
};
|
|
378
430
|
}
|
|
379
431
|
};
|
|
@@ -499,17 +551,36 @@ var PhonePeAdapter = class extends BasePaymentAdapter {
|
|
|
499
551
|
try {
|
|
500
552
|
parsed = JSON.parse(rawString);
|
|
501
553
|
} catch {
|
|
502
|
-
return { isValid: false, gateway: "phonepe", error: "Invalid JSON payload" };
|
|
554
|
+
return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "phonepe", error: "Invalid JSON payload" };
|
|
503
555
|
}
|
|
504
556
|
if (!parsed.response) {
|
|
505
|
-
return { isValid: false, gateway: "phonepe", error: "Missing response field in PhonePe callback" };
|
|
557
|
+
return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "phonepe", error: "Missing response field in PhonePe callback" };
|
|
558
|
+
}
|
|
559
|
+
let decoded = {};
|
|
560
|
+
try {
|
|
561
|
+
decoded = JSON.parse(base64Decode(parsed.response));
|
|
562
|
+
} catch {
|
|
563
|
+
return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "phonepe", error: "Failed to decode base64 PhonePe response" };
|
|
506
564
|
}
|
|
507
|
-
const
|
|
508
|
-
|
|
565
|
+
const rawEvent = decoded.code || "";
|
|
566
|
+
let normalizedEvent = "UNKNOWN";
|
|
567
|
+
if (rawEvent === "PAYMENT_SUCCESS") {
|
|
568
|
+
normalizedEvent = "PAYMENT_SUCCESS";
|
|
569
|
+
} else if (rawEvent === "PAYMENT_ERROR" || rawEvent === "PAYMENT_DECLINED") {
|
|
570
|
+
normalizedEvent = "PAYMENT_FAILED";
|
|
571
|
+
}
|
|
572
|
+
const orderId = decoded.data?.merchantTransactionId;
|
|
573
|
+
const paymentId = decoded.data?.transactionId;
|
|
574
|
+
const amount = decoded.data?.amount ? decoded.data.amount / 100 : void 0;
|
|
509
575
|
return {
|
|
510
576
|
isValid: true,
|
|
577
|
+
normalizedEvent,
|
|
578
|
+
rawEvent,
|
|
511
579
|
gateway: "phonepe",
|
|
512
|
-
|
|
580
|
+
orderId,
|
|
581
|
+
paymentId,
|
|
582
|
+
amount,
|
|
583
|
+
currency: "INR",
|
|
513
584
|
data: decoded.data
|
|
514
585
|
};
|
|
515
586
|
}
|
|
@@ -624,11 +695,21 @@ var PaytmAdapter = class extends BasePaymentAdapter {
|
|
|
624
695
|
const params = new URLSearchParams(rawString);
|
|
625
696
|
parsed = Object.fromEntries(params.entries());
|
|
626
697
|
}
|
|
627
|
-
const
|
|
698
|
+
const rawStatus = parsed?.STATUS || parsed?.resultInfo?.resultStatus;
|
|
699
|
+
const isSuccess = rawStatus === "TXN_SUCCESS";
|
|
700
|
+
const normalizedEvent = isSuccess ? "PAYMENT_SUCCESS" : "PAYMENT_FAILED";
|
|
701
|
+
const orderId = parsed?.ORDERID || parsed?.orderId;
|
|
702
|
+
const paymentId = parsed?.TXNID || parsed?.txnId;
|
|
703
|
+
const amount = parsed?.TXNAMOUNT ? parseFloat(parsed.TXNAMOUNT) : void 0;
|
|
628
704
|
return {
|
|
629
705
|
isValid: true,
|
|
706
|
+
normalizedEvent,
|
|
707
|
+
rawEvent: rawStatus,
|
|
630
708
|
gateway: "paytm",
|
|
631
|
-
|
|
709
|
+
orderId,
|
|
710
|
+
paymentId,
|
|
711
|
+
amount,
|
|
712
|
+
currency: "INR",
|
|
632
713
|
data: parsed
|
|
633
714
|
};
|
|
634
715
|
}
|
|
@@ -720,6 +801,7 @@ var StripeAdapter = class extends BasePaymentAdapter {
|
|
|
720
801
|
refundId: res.id,
|
|
721
802
|
paymentId: options.paymentId,
|
|
722
803
|
amount: res.amount / 100,
|
|
804
|
+
// Normalized to standard currency units
|
|
723
805
|
status: res.status === "succeeded" ? "SUCCESS" : "PENDING",
|
|
724
806
|
rawResponse: res
|
|
725
807
|
};
|
|
@@ -727,11 +809,11 @@ var StripeAdapter = class extends BasePaymentAdapter {
|
|
|
727
809
|
async verifyWebhook(options) {
|
|
728
810
|
const secret = options.webhookSecret || this.config.webhookSecret;
|
|
729
811
|
if (!secret) {
|
|
730
|
-
return { isValid: false, gateway: "stripe", error: "Stripe webhookSecret is not configured." };
|
|
812
|
+
return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "stripe", error: "Stripe webhookSecret is not configured." };
|
|
731
813
|
}
|
|
732
814
|
const sigHeader = this.getHeader(options.headers, "stripe-signature");
|
|
733
815
|
if (!sigHeader || typeof sigHeader !== "string") {
|
|
734
|
-
return { isValid: false, gateway: "stripe", error: "Missing stripe-signature header." };
|
|
816
|
+
return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "stripe", error: "Missing stripe-signature header." };
|
|
735
817
|
}
|
|
736
818
|
const parts = sigHeader.split(",");
|
|
737
819
|
let timestamp = "";
|
|
@@ -751,11 +833,32 @@ var StripeAdapter = class extends BasePaymentAdapter {
|
|
|
751
833
|
} catch {
|
|
752
834
|
parsed = null;
|
|
753
835
|
}
|
|
836
|
+
const rawEvent = parsed?.type || "";
|
|
837
|
+
let normalizedEvent = "UNKNOWN";
|
|
838
|
+
if (rawEvent === "checkout.session.completed" || rawEvent === "payment_intent.succeeded") {
|
|
839
|
+
normalizedEvent = "PAYMENT_SUCCESS";
|
|
840
|
+
} else if (rawEvent === "payment_intent.payment_failed") {
|
|
841
|
+
normalizedEvent = "PAYMENT_FAILED";
|
|
842
|
+
} else if (rawEvent === "charge.refunded") {
|
|
843
|
+
normalizedEvent = "REFUND_PROCESSED";
|
|
844
|
+
} else if (rawEvent?.includes("dispute")) {
|
|
845
|
+
normalizedEvent = "DISPUTE_CREATED";
|
|
846
|
+
}
|
|
847
|
+
const obj = parsed?.data?.object;
|
|
848
|
+
const orderId = obj?.client_reference_id || obj?.metadata?.order_id || obj?.id;
|
|
849
|
+
const paymentId = obj?.payment_intent || obj?.id;
|
|
850
|
+
const amount = obj?.amount_total ? obj.amount_total / 100 : obj?.amount ? obj.amount / 100 : void 0;
|
|
851
|
+
const currency = obj?.currency ? obj.currency.toUpperCase() : void 0;
|
|
754
852
|
return {
|
|
755
853
|
isValid,
|
|
854
|
+
normalizedEvent,
|
|
855
|
+
rawEvent,
|
|
756
856
|
gateway: "stripe",
|
|
757
|
-
|
|
758
|
-
|
|
857
|
+
orderId,
|
|
858
|
+
paymentId,
|
|
859
|
+
amount,
|
|
860
|
+
currency,
|
|
861
|
+
data: obj
|
|
759
862
|
};
|
|
760
863
|
}
|
|
761
864
|
};
|
|
@@ -824,8 +927,9 @@ var CODAdapter = class extends BasePaymentAdapter {
|
|
|
824
927
|
async verifyWebhook(_options) {
|
|
825
928
|
return {
|
|
826
929
|
isValid: true,
|
|
930
|
+
normalizedEvent: "PAYMENT_SUCCESS",
|
|
931
|
+
rawEvent: "COD_ORDER",
|
|
827
932
|
gateway: "cod",
|
|
828
|
-
event: "COD_ORDER",
|
|
829
933
|
data: {}
|
|
830
934
|
};
|
|
831
935
|
}
|
|
@@ -956,6 +1060,60 @@ var PaymentManager = class {
|
|
|
956
1060
|
const adapter = this.getAdapter(options.gateway);
|
|
957
1061
|
return adapter.verifyWebhook(options);
|
|
958
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Ready-made Next.js 13/14/15 App Router Route Handler Webhook Authenticator.
|
|
1065
|
+
* Directly consumes the standard web Request object with raw stream body handling:
|
|
1066
|
+
*
|
|
1067
|
+
* ```typescript
|
|
1068
|
+
* export async function POST(req: Request) {
|
|
1069
|
+
* const result = await payments.verifyNextJsWebhook(req, { gateway: 'razorpay' });
|
|
1070
|
+
* if (!result.isValid) return new Response('Invalid Signature', { status: 400 });
|
|
1071
|
+
* console.log('Event:', result.normalizedEvent, result.orderId);
|
|
1072
|
+
* return new Response('OK');
|
|
1073
|
+
* }
|
|
1074
|
+
* ```
|
|
1075
|
+
*/
|
|
1076
|
+
async verifyNextJsWebhook(request, options) {
|
|
1077
|
+
try {
|
|
1078
|
+
let rawBody = "";
|
|
1079
|
+
if (typeof request.text === "function") {
|
|
1080
|
+
rawBody = await request.text();
|
|
1081
|
+
} else if (typeof request.body === "string") {
|
|
1082
|
+
rawBody = request.body;
|
|
1083
|
+
} else if (Buffer.isBuffer(request.body)) {
|
|
1084
|
+
rawBody = request.body.toString("utf8");
|
|
1085
|
+
}
|
|
1086
|
+
const headers = {};
|
|
1087
|
+
if (request.headers) {
|
|
1088
|
+
if (typeof request.headers.forEach === "function") {
|
|
1089
|
+
request.headers.forEach((val, key) => {
|
|
1090
|
+
headers[key.toLowerCase()] = val;
|
|
1091
|
+
});
|
|
1092
|
+
} else if (typeof request.headers.entries === "function") {
|
|
1093
|
+
for (const [key, val] of request.headers.entries()) {
|
|
1094
|
+
headers[key.toLowerCase()] = val;
|
|
1095
|
+
}
|
|
1096
|
+
} else if (typeof request.headers === "object") {
|
|
1097
|
+
Object.entries(request.headers).forEach(([k, v]) => {
|
|
1098
|
+
headers[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return this.verifyWebhook({
|
|
1103
|
+
gateway: options.gateway,
|
|
1104
|
+
rawBody,
|
|
1105
|
+
headers,
|
|
1106
|
+
webhookSecret: options.webhookSecret
|
|
1107
|
+
});
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
return {
|
|
1110
|
+
isValid: false,
|
|
1111
|
+
normalizedEvent: "UNKNOWN",
|
|
1112
|
+
gateway: options.gateway,
|
|
1113
|
+
error: `Failed to process Next.js webhook: ${err.message}`
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
959
1117
|
};
|
|
960
1118
|
function createPaymentManager(options) {
|
|
961
1119
|
return new PaymentManager(options);
|