@pandait.tech/payment-nuvei 0.4.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -1
- package/dist/handlers/index.cjs +22 -1
- package/dist/handlers/index.cjs.map +1 -1
- package/dist/handlers/index.d.cts +15 -3
- package/dist/handlers/index.d.ts +15 -3
- package/dist/handlers/index.js +22 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/payment-links/index.cjs.map +1 -1
- package/dist/payment-links/index.d.cts +1 -27
- package/dist/payment-links/index.d.ts +1 -27
- package/dist/payment-links/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -23,10 +23,9 @@ interface MinimalOrder extends FirestoreData {
|
|
|
23
23
|
shippingAddress?: {
|
|
24
24
|
fullName?: string;
|
|
25
25
|
} & FirestoreData;
|
|
26
|
+
/** Optional note shown to the customer post-purchase; echoed into the
|
|
27
|
+
* confirmation email when present. */
|
|
26
28
|
postPurchaseNote?: string;
|
|
27
|
-
paymentLinkId?: string;
|
|
28
|
-
tallerId?: string;
|
|
29
|
-
courseId?: string;
|
|
30
29
|
/** Set by charge.ts when the user opted out of saving the card AND the order
|
|
31
30
|
* enters an intermediate status (3ds-pending / otp-pending). The downstream
|
|
32
31
|
* handler that finalizes the payment (3ds-complete, webhook BY_CRES) reads
|
|
@@ -177,6 +176,19 @@ interface NuveiWebhookPayload {
|
|
|
177
176
|
interface WebhookHandlerDeps {
|
|
178
177
|
firebase: FirebaseDeps;
|
|
179
178
|
email: Pick<EmailService, "sendPaymentConfirmation" | "sendPaymentPending">;
|
|
179
|
+
/**
|
|
180
|
+
* Shared secret used to authenticate the incoming webhook. Paymentez/Nuvei
|
|
181
|
+
* DMNs are NOT signed, so the only robust auth is a secret the merchant
|
|
182
|
+
* embeds in the DMN URL configured in the Nuvei dashboard, e.g.
|
|
183
|
+
* `https://shop.com/api/webhooks/nuvei?key=THE_SECRET` (also accepted as the
|
|
184
|
+
* `x-webhook-key` header). Falls back to `process.env.NUVEI_WEBHOOK_SECRET`.
|
|
185
|
+
*
|
|
186
|
+
* If set, requests without a matching key are rejected with 401. If unset,
|
|
187
|
+
* the handler logs a loud warning and processes anyway (the endpoint is then
|
|
188
|
+
* forgeable — anyone who knows an orderId could fake a "paid" notification).
|
|
189
|
+
* STRONGLY RECOMMENDED in production.
|
|
190
|
+
*/
|
|
191
|
+
webhookSecret?: string;
|
|
180
192
|
/** Optional post-success hook. Fires when the webhook transitions an order to "paid" (either via direct status_detail=3 or via verify BY_CRES). Used for enrollment, fulfillment triggers, paymentLink usage tracking, etc. */
|
|
181
193
|
onPaymentSucceeded?: (order: MinimalOrder & {
|
|
182
194
|
id: string;
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -23,10 +23,9 @@ interface MinimalOrder extends FirestoreData {
|
|
|
23
23
|
shippingAddress?: {
|
|
24
24
|
fullName?: string;
|
|
25
25
|
} & FirestoreData;
|
|
26
|
+
/** Optional note shown to the customer post-purchase; echoed into the
|
|
27
|
+
* confirmation email when present. */
|
|
26
28
|
postPurchaseNote?: string;
|
|
27
|
-
paymentLinkId?: string;
|
|
28
|
-
tallerId?: string;
|
|
29
|
-
courseId?: string;
|
|
30
29
|
/** Set by charge.ts when the user opted out of saving the card AND the order
|
|
31
30
|
* enters an intermediate status (3ds-pending / otp-pending). The downstream
|
|
32
31
|
* handler that finalizes the payment (3ds-complete, webhook BY_CRES) reads
|
|
@@ -177,6 +176,19 @@ interface NuveiWebhookPayload {
|
|
|
177
176
|
interface WebhookHandlerDeps {
|
|
178
177
|
firebase: FirebaseDeps;
|
|
179
178
|
email: Pick<EmailService, "sendPaymentConfirmation" | "sendPaymentPending">;
|
|
179
|
+
/**
|
|
180
|
+
* Shared secret used to authenticate the incoming webhook. Paymentez/Nuvei
|
|
181
|
+
* DMNs are NOT signed, so the only robust auth is a secret the merchant
|
|
182
|
+
* embeds in the DMN URL configured in the Nuvei dashboard, e.g.
|
|
183
|
+
* `https://shop.com/api/webhooks/nuvei?key=THE_SECRET` (also accepted as the
|
|
184
|
+
* `x-webhook-key` header). Falls back to `process.env.NUVEI_WEBHOOK_SECRET`.
|
|
185
|
+
*
|
|
186
|
+
* If set, requests without a matching key are rejected with 401. If unset,
|
|
187
|
+
* the handler logs a loud warning and processes anyway (the endpoint is then
|
|
188
|
+
* forgeable — anyone who knows an orderId could fake a "paid" notification).
|
|
189
|
+
* STRONGLY RECOMMENDED in production.
|
|
190
|
+
*/
|
|
191
|
+
webhookSecret?: string;
|
|
180
192
|
/** Optional post-success hook. Fires when the webhook transitions an order to "paid" (either via direct status_detail=3 or via verify BY_CRES). Used for enrollment, fulfillment triggers, paymentLink usage tracking, etc. */
|
|
181
193
|
onPaymentSucceeded?: (order: MinimalOrder & {
|
|
182
194
|
id: string;
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FieldValue } from 'firebase-admin/firestore';
|
|
2
1
|
import crypto from 'crypto';
|
|
2
|
+
import { FieldValue } from 'firebase-admin/firestore';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
// src/http.ts
|
|
@@ -10,6 +10,15 @@ function json(data, init) {
|
|
|
10
10
|
}
|
|
11
11
|
return new Response(JSON.stringify(data), { ...init, headers });
|
|
12
12
|
}
|
|
13
|
+
function timingSafeEqualStr(a, b) {
|
|
14
|
+
const ab = Buffer.from(a, "utf8");
|
|
15
|
+
const bb = Buffer.from(b, "utf8");
|
|
16
|
+
if (ab.length !== bb.length) {
|
|
17
|
+
crypto.timingSafeEqual(ab, ab);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return crypto.timingSafeEqual(ab, bb);
|
|
21
|
+
}
|
|
13
22
|
function getCookie(request, name) {
|
|
14
23
|
const header = request.headers.get("cookie");
|
|
15
24
|
if (!header) return void 0;
|
|
@@ -688,8 +697,20 @@ function extractCres(payload) {
|
|
|
688
697
|
function createWebhookHandler(deps) {
|
|
689
698
|
const logger = deps.logger ?? console;
|
|
690
699
|
const { db } = deps.firebase;
|
|
700
|
+
const expectedSecret = deps.webhookSecret ?? process.env.NUVEI_WEBHOOK_SECRET;
|
|
691
701
|
return async function POST(request) {
|
|
692
702
|
try {
|
|
703
|
+
if (expectedSecret) {
|
|
704
|
+
const provided = new URL(request.url).searchParams.get("key") ?? request.headers.get("x-webhook-key") ?? "";
|
|
705
|
+
if (!timingSafeEqualStr(provided, expectedSecret)) {
|
|
706
|
+
logger.warn("[webhook] rejected: missing/invalid webhook secret");
|
|
707
|
+
return json({ error: "Unauthorized" }, { status: 401 });
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
logger.warn(
|
|
711
|
+
"[webhook] NUVEI_WEBHOOK_SECRET is not configured \u2014 this endpoint is UNAUTHENTICATED and forgeable. Set a secret (deps.webhookSecret or env) and append it to your DMN URL as ?key=... before going to production."
|
|
712
|
+
);
|
|
713
|
+
}
|
|
693
714
|
const payload = await request.json();
|
|
694
715
|
logger.log("[webhook] Full payload:", JSON.stringify(payload));
|
|
695
716
|
const { transaction } = payload;
|