@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 CHANGED
@@ -1,39 +1,91 @@
1
- # @boostengine/payments
1
+ # @boostengine/payments 💳
2
2
 
3
- > **Unified Multi-Gateway Payment Orchestration Layer** for Indian & Global eCommerce. Seamlessly plug in **Razorpay, Cashfree, PhonePe, Paytm, Stripe, and COD** through a single unified API with smart routing, automatic fallbacks, and signature verification.
3
+ [![npm version](https://img.shields.io/npm/v/@boostengine/payments.svg?style=flat-square&color=blue)](https://www.npmjs.com/package/@boostengine/payments)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@boostengine/payments.svg?style=flat-square&color=green)](https://www.npmjs.com/package/@boostengine/payments)
5
+ [![license](https://img.shields.io/npm/l/@boostengine/payments.svg?style=flat-square)](https://github.com/boostengine/boostengine/blob/main/LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6.svg?style=flat-square)](https://www.typescriptlang.org/)
7
+ [![Gateways](https://img.shields.io/badge/Gateways-Razorpay%20%7C%20Cashfree%20%7C%20PhonePe%20%7C%20Paytm%20%7C%20Stripe%20%7C%20COD-purple.svg?style=flat-square)](https://npmjs.com/package/@boostengine/payments)
8
+
9
+ > **Unified multi-gateway payment orchestration for Indian and global eCommerce. Connect Razorpay, Cashfree, PhonePe, Paytm, Stripe, and Cash on Delivery (COD) with a single, unified API. Features automatic gateway failover, smart currency routing, Next.js webhook normalizers, and React checkout hooks.**
10
+
11
+ Zero dependency bloat — uses native Node.js `crypto` and `fetch`. No need to install 6 separate heavy vendor SDKs.
4
12
 
5
13
  ---
6
14
 
7
- ## Why @boostengine/payments?
15
+ ## 📸 Architecture & Payment Flow
16
+
17
+ ```text
18
+ Customer Clicks "Pay Now" (₹1,499)
19
+
20
+
21
+ ┌─────────────────────────────────────────────────────────────┐
22
+ │ Smart Payment Orchestrator │
23
+ ├─────────────────────────────────────────────────────────────┤
24
+ │ Currency Routing: │
25
+ │ - If INR ──► Route to Cashfree / Razorpay / PhonePe │
26
+ │ - If USD ──► Route to Stripe │
27
+ │ - If COD ──► Verify min/max limits & add handling fee │
28
+ │ │
29
+ │ High-Availability Failover: │
30
+ │ [Cashfree Server 500] ──► Auto-retry with [Razorpay] ──► OK│
31
+ └──────────────────────────────┬──────────────────────────────┘
32
+
33
+
34
+ ┌─────────────────────────────────────────────────────────────┐
35
+ │ Client Checkout Hook (`useBoostPayment`) │
36
+ ├─────────────────────────────────────────────────────────────┤
37
+ │ Opens Razorpay Popup / Cashfree Dropin / PhonePe App / UPI │
38
+ └──────────────────────────────┬──────────────────────────────┘
39
+
40
+
41
+ ┌─────────────────────────────────────────────────────────────┐
42
+ │ Next.js Webhook Handler (2 Lines) │
43
+ ├─────────────────────────────────────────────────────────────┤
44
+ │ Validates HMAC-SHA256 signature and returns normalized event:│
45
+ │ 'PAYMENT_SUCCESS' | 'PAYMENT_FAILED' | 'REFUND_PROCESSED' │
46
+ └─────────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ---
8
50
 
9
- - 🔌 **Unified API**: Write your payment flow once. Switch or combine Razorpay, Cashfree, PhonePe, or Stripe without rewriting checkout logic.
10
- - 🛡️ **Smart Fallback**: If your primary payment gateway has a downtime or failure, transactions automatically failover to your secondary gateway!
11
- - 💱 **Multi-Currency Auto-Routing**: Automatically route USD/EUR to Stripe and INR to Razorpay/Cashfree/PhonePe.
12
- - 🔐 **Built-in Signature Verification**: Native verification for Razorpay HMAC, Cashfree webhook, PhonePe SHA-256 + Salt `X-VERIFY`, and Stripe webhooks.
13
- - 🪶 **Zero Dependency Bloat**: Uses native Node.js `fetch` and `crypto`. No 10 heavy third-party vendor SDKs.
51
+ ## 🌟 Gateway Comparison
52
+
53
+ | Gateway | Supported Currencies | Methods Supported | Best For |
54
+ | :--- | :--- | :--- | :--- |
55
+ | **Razorpay** | INR, USD, EUR + 90 more | UPI, Cards, NetBanking, Wallets, EMI | Reliable Indian D2C checkout |
56
+ | **Cashfree** | INR | Instant UPI QR, Intent, NetBanking, Cards | Low transaction fees & high UPI success |
57
+ | **PhonePe** | INR | PhonePe App, UPI Intent, QR Code | Fast mobile conversions in India |
58
+ | **Stripe** | Global (135+ currencies) | Credit/Debit Cards, Apple Pay, Google Pay | International cross-border sales |
59
+ | **Paytm** | INR | Paytm Wallet, UPI, Postpaid, NetBanking | Indian mobile users |
60
+ | **Cash on Delivery** | Any | Pay in cash upon delivery | Tier 2/3 Indian cities |
14
61
 
15
62
  ---
16
63
 
17
64
  ## 📦 Installation
18
65
 
19
66
  ```bash
67
+ # npm
20
68
  npm install @boostengine/payments
69
+
70
+ # pnpm
71
+ pnpm add @boostengine/payments
72
+
73
+ # yarn
74
+ yarn add @boostengine/payments
21
75
  ```
22
76
 
23
77
  ---
24
78
 
25
- ## 🚀 Quickstart
79
+ ## 🚀 Step-by-Step Integration Guide
26
80
 
27
- ### 1. Initialize PaymentManager
81
+ ### Step 1: Initialize Payment Manager (`lib/payments.ts`)
28
82
 
29
83
  ```typescript
30
84
  import { createPaymentManager } from '@boostengine/payments';
31
85
 
32
86
  export const payments = createPaymentManager({
33
- // Default gateway when none is specified
34
87
  defaultGateway: 'cashfree',
35
88
 
36
- // Configure any or all gateways
37
89
  gateways: {
38
90
  cashfree: {
39
91
  appId: process.env.CASHFREE_APP_ID!,
@@ -58,11 +110,11 @@ export const payments = createPaymentManager({
58
110
  cod: {
59
111
  minOrderValue: 200,
60
112
  maxOrderValue: 10000,
61
- extraFee: 49, // Rs. 49 COD handling charge
113
+ extraFee: 49, // 49 COD handling charge
62
114
  },
63
115
  },
64
116
 
65
- // Smart Routing & Fallbacks
117
+ // Smart Routing & High Availability
66
118
  smartRouting: {
67
119
  currencyMap: {
68
120
  USD: 'stripe',
@@ -76,122 +128,142 @@ export const payments = createPaymentManager({
76
128
 
77
129
  ---
78
130
 
79
- ### 2. Create an Order (Single or Multi-Gateway)
131
+ ### Step 2: Create Order API Route (`app/api/checkout/route.ts`)
132
+
133
+ Pass standard human amounts (e.g. `1499.00`). Paise and cents conversion is handled automatically!
80
134
 
81
135
  ```typescript
82
- // Works identically across Cashfree, Razorpay, PhonePe, Stripe, and COD!
83
- const order = await payments.createOrder({
84
- amount: 1499.00,
85
- currency: 'INR',
86
- receipt: `order_${Date.now()}`,
87
- customer: {
88
- name: 'Aman Sharma',
89
- email: 'aman@example.com',
90
- phone: '9876543210',
91
- },
92
- // Optionally override gateway per checkout button:
93
- // gateway: 'razorpay' | 'phonepe' | 'cashfree' | 'stripe' | 'cod'
94
- });
136
+ import { payments } from '@/lib/payments';
137
+
138
+ export async function POST(req: Request) {
139
+ const { amount, customer, gateway } = await req.json();
140
+
141
+ // Create order with automatic fallback if primary gateway is down
142
+ const order = await payments.createOrderWithFallback({
143
+ amount: amount, // e.g. 1499
144
+ currency: 'INR',
145
+ receipt: `order_${Date.now()}`,
146
+ customer: {
147
+ name: customer.name,
148
+ email: customer.email,
149
+ phone: customer.phone,
150
+ },
151
+ fallbackChain: gateway ? [gateway, 'razorpay'] : ['cashfree', 'razorpay'],
152
+ });
95
153
 
96
- console.log(order);
97
- /*
98
- Output:
99
- {
100
- gateway: 'cashfree',
101
- orderId: 'order_1709923812',
102
- gatewayOrderId: 'order_1709923812',
103
- amount: 1499,
104
- currency: 'INR',
105
- status: 'CREATED',
106
- paymentSessionId: 'session_cf_109283719283', // Ready for Cashfree Dropin
107
- redirectUrl: '...', // Available for PhonePe / Stripe
108
- rawResponse: { ... }
154
+ return Response.json(order);
109
155
  }
110
- */
111
156
  ```
112
157
 
113
158
  ---
114
159
 
115
- ### 3. Smart Fallback (High-Availability Checkout)
116
-
117
- If your primary payment gateway experiences bank server outages or 500 errors, automatic fallback routes the order through the next available gateway:
118
-
119
- ```typescript
120
- const order = await payments.createOrderWithFallback({
121
- amount: 1499.00,
122
- currency: 'INR',
123
- receipt: `order_${Date.now()}`,
124
- customer: {
125
- name: 'Aman Sharma',
126
- email: 'aman@example.com',
127
- phone: '9876543210',
128
- },
129
- fallbackChain: ['razorpay', 'cashfree', 'phonepe'],
130
- });
131
- ```
132
-
133
- ---
134
-
135
- ### 4. Verify Payment & Webhooks
136
-
137
- ```typescript
138
- // 1. Verify frontend checkout completion:
139
- const verification = await payments.verifyPayment({
140
- gateway: 'razorpay',
141
- orderId: 'order_123',
142
- paymentId: 'pay_456',
143
- signature: req.body.razorpay_signature,
144
- });
145
-
146
- if (verification.isSuccessful) {
147
- console.log(`Payment confirmed! ID: ${verification.paymentId}`);
148
- }
149
-
150
- // 2. Universal Webhook Authenticator:
151
- const webhook = await payments.verifyWebhook({
152
- gateway: 'cashfree', // or 'razorpay' | 'phonepe' | 'stripe'
153
- rawBody: req.rawBody,
154
- headers: req.headers,
155
- });
156
-
157
- if (webhook.isValid) {
158
- console.log(`Verified webhook event: ${webhook.event}`, webhook.data);
160
+ ### Step 3: Frontend Checkout Button (`components/CheckoutButton.tsx`)
161
+
162
+ The `useBoostPayment` hook automatically injects the right vendor scripts on demand and displays payment modals:
163
+
164
+ ```tsx
165
+ 'use client';
166
+
167
+ import { useBoostPayment } from '@boostengine/payments/react';
168
+ import { useRouter } from 'next/navigation';
169
+
170
+ export default function CheckoutButton({ cartTotal, customer }: any) {
171
+ const router = useRouter();
172
+ const { openPaymentModal, isProcessing } = useBoostPayment();
173
+
174
+ const handlePay = async (gateway: 'razorpay' | 'cashfree' | 'phonepe' | 'cod') => {
175
+ // 1. Create order on server
176
+ const res = await fetch('/api/checkout', {
177
+ method: 'POST',
178
+ body: JSON.stringify({ amount: cartTotal, customer, gateway }),
179
+ });
180
+ const order = await res.json();
181
+
182
+ // 2. Open popup modal or redirect
183
+ openPaymentModal({
184
+ order,
185
+ name: 'BoostStore',
186
+ description: 'Order Payment',
187
+ themeColor: '#4f46e5',
188
+ onSuccess: async (result) => {
189
+ router.push(`/order-success?id=${result.orderId}`);
190
+ },
191
+ onFailure: (err) => {
192
+ alert(`Payment failed: ${err.message}`);
193
+ },
194
+ });
195
+ };
196
+
197
+ return (
198
+ <div style={{ display: 'flex', gap: '10px' }}>
199
+ <button disabled={isProcessing} onClick={() => handlePay('cashfree')}>
200
+ Pay Online (UPI / Card)
201
+ </button>
202
+ <button disabled={isProcessing} onClick={() => handlePay('cod')}>
203
+ Cash on Delivery
204
+ </button>
205
+ </div>
206
+ );
159
207
  }
160
208
  ```
161
209
 
162
210
  ---
163
211
 
164
- ### 5. Instant Refunds
212
+ ### Step 4: Next.js Universal Webhook Handler (`app/api/webhooks/[gateway]/route.ts`)
165
213
 
166
- ```typescript
167
- const refund = await payments.refund({
168
- gateway: 'razorpay',
169
- paymentId: 'pay_456',
170
- amount: 1499.00, // full or partial
171
- reason: 'Customer cancelled before dispatch',
172
- });
214
+ Verify webhook signatures in 2 lines with standard normalized events:
173
215
 
174
- console.log('Refund ID:', refund.refundId, 'Status:', refund.status);
216
+ ```typescript
217
+ import { payments } from '@/lib/payments';
218
+
219
+ export async function POST(
220
+ req: Request,
221
+ { params }: { params: { gateway: string } }
222
+ ) {
223
+ const result = await payments.verifyNextJsWebhook(req, {
224
+ gateway: params.gateway as any,
225
+ });
226
+
227
+ if (!result.isValid) {
228
+ return new Response('Invalid Signature', { status: 400 });
229
+ }
230
+
231
+ // Handle standardized events across all payment gateways!
232
+ switch (result.normalizedEvent) {
233
+ case 'PAYMENT_SUCCESS':
234
+ console.log(`✅ Order ${result.orderId} paid! Amount: ₹${result.amount}`);
235
+ // TODO: Update Order status to 'PAID' in database
236
+ break;
237
+
238
+ case 'PAYMENT_FAILED':
239
+ console.log(`❌ Order ${result.orderId} failed.`);
240
+ break;
241
+
242
+ case 'REFUND_PROCESSED':
243
+ console.log(`🔄 Refund completed for ${result.paymentId}`);
244
+ break;
245
+ }
246
+
247
+ return new Response('OK');
248
+ }
175
249
  ```
176
250
 
177
251
  ---
178
252
 
179
- ## 🛠️ CLI Quickstart
253
+ ## 🛠️ CLI Utilities
180
254
 
181
- Generate an environment template or check supported gateways:
255
+ Test and configure payment credentials quickly:
182
256
 
183
257
  ```bash
184
- # List all 6 supported gateways
258
+ # List supported gateways and their status
185
259
  npx @boostengine/payments list
186
260
 
187
- # Generate .env.payments.example template
261
+ # Generate .env.payments template
188
262
  npx @boostengine/payments init-env
189
-
190
- # Compute quick SHA-256 hash
191
- npx @boostengine/payments hash "my_test_payload"
192
263
  ```
193
264
 
194
265
  ---
195
266
 
196
267
  ## 📄 License
197
- MIT © Boost Engine Team
268
+
269
+ MIT © [Boost Engine](https://github.com/boostengine)
package/dist/index.cjs CHANGED
@@ -178,6 +178,7 @@ var RazorpayAdapter = class extends BasePaymentAdapter {
178
178
  paymentId: payment.id,
179
179
  orderId: options.orderId,
180
180
  amount: payment.amount / 100,
181
+ // Normalized back to standard currency units
181
182
  currency: payment.currency,
182
183
  paymentMethod: payment.method,
183
184
  rawResponse: payment
@@ -202,6 +203,7 @@ var RazorpayAdapter = class extends BasePaymentAdapter {
202
203
  refundId: res.id,
203
204
  paymentId: options.paymentId,
204
205
  amount: res.amount / 100,
206
+ // Normalized to standard currency units
205
207
  status: res.status === "processed" ? "SUCCESS" : "PENDING",
206
208
  rawResponse: res
207
209
  };
@@ -211,32 +213,58 @@ var RazorpayAdapter = class extends BasePaymentAdapter {
211
213
  if (!secret) {
212
214
  return {
213
215
  isValid: false,
216
+ normalizedEvent: "UNKNOWN",
214
217
  gateway: "razorpay",
215
218
  error: "Razorpay webhookSecret is not configured."
216
219
  };
217
220
  }
218
221
  const signature = this.getHeader(options.headers, "x-razorpay-signature");
219
- if (!signature || typeof signature !== "string") {
222
+ if (!signature) {
220
223
  return {
221
224
  isValid: false,
225
+ normalizedEvent: "UNKNOWN",
222
226
  gateway: "razorpay",
223
- error: "Missing X-Razorpay-Signature header."
227
+ error: "Missing x-razorpay-signature header."
224
228
  };
225
229
  }
226
230
  const rawString = typeof options.rawBody === "string" ? options.rawBody : options.rawBody.toString("utf8");
227
231
  const expectedSignature = hmacSha256(rawString, secret);
228
232
  const isValid = safeCompare(expectedSignature, signature);
229
- let parsedData;
233
+ let parsed;
230
234
  try {
231
- parsedData = JSON.parse(rawString);
235
+ parsed = JSON.parse(rawString);
232
236
  } catch {
233
- parsedData = null;
237
+ parsed = null;
234
238
  }
239
+ const rawEvent = parsed?.event || "";
240
+ let normalizedEvent = "UNKNOWN";
241
+ if (rawEvent === "order.paid" || rawEvent === "payment.captured" || rawEvent === "payment.authorized") {
242
+ normalizedEvent = "PAYMENT_SUCCESS";
243
+ } else if (rawEvent === "payment.failed") {
244
+ normalizedEvent = "PAYMENT_FAILED";
245
+ } else if (rawEvent === "refund.processed" || rawEvent === "refund.created") {
246
+ normalizedEvent = "REFUND_PROCESSED";
247
+ } else if (rawEvent === "refund.failed") {
248
+ normalizedEvent = "REFUND_FAILED";
249
+ } else if (rawEvent?.includes("dispute")) {
250
+ normalizedEvent = "DISPUTE_CREATED";
251
+ }
252
+ const paymentEntity = parsed?.payload?.payment?.entity;
253
+ const orderEntity = parsed?.payload?.order?.entity;
254
+ const orderId = orderEntity?.receipt || paymentEntity?.order_id || orderEntity?.id;
255
+ const paymentId = paymentEntity?.id;
256
+ const amount = paymentEntity?.amount ? paymentEntity.amount / 100 : orderEntity?.amount ? orderEntity.amount / 100 : void 0;
257
+ const currency = paymentEntity?.currency || orderEntity?.currency;
235
258
  return {
236
259
  isValid,
260
+ normalizedEvent,
261
+ rawEvent,
237
262
  gateway: "razorpay",
238
- event: parsedData?.event,
239
- data: parsedData?.payload
263
+ orderId,
264
+ paymentId,
265
+ amount,
266
+ currency,
267
+ data: parsed?.payload
240
268
  };
241
269
  }
242
270
  };
@@ -369,17 +397,41 @@ var CashfreeAdapter = class extends BasePaymentAdapter {
369
397
  ).toString("base64");
370
398
  isValid = safeCompare(expectedSignature, signature);
371
399
  }
372
- let parsedData;
400
+ let parsed;
373
401
  try {
374
- parsedData = JSON.parse(rawString);
402
+ parsed = JSON.parse(rawString);
375
403
  } catch {
376
- parsedData = null;
404
+ parsed = null;
405
+ }
406
+ const rawEvent = parsed?.type || "";
407
+ let normalizedEvent = "UNKNOWN";
408
+ if (rawEvent === "PAYMENT_SUCCESS_WEBHOOK") {
409
+ normalizedEvent = "PAYMENT_SUCCESS";
410
+ } else if (rawEvent === "PAYMENT_FAILED_WEBHOOK" || rawEvent === "USER_DROPPED_WEBHOOK") {
411
+ normalizedEvent = "PAYMENT_FAILED";
412
+ } else if (rawEvent === "REFUND_STATUS_WEBHOOK") {
413
+ const refundStatus = parsed?.data?.refund?.refund_status;
414
+ normalizedEvent = refundStatus === "SUCCESS" ? "REFUND_PROCESSED" : "REFUND_FAILED";
415
+ } else if (rawEvent?.includes("DISPUTE")) {
416
+ normalizedEvent = "DISPUTE_CREATED";
377
417
  }
418
+ const orderData = parsed?.data?.order;
419
+ const paymentData = parsed?.data?.payment;
420
+ const refundData = parsed?.data?.refund;
421
+ const orderId = orderData?.order_id || refundData?.order_id;
422
+ const paymentId = paymentData ? String(paymentData.cf_payment_id) : refundData ? String(refundData.cf_payment_id) : void 0;
423
+ const amount = paymentData?.payment_amount ?? orderData?.order_amount ?? refundData?.refund_amount;
424
+ const currency = paymentData?.payment_currency || orderData?.order_currency;
378
425
  return {
379
426
  isValid,
427
+ normalizedEvent,
428
+ rawEvent,
380
429
  gateway: "cashfree",
381
- event: parsedData?.type,
382
- data: parsedData?.data
430
+ orderId,
431
+ paymentId,
432
+ amount,
433
+ currency,
434
+ data: parsed?.data
383
435
  };
384
436
  }
385
437
  };
@@ -505,17 +557,36 @@ var PhonePeAdapter = class extends BasePaymentAdapter {
505
557
  try {
506
558
  parsed = JSON.parse(rawString);
507
559
  } catch {
508
- return { isValid: false, gateway: "phonepe", error: "Invalid JSON payload" };
560
+ return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "phonepe", error: "Invalid JSON payload" };
509
561
  }
510
562
  if (!parsed.response) {
511
- return { isValid: false, gateway: "phonepe", error: "Missing response field in PhonePe callback" };
563
+ return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "phonepe", error: "Missing response field in PhonePe callback" };
564
+ }
565
+ let decoded = {};
566
+ try {
567
+ decoded = JSON.parse(base64Decode(parsed.response));
568
+ } catch {
569
+ return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "phonepe", error: "Failed to decode base64 PhonePe response" };
512
570
  }
513
- const decoded = JSON.parse(base64Decode(parsed.response));
514
- decoded.code === "PAYMENT_SUCCESS";
571
+ const rawEvent = decoded.code || "";
572
+ let normalizedEvent = "UNKNOWN";
573
+ if (rawEvent === "PAYMENT_SUCCESS") {
574
+ normalizedEvent = "PAYMENT_SUCCESS";
575
+ } else if (rawEvent === "PAYMENT_ERROR" || rawEvent === "PAYMENT_DECLINED") {
576
+ normalizedEvent = "PAYMENT_FAILED";
577
+ }
578
+ const orderId = decoded.data?.merchantTransactionId;
579
+ const paymentId = decoded.data?.transactionId;
580
+ const amount = decoded.data?.amount ? decoded.data.amount / 100 : void 0;
515
581
  return {
516
582
  isValid: true,
583
+ normalizedEvent,
584
+ rawEvent,
517
585
  gateway: "phonepe",
518
- event: decoded.code,
586
+ orderId,
587
+ paymentId,
588
+ amount,
589
+ currency: "INR",
519
590
  data: decoded.data
520
591
  };
521
592
  }
@@ -630,11 +701,21 @@ var PaytmAdapter = class extends BasePaymentAdapter {
630
701
  const params = new URLSearchParams(rawString);
631
702
  parsed = Object.fromEntries(params.entries());
632
703
  }
633
- const isSuccess = parsed?.STATUS === "TXN_SUCCESS" || parsed?.resultInfo?.resultStatus === "TXN_SUCCESS";
704
+ const rawStatus = parsed?.STATUS || parsed?.resultInfo?.resultStatus;
705
+ const isSuccess = rawStatus === "TXN_SUCCESS";
706
+ const normalizedEvent = isSuccess ? "PAYMENT_SUCCESS" : "PAYMENT_FAILED";
707
+ const orderId = parsed?.ORDERID || parsed?.orderId;
708
+ const paymentId = parsed?.TXNID || parsed?.txnId;
709
+ const amount = parsed?.TXNAMOUNT ? parseFloat(parsed.TXNAMOUNT) : void 0;
634
710
  return {
635
711
  isValid: true,
712
+ normalizedEvent,
713
+ rawEvent: rawStatus,
636
714
  gateway: "paytm",
637
- event: isSuccess ? "TXN_SUCCESS" : "TXN_FAILURE",
715
+ orderId,
716
+ paymentId,
717
+ amount,
718
+ currency: "INR",
638
719
  data: parsed
639
720
  };
640
721
  }
@@ -726,6 +807,7 @@ var StripeAdapter = class extends BasePaymentAdapter {
726
807
  refundId: res.id,
727
808
  paymentId: options.paymentId,
728
809
  amount: res.amount / 100,
810
+ // Normalized to standard currency units
729
811
  status: res.status === "succeeded" ? "SUCCESS" : "PENDING",
730
812
  rawResponse: res
731
813
  };
@@ -733,11 +815,11 @@ var StripeAdapter = class extends BasePaymentAdapter {
733
815
  async verifyWebhook(options) {
734
816
  const secret = options.webhookSecret || this.config.webhookSecret;
735
817
  if (!secret) {
736
- return { isValid: false, gateway: "stripe", error: "Stripe webhookSecret is not configured." };
818
+ return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "stripe", error: "Stripe webhookSecret is not configured." };
737
819
  }
738
820
  const sigHeader = this.getHeader(options.headers, "stripe-signature");
739
821
  if (!sigHeader || typeof sigHeader !== "string") {
740
- return { isValid: false, gateway: "stripe", error: "Missing stripe-signature header." };
822
+ return { isValid: false, normalizedEvent: "UNKNOWN", gateway: "stripe", error: "Missing stripe-signature header." };
741
823
  }
742
824
  const parts = sigHeader.split(",");
743
825
  let timestamp = "";
@@ -757,11 +839,32 @@ var StripeAdapter = class extends BasePaymentAdapter {
757
839
  } catch {
758
840
  parsed = null;
759
841
  }
842
+ const rawEvent = parsed?.type || "";
843
+ let normalizedEvent = "UNKNOWN";
844
+ if (rawEvent === "checkout.session.completed" || rawEvent === "payment_intent.succeeded") {
845
+ normalizedEvent = "PAYMENT_SUCCESS";
846
+ } else if (rawEvent === "payment_intent.payment_failed") {
847
+ normalizedEvent = "PAYMENT_FAILED";
848
+ } else if (rawEvent === "charge.refunded") {
849
+ normalizedEvent = "REFUND_PROCESSED";
850
+ } else if (rawEvent?.includes("dispute")) {
851
+ normalizedEvent = "DISPUTE_CREATED";
852
+ }
853
+ const obj = parsed?.data?.object;
854
+ const orderId = obj?.client_reference_id || obj?.metadata?.order_id || obj?.id;
855
+ const paymentId = obj?.payment_intent || obj?.id;
856
+ const amount = obj?.amount_total ? obj.amount_total / 100 : obj?.amount ? obj.amount / 100 : void 0;
857
+ const currency = obj?.currency ? obj.currency.toUpperCase() : void 0;
760
858
  return {
761
859
  isValid,
860
+ normalizedEvent,
861
+ rawEvent,
762
862
  gateway: "stripe",
763
- event: parsed?.type,
764
- data: parsed?.data?.object
863
+ orderId,
864
+ paymentId,
865
+ amount,
866
+ currency,
867
+ data: obj
765
868
  };
766
869
  }
767
870
  };
@@ -830,8 +933,9 @@ var CODAdapter = class extends BasePaymentAdapter {
830
933
  async verifyWebhook(_options) {
831
934
  return {
832
935
  isValid: true,
936
+ normalizedEvent: "PAYMENT_SUCCESS",
937
+ rawEvent: "COD_ORDER",
833
938
  gateway: "cod",
834
- event: "COD_ORDER",
835
939
  data: {}
836
940
  };
837
941
  }
@@ -962,6 +1066,60 @@ var PaymentManager = class {
962
1066
  const adapter = this.getAdapter(options.gateway);
963
1067
  return adapter.verifyWebhook(options);
964
1068
  }
1069
+ /**
1070
+ * Ready-made Next.js 13/14/15 App Router Route Handler Webhook Authenticator.
1071
+ * Directly consumes the standard web Request object with raw stream body handling:
1072
+ *
1073
+ * ```typescript
1074
+ * export async function POST(req: Request) {
1075
+ * const result = await payments.verifyNextJsWebhook(req, { gateway: 'razorpay' });
1076
+ * if (!result.isValid) return new Response('Invalid Signature', { status: 400 });
1077
+ * console.log('Event:', result.normalizedEvent, result.orderId);
1078
+ * return new Response('OK');
1079
+ * }
1080
+ * ```
1081
+ */
1082
+ async verifyNextJsWebhook(request, options) {
1083
+ try {
1084
+ let rawBody = "";
1085
+ if (typeof request.text === "function") {
1086
+ rawBody = await request.text();
1087
+ } else if (typeof request.body === "string") {
1088
+ rawBody = request.body;
1089
+ } else if (Buffer.isBuffer(request.body)) {
1090
+ rawBody = request.body.toString("utf8");
1091
+ }
1092
+ const headers = {};
1093
+ if (request.headers) {
1094
+ if (typeof request.headers.forEach === "function") {
1095
+ request.headers.forEach((val, key) => {
1096
+ headers[key.toLowerCase()] = val;
1097
+ });
1098
+ } else if (typeof request.headers.entries === "function") {
1099
+ for (const [key, val] of request.headers.entries()) {
1100
+ headers[key.toLowerCase()] = val;
1101
+ }
1102
+ } else if (typeof request.headers === "object") {
1103
+ Object.entries(request.headers).forEach(([k, v]) => {
1104
+ headers[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
1105
+ });
1106
+ }
1107
+ }
1108
+ return this.verifyWebhook({
1109
+ gateway: options.gateway,
1110
+ rawBody,
1111
+ headers,
1112
+ webhookSecret: options.webhookSecret
1113
+ });
1114
+ } catch (err) {
1115
+ return {
1116
+ isValid: false,
1117
+ normalizedEvent: "UNKNOWN",
1118
+ gateway: options.gateway,
1119
+ error: `Failed to process Next.js webhook: ${err.message}`
1120
+ };
1121
+ }
1122
+ }
965
1123
  };
966
1124
  function createPaymentManager(options) {
967
1125
  return new PaymentManager(options);