@pandait.tech/payment-nuvei 0.1.0 → 0.2.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 +29 -0
- package/dist/adapters/index.cjs +54 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +35 -0
- package/dist/adapters/index.d.ts +35 -0
- package/dist/adapters/index.js +51 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/handlers/index.cjs +188 -105
- package/dist/handlers/index.cjs.map +1 -1
- package/dist/handlers/index.d.cts +29 -117
- package/dist/handlers/index.d.ts +29 -117
- package/dist/handlers/index.js +188 -106
- package/dist/handlers/index.js.map +1 -1
- package/dist/ui/index.cjs +418 -290
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +4 -4
- package/dist/ui/index.d.ts +4 -4
- package/dist/ui/index.js +418 -290
- package/dist/ui/index.js.map +1 -1
- package/package.json +17 -9
package/dist/handlers/index.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var server = require('next/server');
|
|
4
3
|
var firestore = require('firebase-admin/firestore');
|
|
5
4
|
var crypto = require('crypto');
|
|
6
5
|
var zod = require('zod');
|
|
@@ -9,7 +8,27 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
9
8
|
|
|
10
9
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
11
10
|
|
|
12
|
-
// src/
|
|
11
|
+
// src/http.ts
|
|
12
|
+
function json(data, init) {
|
|
13
|
+
const headers = new Headers(init?.headers);
|
|
14
|
+
if (!headers.has("content-type")) {
|
|
15
|
+
headers.set("content-type", "application/json; charset=utf-8");
|
|
16
|
+
}
|
|
17
|
+
return new Response(JSON.stringify(data), { ...init, headers });
|
|
18
|
+
}
|
|
19
|
+
function getCookie(request, name) {
|
|
20
|
+
const header = request.headers.get("cookie");
|
|
21
|
+
if (!header) return void 0;
|
|
22
|
+
for (const part of header.split(";")) {
|
|
23
|
+
const eq = part.indexOf("=");
|
|
24
|
+
if (eq === -1) continue;
|
|
25
|
+
const key = part.slice(0, eq).trim();
|
|
26
|
+
if (key === name) {
|
|
27
|
+
return decodeURIComponent(part.slice(eq + 1).trim());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
13
32
|
var NUVEI_DOMAIN = "paymentez.com";
|
|
14
33
|
function getBaseUrl() {
|
|
15
34
|
const env = process.env.NUVEI_ENV === "prod" ? "prod" : "stg";
|
|
@@ -212,15 +231,15 @@ function createChargeHandler(deps) {
|
|
|
212
231
|
const { db, auth } = deps.firebase;
|
|
213
232
|
return async function POST(request) {
|
|
214
233
|
try {
|
|
215
|
-
const sessionCookie = request
|
|
234
|
+
const sessionCookie = getCookie(request, "__session");
|
|
216
235
|
if (!sessionCookie || !auth) {
|
|
217
|
-
return
|
|
236
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
218
237
|
}
|
|
219
238
|
let decodedToken;
|
|
220
239
|
try {
|
|
221
240
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
222
241
|
} catch {
|
|
223
|
-
return
|
|
242
|
+
return json({ error: "Sesion invalida" }, { status: 401 });
|
|
224
243
|
}
|
|
225
244
|
const clientIpForLimit = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "unknown";
|
|
226
245
|
if (deps.rateLimit) {
|
|
@@ -230,7 +249,7 @@ function createChargeHandler(deps) {
|
|
|
230
249
|
15 * 60 * 1e3
|
|
231
250
|
);
|
|
232
251
|
if (!allowed) {
|
|
233
|
-
return
|
|
252
|
+
return json(
|
|
234
253
|
{ error: "Demasiados intentos. Intent\xE1 de nuevo en unos minutos." },
|
|
235
254
|
{ status: 429 }
|
|
236
255
|
);
|
|
@@ -240,7 +259,7 @@ function createChargeHandler(deps) {
|
|
|
240
259
|
if (deps.turnstile) {
|
|
241
260
|
const turnstileToken = body.turnstileToken;
|
|
242
261
|
if (!turnstileToken) {
|
|
243
|
-
return
|
|
262
|
+
return json(
|
|
244
263
|
{ error: "Verificaci\xF3n de seguridad faltante. Recarg\xE1 la p\xE1gina." },
|
|
245
264
|
{ status: 403 }
|
|
246
265
|
);
|
|
@@ -250,7 +269,7 @@ function createChargeHandler(deps) {
|
|
|
250
269
|
clientIpForLimit === "unknown" ? void 0 : clientIpForLimit
|
|
251
270
|
);
|
|
252
271
|
if (!turnstileResult.success) {
|
|
253
|
-
return
|
|
272
|
+
return json(
|
|
254
273
|
{ error: "No pudimos verificar que sos humano. Recarg\xE1 la p\xE1gina." },
|
|
255
274
|
{ status: 403 }
|
|
256
275
|
);
|
|
@@ -276,18 +295,18 @@ function createChargeHandler(deps) {
|
|
|
276
295
|
userId: userId?.substring(0, 8) + "..."
|
|
277
296
|
});
|
|
278
297
|
if (!token || !orderId || !amount) {
|
|
279
|
-
return
|
|
298
|
+
return json({ error: "Datos incompletos" }, { status: 400 });
|
|
280
299
|
}
|
|
281
300
|
if (decodedToken.uid !== userId) {
|
|
282
|
-
return
|
|
301
|
+
return json({ error: "Usuario no coincide" }, { status: 403 });
|
|
283
302
|
}
|
|
284
303
|
const orderDoc = await db.collection("orders").doc(orderId).get();
|
|
285
304
|
if (!orderDoc.exists) {
|
|
286
|
-
return
|
|
305
|
+
return json({ error: "Orden no encontrada" }, { status: 404 });
|
|
287
306
|
}
|
|
288
307
|
const orderData = orderDoc.data() ?? {};
|
|
289
308
|
if (orderData.status !== "pending") {
|
|
290
|
-
return
|
|
309
|
+
return json(
|
|
291
310
|
{ error: "Esta orden ya fue procesada" },
|
|
292
311
|
{ status: 409 }
|
|
293
312
|
);
|
|
@@ -301,7 +320,7 @@ function createChargeHandler(deps) {
|
|
|
301
320
|
});
|
|
302
321
|
if (customResult.handled) {
|
|
303
322
|
if (!customResult.valid) {
|
|
304
|
-
return
|
|
323
|
+
return json(
|
|
305
324
|
{ error: customResult.error },
|
|
306
325
|
{ status: customResult.status }
|
|
307
326
|
);
|
|
@@ -315,7 +334,7 @@ function createChargeHandler(deps) {
|
|
|
315
334
|
for (const item of orderItems) {
|
|
316
335
|
const productDoc = await db.collection("products").doc(item.productId).get();
|
|
317
336
|
if (!productDoc.exists || !productDoc.data()?.isActive) {
|
|
318
|
-
return
|
|
337
|
+
return json(
|
|
319
338
|
{ error: `El producto "${item.name}" ya no est\xE1 disponible` },
|
|
320
339
|
{ status: 409 }
|
|
321
340
|
);
|
|
@@ -324,7 +343,7 @@ function createChargeHandler(deps) {
|
|
|
324
343
|
const isDigital = productData.isDigital === true;
|
|
325
344
|
const stock = productData.stock ?? 0;
|
|
326
345
|
if (!isDigital && stock < item.quantity) {
|
|
327
|
-
return
|
|
346
|
+
return json(
|
|
328
347
|
{
|
|
329
348
|
error: `Stock insuficiente para "${item.name}" (disponible: ${stock})`
|
|
330
349
|
},
|
|
@@ -337,7 +356,7 @@ function createChargeHandler(deps) {
|
|
|
337
356
|
});
|
|
338
357
|
const expectedItemSubtotal = display.finalSubtotal;
|
|
339
358
|
if (Math.abs(expectedItemSubtotal - item.price) > 0.02) {
|
|
340
|
-
return
|
|
359
|
+
return json(
|
|
341
360
|
{
|
|
342
361
|
error: `El precio de "${item.name}" cambi\xF3. Recarg\xE1 la p\xE1gina para ver el precio actualizado.`
|
|
343
362
|
},
|
|
@@ -352,7 +371,7 @@ function createChargeHandler(deps) {
|
|
|
352
371
|
if (orderPromotionId && orderDiscount > 0) {
|
|
353
372
|
const promoDoc = await db.collection("promotions").doc(orderPromotionId).get();
|
|
354
373
|
if (!promoDoc.exists || !promoDoc.data()?.isActive) {
|
|
355
|
-
return
|
|
374
|
+
return json(
|
|
356
375
|
{ error: "El cupon aplicado ya no es valido. Remuevelo y vuelve a intentar." },
|
|
357
376
|
{ status: 409 }
|
|
358
377
|
);
|
|
@@ -362,13 +381,13 @@ function createChargeHandler(deps) {
|
|
|
362
381
|
const validFrom = promo.rules.validFrom.toDate ? promo.rules.validFrom.toDate() : new Date(promo.rules.validFrom);
|
|
363
382
|
const validUntil = promo.rules.validUntil.toDate ? promo.rules.validUntil.toDate() : new Date(promo.rules.validUntil);
|
|
364
383
|
if (now < validFrom || now > validUntil) {
|
|
365
|
-
return
|
|
384
|
+
return json(
|
|
366
385
|
{ error: "El cupon aplicado ha expirado. Remuevelo y vuelve a intentar." },
|
|
367
386
|
{ status: 409 }
|
|
368
387
|
);
|
|
369
388
|
}
|
|
370
389
|
if (promo.rules.maxTotalUses && promo.currentUses >= promo.rules.maxTotalUses) {
|
|
371
|
-
return
|
|
390
|
+
return json(
|
|
372
391
|
{ error: "El cupon aplicado ya alcanzo su limite de usos." },
|
|
373
392
|
{ status: 409 }
|
|
374
393
|
);
|
|
@@ -387,7 +406,7 @@ function createChargeHandler(deps) {
|
|
|
387
406
|
const verifiedVat = Math.round(verifiedDiscountedSubtotal * 0.15 * 100) / 100;
|
|
388
407
|
const verifiedTotal = Math.round((verifiedDiscountedSubtotal + verifiedVat) * 100) / 100;
|
|
389
408
|
if (verifiedTotal !== amount) {
|
|
390
|
-
return
|
|
409
|
+
return json(
|
|
391
410
|
{
|
|
392
411
|
error: "El monto no coincide con los precios actuales. Actualiza tu carrito."
|
|
393
412
|
},
|
|
@@ -510,7 +529,7 @@ function createChargeHandler(deps) {
|
|
|
510
529
|
(err) => logger.error("[charge] Failed to delete card after payment:", err)
|
|
511
530
|
);
|
|
512
531
|
}
|
|
513
|
-
return
|
|
532
|
+
return json({
|
|
514
533
|
success: true,
|
|
515
534
|
transactionId: nuveiData.transaction.id,
|
|
516
535
|
authorizationCode: hasValidAuthCode ? rawAuthCode : null,
|
|
@@ -525,7 +544,7 @@ function createChargeHandler(deps) {
|
|
|
525
544
|
chargeResponseAt: /* @__PURE__ */ new Date(),
|
|
526
545
|
updatedAt: /* @__PURE__ */ new Date()
|
|
527
546
|
});
|
|
528
|
-
return
|
|
547
|
+
return json({
|
|
529
548
|
review: true,
|
|
530
549
|
orderId,
|
|
531
550
|
transactionId: nuveiData.transaction.id
|
|
@@ -542,7 +561,7 @@ function createChargeHandler(deps) {
|
|
|
542
561
|
...persistDeleteOnPaid,
|
|
543
562
|
updatedAt: /* @__PURE__ */ new Date()
|
|
544
563
|
});
|
|
545
|
-
return
|
|
564
|
+
return json({
|
|
546
565
|
challenge: true,
|
|
547
566
|
challengeHtml: hiddenIframeHtml,
|
|
548
567
|
isDeviceFingerprint: true,
|
|
@@ -558,7 +577,7 @@ function createChargeHandler(deps) {
|
|
|
558
577
|
...persistDeleteOnPaid,
|
|
559
578
|
updatedAt: /* @__PURE__ */ new Date()
|
|
560
579
|
});
|
|
561
|
-
return
|
|
580
|
+
return json({
|
|
562
581
|
otpRequired: true,
|
|
563
582
|
orderId,
|
|
564
583
|
nuveiTransactionId: nuveiData.transaction.id,
|
|
@@ -581,7 +600,7 @@ function createChargeHandler(deps) {
|
|
|
581
600
|
...persistDeleteOnPaid,
|
|
582
601
|
updatedAt: /* @__PURE__ */ new Date()
|
|
583
602
|
});
|
|
584
|
-
return
|
|
603
|
+
return json({
|
|
585
604
|
challenge: true,
|
|
586
605
|
challengeHtml,
|
|
587
606
|
isDeviceFingerprint: false,
|
|
@@ -616,7 +635,7 @@ function createChargeHandler(deps) {
|
|
|
616
635
|
}).catch(() => {
|
|
617
636
|
});
|
|
618
637
|
}
|
|
619
|
-
return
|
|
638
|
+
return json({ error: threeDSErrorMsg }, { status: 400 });
|
|
620
639
|
}
|
|
621
640
|
const failedErrorMsg = getNuveiUserMessage(
|
|
622
641
|
nuveiData.transaction?.status_detail,
|
|
@@ -644,10 +663,10 @@ function createChargeHandler(deps) {
|
|
|
644
663
|
}).catch(() => {
|
|
645
664
|
});
|
|
646
665
|
}
|
|
647
|
-
return
|
|
666
|
+
return json({ error: failedErrorMsg }, { status: 400 });
|
|
648
667
|
} catch (error) {
|
|
649
668
|
logger.error("Payment charge error:", error);
|
|
650
|
-
return
|
|
669
|
+
return json(
|
|
651
670
|
{ error: "Error interno del servidor" },
|
|
652
671
|
{ status: 500 }
|
|
653
672
|
);
|
|
@@ -681,7 +700,7 @@ function createWebhookHandler(deps) {
|
|
|
681
700
|
logger.log("[webhook] Full payload:", JSON.stringify(payload));
|
|
682
701
|
const { transaction } = payload;
|
|
683
702
|
if (!transaction?.id || !transaction?.dev_reference) {
|
|
684
|
-
return
|
|
703
|
+
return json({ error: "Payload inv\xE1lido" }, { status: 400 });
|
|
685
704
|
}
|
|
686
705
|
const incomingCres = extractCres(payload);
|
|
687
706
|
if (incomingCres) {
|
|
@@ -703,7 +722,7 @@ function createWebhookHandler(deps) {
|
|
|
703
722
|
const orderDoc = await orderRef.get();
|
|
704
723
|
if (!orderDoc.exists) {
|
|
705
724
|
logger.error(`Webhook: Order ${orderId} not found`);
|
|
706
|
-
return
|
|
725
|
+
return json({ error: "Orden no encontrada" }, { status: 404 });
|
|
707
726
|
}
|
|
708
727
|
const orderDataEarly = orderDoc.data() ?? {};
|
|
709
728
|
if (incomingCres && orderDataEarly.status === "3ds-pending" && !orderDataEarly.verifyCalledAt) {
|
|
@@ -797,7 +816,7 @@ function createWebhookHandler(deps) {
|
|
|
797
816
|
);
|
|
798
817
|
}
|
|
799
818
|
}
|
|
800
|
-
return
|
|
819
|
+
return json({ received: true, verifiedFromWebhook: true });
|
|
801
820
|
}
|
|
802
821
|
} catch (err) {
|
|
803
822
|
if (err.message !== "ALREADY_CALLED") {
|
|
@@ -891,16 +910,18 @@ function createWebhookHandler(deps) {
|
|
|
891
910
|
);
|
|
892
911
|
}
|
|
893
912
|
}
|
|
894
|
-
return
|
|
913
|
+
return json({ received: true });
|
|
895
914
|
} catch (error) {
|
|
896
915
|
logger.error("Webhook processing error:", error);
|
|
897
|
-
return
|
|
916
|
+
return json(
|
|
898
917
|
{ error: "Error procesando webhook" },
|
|
899
918
|
{ status: 500 }
|
|
900
919
|
);
|
|
901
920
|
}
|
|
902
921
|
};
|
|
903
922
|
}
|
|
923
|
+
|
|
924
|
+
// src/handlers/3ds-callback.ts
|
|
904
925
|
function buildCompletionPage(orderId, transStatus) {
|
|
905
926
|
const html = `<!DOCTYPE html>
|
|
906
927
|
<html><head><meta charset="utf-8"><title>3DS Verification</title></head>
|
|
@@ -925,7 +946,7 @@ function buildCompletionPage(orderId, transStatus) {
|
|
|
925
946
|
Verificando autenticación...
|
|
926
947
|
</p>
|
|
927
948
|
</body></html>`;
|
|
928
|
-
return new
|
|
949
|
+
return new Response(html, {
|
|
929
950
|
status: 200,
|
|
930
951
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
931
952
|
});
|
|
@@ -989,32 +1010,32 @@ function create3dsCompleteHandler(deps) {
|
|
|
989
1010
|
const { db, auth } = deps.firebase;
|
|
990
1011
|
return async function POST(request) {
|
|
991
1012
|
try {
|
|
992
|
-
const sessionCookie = request
|
|
1013
|
+
const sessionCookie = getCookie(request, "__session");
|
|
993
1014
|
if (!sessionCookie || !auth) {
|
|
994
|
-
return
|
|
1015
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
995
1016
|
}
|
|
996
1017
|
let decodedToken;
|
|
997
1018
|
try {
|
|
998
1019
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
999
1020
|
} catch {
|
|
1000
|
-
return
|
|
1021
|
+
return json({ error: "Sesion invalida" }, { status: 401 });
|
|
1001
1022
|
}
|
|
1002
1023
|
const rawBody = await request.json();
|
|
1003
1024
|
const parsed = ThreeDSCompleteSchema.safeParse(rawBody);
|
|
1004
1025
|
if (!parsed.success) {
|
|
1005
|
-
return
|
|
1026
|
+
return json({ error: "Datos incompletos" }, { status: 400 });
|
|
1006
1027
|
}
|
|
1007
1028
|
const { orderId, userId, type, nuveiTransactionId: bodyTxId, otpCode } = parsed.data;
|
|
1008
1029
|
if (decodedToken.uid !== userId) {
|
|
1009
|
-
return
|
|
1030
|
+
return json({ error: "Usuario no coincide" }, { status: 403 });
|
|
1010
1031
|
}
|
|
1011
1032
|
const orderDoc = await db.collection("orders").doc(orderId).get();
|
|
1012
1033
|
if (!orderDoc.exists) {
|
|
1013
|
-
return
|
|
1034
|
+
return json({ error: "Orden no encontrada" }, { status: 404 });
|
|
1014
1035
|
}
|
|
1015
1036
|
const orderData = orderDoc.data() ?? {};
|
|
1016
1037
|
if (orderData.status === "paid") {
|
|
1017
|
-
return
|
|
1038
|
+
return json({
|
|
1018
1039
|
success: true,
|
|
1019
1040
|
transactionId: orderData.paymentTransactionId,
|
|
1020
1041
|
authorizationCode: orderData.authorizationCode || null,
|
|
@@ -1025,10 +1046,10 @@ function create3dsCompleteHandler(deps) {
|
|
|
1025
1046
|
logger.log(
|
|
1026
1047
|
`[3ds-complete] verify already called for order ${orderId}, returning current state`
|
|
1027
1048
|
);
|
|
1028
|
-
return
|
|
1049
|
+
return json({ error: "Pago ya procesado" }, { status: 409 });
|
|
1029
1050
|
}
|
|
1030
1051
|
if (orderData.status !== "3ds-pending" && orderData.status !== "otp-pending") {
|
|
1031
|
-
return
|
|
1052
|
+
return json(
|
|
1032
1053
|
{ error: "Esta orden ya fue procesada" },
|
|
1033
1054
|
{ status: 409 }
|
|
1034
1055
|
);
|
|
@@ -1055,29 +1076,29 @@ function create3dsCompleteHandler(deps) {
|
|
|
1055
1076
|
}).catch(() => {
|
|
1056
1077
|
});
|
|
1057
1078
|
}
|
|
1058
|
-
return
|
|
1079
|
+
return json({ error: msg }, { status: 400 });
|
|
1059
1080
|
}
|
|
1060
1081
|
const transactionId = orderData.nuveiTransactionId || bodyTxId;
|
|
1061
1082
|
if (!transactionId) {
|
|
1062
|
-
return
|
|
1083
|
+
return json(
|
|
1063
1084
|
{ error: "No se encontr\xF3 el ID de transacci\xF3n para verificar" },
|
|
1064
1085
|
{ status: 400 }
|
|
1065
1086
|
);
|
|
1066
1087
|
}
|
|
1067
1088
|
if (type === "AUTHENTICATION_CONTINUE" && !orderData.threeDSCres && !orderData.isDeviceFingerprint) {
|
|
1068
1089
|
logger.log(`[3ds-complete] No CRES yet for order ${orderId} \u2014 still pending`);
|
|
1069
|
-
return
|
|
1090
|
+
return json({ stillPending: true });
|
|
1070
1091
|
}
|
|
1071
1092
|
const actualType = type === "AUTHENTICATION_CONTINUE" && orderData.threeDSCres ? "BY_CRES" : type;
|
|
1072
1093
|
const cresValue = actualType === "BY_CRES" ? orderData.threeDSCres : void 0;
|
|
1073
1094
|
if (actualType === "BY_CRES" && !cresValue) {
|
|
1074
|
-
return
|
|
1095
|
+
return json(
|
|
1075
1096
|
{ error: "No se encontr\xF3 el valor de autenticaci\xF3n 3DS (cres)" },
|
|
1076
1097
|
{ status: 400 }
|
|
1077
1098
|
);
|
|
1078
1099
|
}
|
|
1079
1100
|
if (type === "BY_OTP" && !otpCode) {
|
|
1080
|
-
return
|
|
1101
|
+
return json(
|
|
1081
1102
|
{ error: "Debes ingresar el c\xF3digo OTP" },
|
|
1082
1103
|
{ status: 400 }
|
|
1083
1104
|
);
|
|
@@ -1108,7 +1129,7 @@ function create3dsCompleteHandler(deps) {
|
|
|
1108
1129
|
logger.log(
|
|
1109
1130
|
`[3ds-complete] Still pending: txStatus=${txStatus} detail=${txStatusDetail}. Polling continues.`
|
|
1110
1131
|
);
|
|
1111
|
-
return
|
|
1132
|
+
return json({ stillPending: true });
|
|
1112
1133
|
}
|
|
1113
1134
|
if (isSuccess) {
|
|
1114
1135
|
const hasValidAuthCode = typeof txAuthCode === "string" && txAuthCode.trim().length > 0 && txAuthCode !== "null";
|
|
@@ -1194,7 +1215,7 @@ function create3dsCompleteHandler(deps) {
|
|
|
1194
1215
|
(err) => logger.error("[3ds-complete] Failed to delete card after payment:", err)
|
|
1195
1216
|
);
|
|
1196
1217
|
}
|
|
1197
|
-
return
|
|
1218
|
+
return json({
|
|
1198
1219
|
success: true,
|
|
1199
1220
|
transactionId: txId,
|
|
1200
1221
|
authorizationCode: hasValidAuthCode ? txAuthCode : null,
|
|
@@ -1212,7 +1233,7 @@ function create3dsCompleteHandler(deps) {
|
|
|
1212
1233
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
1213
1234
|
threeDSCres: firestore.FieldValue.delete()
|
|
1214
1235
|
});
|
|
1215
|
-
return
|
|
1236
|
+
return json({
|
|
1216
1237
|
challenge: true,
|
|
1217
1238
|
challengeHtml,
|
|
1218
1239
|
isDeviceFingerprint: false,
|
|
@@ -1242,48 +1263,50 @@ function create3dsCompleteHandler(deps) {
|
|
|
1242
1263
|
}).catch(() => {
|
|
1243
1264
|
});
|
|
1244
1265
|
}
|
|
1245
|
-
return
|
|
1266
|
+
return json(
|
|
1246
1267
|
{ error: "Pago rechazado tras autenticaci\xF3n 3DS." },
|
|
1247
1268
|
{ status: 400 }
|
|
1248
1269
|
);
|
|
1249
1270
|
} catch (error) {
|
|
1250
1271
|
logger.error("3DS complete error:", error);
|
|
1251
|
-
return
|
|
1272
|
+
return json(
|
|
1252
1273
|
{ error: "Error interno del servidor" },
|
|
1253
1274
|
{ status: 500 }
|
|
1254
1275
|
);
|
|
1255
1276
|
}
|
|
1256
1277
|
};
|
|
1257
1278
|
}
|
|
1279
|
+
|
|
1280
|
+
// src/handlers/3ds-timeout.ts
|
|
1258
1281
|
function create3dsTimeoutHandler(deps) {
|
|
1259
1282
|
const logger = deps.logger ?? console;
|
|
1260
1283
|
const { db, auth } = deps.firebase;
|
|
1261
1284
|
return async function POST(request) {
|
|
1262
|
-
const sessionCookie = request
|
|
1285
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1263
1286
|
if (!sessionCookie || !auth) {
|
|
1264
|
-
return
|
|
1287
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1265
1288
|
}
|
|
1266
1289
|
let decodedToken;
|
|
1267
1290
|
try {
|
|
1268
1291
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
1269
1292
|
} catch {
|
|
1270
|
-
return
|
|
1293
|
+
return json({ error: "Sesion invalida" }, { status: 401 });
|
|
1271
1294
|
}
|
|
1272
1295
|
const { orderId } = await request.json();
|
|
1273
1296
|
if (!orderId || typeof orderId !== "string") {
|
|
1274
|
-
return
|
|
1297
|
+
return json({ error: "orderId requerido" }, { status: 400 });
|
|
1275
1298
|
}
|
|
1276
1299
|
const orderRef = db.collection("orders").doc(orderId);
|
|
1277
1300
|
const orderDoc = await orderRef.get();
|
|
1278
1301
|
if (!orderDoc.exists) {
|
|
1279
|
-
return
|
|
1302
|
+
return json({ error: "Orden no encontrada" }, { status: 404 });
|
|
1280
1303
|
}
|
|
1281
1304
|
const orderData = orderDoc.data() ?? {};
|
|
1282
1305
|
if (decodedToken.uid !== orderData.userId) {
|
|
1283
|
-
return
|
|
1306
|
+
return json({ error: "Usuario no coincide" }, { status: 403 });
|
|
1284
1307
|
}
|
|
1285
1308
|
if (orderData.status !== "3ds-pending") {
|
|
1286
|
-
return
|
|
1309
|
+
return json({ alreadyResolved: true });
|
|
1287
1310
|
}
|
|
1288
1311
|
await orderRef.update({
|
|
1289
1312
|
status: "failed",
|
|
@@ -1304,48 +1327,50 @@ function create3dsTimeoutHandler(deps) {
|
|
|
1304
1327
|
...retryUrl ? { retryUrl } : {}
|
|
1305
1328
|
}).catch((err) => logger.error("[3ds-timeout] Failed to send email:", err));
|
|
1306
1329
|
}
|
|
1307
|
-
return
|
|
1330
|
+
return json({ ok: true });
|
|
1308
1331
|
};
|
|
1309
1332
|
}
|
|
1333
|
+
|
|
1334
|
+
// src/handlers/refund.ts
|
|
1310
1335
|
function createRefundHandler(deps) {
|
|
1311
1336
|
const logger = deps.logger ?? console;
|
|
1312
1337
|
const { db, auth } = deps.firebase;
|
|
1313
1338
|
return async function POST(request) {
|
|
1314
1339
|
try {
|
|
1315
|
-
const sessionCookie = request
|
|
1340
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1316
1341
|
if (!sessionCookie || !auth) {
|
|
1317
|
-
return
|
|
1342
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1318
1343
|
}
|
|
1319
1344
|
let decodedToken;
|
|
1320
1345
|
try {
|
|
1321
1346
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
1322
1347
|
} catch {
|
|
1323
|
-
return
|
|
1348
|
+
return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
|
|
1324
1349
|
}
|
|
1325
1350
|
const { orderId } = await request.json();
|
|
1326
1351
|
if (!orderId) {
|
|
1327
|
-
return
|
|
1352
|
+
return json({ error: "orderId requerido" }, { status: 400 });
|
|
1328
1353
|
}
|
|
1329
1354
|
const orderDoc = await db.collection("orders").doc(orderId).get();
|
|
1330
1355
|
if (!orderDoc.exists) {
|
|
1331
|
-
return
|
|
1356
|
+
return json({ error: "Orden no encontrada" }, { status: 404 });
|
|
1332
1357
|
}
|
|
1333
1358
|
const order = orderDoc.data() ?? {};
|
|
1334
1359
|
if (order.userId !== decodedToken.uid) {
|
|
1335
|
-
return
|
|
1360
|
+
return json(
|
|
1336
1361
|
{ error: "No autorizado para esta orden" },
|
|
1337
1362
|
{ status: 403 }
|
|
1338
1363
|
);
|
|
1339
1364
|
}
|
|
1340
1365
|
if (order.status !== "paid") {
|
|
1341
|
-
return
|
|
1366
|
+
return json(
|
|
1342
1367
|
{ error: "Solo se pueden reembolsar \xF3rdenes pagadas" },
|
|
1343
1368
|
{ status: 400 }
|
|
1344
1369
|
);
|
|
1345
1370
|
}
|
|
1346
1371
|
const paymentTransactionId = order.paymentTransactionId;
|
|
1347
1372
|
if (!paymentTransactionId) {
|
|
1348
|
-
return
|
|
1373
|
+
return json(
|
|
1349
1374
|
{ error: "No se encontr\xF3 ID de transacci\xF3n" },
|
|
1350
1375
|
{ status: 400 }
|
|
1351
1376
|
);
|
|
@@ -1367,15 +1392,15 @@ function createRefundHandler(deps) {
|
|
|
1367
1392
|
);
|
|
1368
1393
|
}
|
|
1369
1394
|
}
|
|
1370
|
-
return
|
|
1395
|
+
return json({ success: true, detail: result.detail });
|
|
1371
1396
|
}
|
|
1372
|
-
return
|
|
1397
|
+
return json(
|
|
1373
1398
|
{ error: result.detail || "Error al procesar reembolso" },
|
|
1374
1399
|
{ status: 400 }
|
|
1375
1400
|
);
|
|
1376
1401
|
} catch (error) {
|
|
1377
1402
|
logger.error("Refund error:", error);
|
|
1378
|
-
return
|
|
1403
|
+
return json(
|
|
1379
1404
|
{ error: "Error interno del servidor" },
|
|
1380
1405
|
{ status: 500 }
|
|
1381
1406
|
);
|
|
@@ -1386,20 +1411,20 @@ function createInitCheckoutHandler(deps) {
|
|
|
1386
1411
|
const logger = deps.logger ?? console;
|
|
1387
1412
|
const { auth } = deps.firebase;
|
|
1388
1413
|
return async function POST(request) {
|
|
1389
|
-
const sessionCookie = request
|
|
1414
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1390
1415
|
if (!sessionCookie || !auth) {
|
|
1391
|
-
return
|
|
1416
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1392
1417
|
}
|
|
1393
1418
|
let decodedToken;
|
|
1394
1419
|
try {
|
|
1395
1420
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
1396
1421
|
} catch {
|
|
1397
|
-
return
|
|
1422
|
+
return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
|
|
1398
1423
|
}
|
|
1399
1424
|
try {
|
|
1400
1425
|
const { amount, vat, description, devReference } = await request.json();
|
|
1401
1426
|
if (!amount || !devReference) {
|
|
1402
|
-
return
|
|
1427
|
+
return json(
|
|
1403
1428
|
{ error: "amount y devReference son requeridos" },
|
|
1404
1429
|
{ status: 400 }
|
|
1405
1430
|
);
|
|
@@ -1427,36 +1452,38 @@ function createInitCheckoutHandler(deps) {
|
|
|
1427
1452
|
}
|
|
1428
1453
|
});
|
|
1429
1454
|
if (result.error) {
|
|
1430
|
-
return
|
|
1455
|
+
return json(
|
|
1431
1456
|
{ error: result.error.description || "Error al inicializar checkout" },
|
|
1432
1457
|
{ status: 400 }
|
|
1433
1458
|
);
|
|
1434
1459
|
}
|
|
1435
1460
|
if (!result.reference) {
|
|
1436
|
-
return
|
|
1461
|
+
return json(
|
|
1437
1462
|
{ error: "No se recibi\xF3 referencia de checkout" },
|
|
1438
1463
|
{ status: 500 }
|
|
1439
1464
|
);
|
|
1440
1465
|
}
|
|
1441
|
-
return
|
|
1466
|
+
return json({
|
|
1442
1467
|
reference: result.reference,
|
|
1443
1468
|
checkoutUrl: result.checkout_url
|
|
1444
1469
|
});
|
|
1445
1470
|
} catch (error) {
|
|
1446
1471
|
logger.error("Init checkout error:", error);
|
|
1447
|
-
return
|
|
1472
|
+
return json(
|
|
1448
1473
|
{ error: "Error al inicializar checkout" },
|
|
1449
1474
|
{ status: 500 }
|
|
1450
1475
|
);
|
|
1451
1476
|
}
|
|
1452
1477
|
};
|
|
1453
1478
|
}
|
|
1479
|
+
|
|
1480
|
+
// src/handlers/cards.ts
|
|
1454
1481
|
function createCardsHandler(deps) {
|
|
1455
1482
|
const logger = deps.logger ?? console;
|
|
1456
1483
|
const { db, auth } = deps.firebase;
|
|
1457
1484
|
const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
|
|
1458
1485
|
async function verifySession(request) {
|
|
1459
|
-
const sessionCookie = request
|
|
1486
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1460
1487
|
if (!sessionCookie || !auth) return null;
|
|
1461
1488
|
try {
|
|
1462
1489
|
return await auth.verifySessionCookie(sessionCookie, true);
|
|
@@ -1467,7 +1494,7 @@ function createCardsHandler(deps) {
|
|
|
1467
1494
|
const GET = async function GET2(request) {
|
|
1468
1495
|
const decoded = await verifySession(request);
|
|
1469
1496
|
if (!decoded) {
|
|
1470
|
-
return
|
|
1497
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1471
1498
|
}
|
|
1472
1499
|
try {
|
|
1473
1500
|
const result = await listCards(decoded.uid);
|
|
@@ -1491,12 +1518,12 @@ function createCardsHandler(deps) {
|
|
|
1491
1518
|
const enrichedCards = cards.map(
|
|
1492
1519
|
(c) => c.status === "review" && verifiedTokens.has(c.token) ? { ...c, status: "valid" } : c
|
|
1493
1520
|
);
|
|
1494
|
-
return
|
|
1521
|
+
return json({ cards: enrichedCards });
|
|
1495
1522
|
}
|
|
1496
|
-
return
|
|
1523
|
+
return json({ cards });
|
|
1497
1524
|
} catch (error) {
|
|
1498
1525
|
logger.error("Error listing cards:", error);
|
|
1499
|
-
return
|
|
1526
|
+
return json(
|
|
1500
1527
|
{ error: "Error al obtener tarjetas" },
|
|
1501
1528
|
{ status: 500 }
|
|
1502
1529
|
);
|
|
@@ -1505,12 +1532,12 @@ function createCardsHandler(deps) {
|
|
|
1505
1532
|
const DELETE = async function DELETE2(request) {
|
|
1506
1533
|
const decoded = await verifySession(request);
|
|
1507
1534
|
if (!decoded) {
|
|
1508
|
-
return
|
|
1535
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1509
1536
|
}
|
|
1510
1537
|
try {
|
|
1511
1538
|
const { token } = await request.json();
|
|
1512
1539
|
if (!token) {
|
|
1513
|
-
return
|
|
1540
|
+
return json(
|
|
1514
1541
|
{ error: "Token de tarjeta requerido" },
|
|
1515
1542
|
{ status: 400 }
|
|
1516
1543
|
);
|
|
@@ -1520,7 +1547,7 @@ function createCardsHandler(deps) {
|
|
|
1520
1547
|
const resultWithError = result;
|
|
1521
1548
|
if (resultWithError.error) {
|
|
1522
1549
|
logger.error("[cards/DELETE] Nuvei delete failed:", JSON.stringify(result));
|
|
1523
|
-
return
|
|
1550
|
+
return json(
|
|
1524
1551
|
{ error: "No se pudo eliminar la tarjeta en Nuvei", detail: result },
|
|
1525
1552
|
{ status: 400 }
|
|
1526
1553
|
);
|
|
@@ -1531,10 +1558,10 @@ function createCardsHandler(deps) {
|
|
|
1531
1558
|
} catch {
|
|
1532
1559
|
}
|
|
1533
1560
|
}
|
|
1534
|
-
return
|
|
1561
|
+
return json(result);
|
|
1535
1562
|
} catch (error) {
|
|
1536
1563
|
logger.error("Error deleting card:", error);
|
|
1537
|
-
return
|
|
1564
|
+
return json(
|
|
1538
1565
|
{ error: "Error al eliminar tarjeta" },
|
|
1539
1566
|
{ status: 500 }
|
|
1540
1567
|
);
|
|
@@ -1542,25 +1569,27 @@ function createCardsHandler(deps) {
|
|
|
1542
1569
|
};
|
|
1543
1570
|
return { GET, DELETE };
|
|
1544
1571
|
}
|
|
1572
|
+
|
|
1573
|
+
// src/handlers/verify.ts
|
|
1545
1574
|
function createVerifyHandler(deps) {
|
|
1546
1575
|
const logger = deps.logger ?? console;
|
|
1547
1576
|
const { db, auth } = deps.firebase;
|
|
1548
1577
|
const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
|
|
1549
1578
|
return async function POST(request) {
|
|
1550
|
-
const sessionCookie = request
|
|
1579
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1551
1580
|
if (!sessionCookie || !auth) {
|
|
1552
|
-
return
|
|
1581
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1553
1582
|
}
|
|
1554
1583
|
let decodedToken;
|
|
1555
1584
|
try {
|
|
1556
1585
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
1557
1586
|
} catch {
|
|
1558
|
-
return
|
|
1587
|
+
return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
|
|
1559
1588
|
}
|
|
1560
1589
|
try {
|
|
1561
1590
|
const { cardToken, transactionReference, value } = await request.json();
|
|
1562
1591
|
if (!cardToken && !transactionReference || !value) {
|
|
1563
|
-
return
|
|
1592
|
+
return json(
|
|
1564
1593
|
{ error: "transactionReference y value son requeridos" },
|
|
1565
1594
|
{ status: 400 }
|
|
1566
1595
|
);
|
|
@@ -1571,7 +1600,7 @@ function createVerifyHandler(deps) {
|
|
|
1571
1600
|
value
|
|
1572
1601
|
});
|
|
1573
1602
|
if (result.error) {
|
|
1574
|
-
return
|
|
1603
|
+
return json(
|
|
1575
1604
|
{ error: result.error.description || "Error de verificaci\xF3n" },
|
|
1576
1605
|
{ status: 400 }
|
|
1577
1606
|
);
|
|
@@ -1583,41 +1612,43 @@ function createVerifyHandler(deps) {
|
|
|
1583
1612
|
logger.error("Failed to persist verified card:", err);
|
|
1584
1613
|
}
|
|
1585
1614
|
}
|
|
1586
|
-
return
|
|
1615
|
+
return json({
|
|
1587
1616
|
success: true,
|
|
1588
1617
|
transaction: result.transaction
|
|
1589
1618
|
});
|
|
1590
1619
|
} catch (error) {
|
|
1591
1620
|
logger.error("Card verify error:", error);
|
|
1592
|
-
return
|
|
1621
|
+
return json(
|
|
1593
1622
|
{ error: "Error al verificar tarjeta" },
|
|
1594
1623
|
{ status: 500 }
|
|
1595
1624
|
);
|
|
1596
1625
|
}
|
|
1597
1626
|
};
|
|
1598
1627
|
}
|
|
1628
|
+
|
|
1629
|
+
// src/handlers/test-charge.ts
|
|
1599
1630
|
function createTestChargeHandler(deps) {
|
|
1600
1631
|
const logger = deps.logger ?? console;
|
|
1601
1632
|
const { auth } = deps.firebase;
|
|
1602
1633
|
const defaultDescription = deps.defaultDescription ?? "Test charge";
|
|
1603
1634
|
return async function POST(request) {
|
|
1604
1635
|
if (process.env.NUVEI_ENV === "prod") {
|
|
1605
|
-
return
|
|
1636
|
+
return json({ error: "Not available" }, { status: 404 });
|
|
1606
1637
|
}
|
|
1607
|
-
const sessionCookie = request
|
|
1638
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1608
1639
|
if (!sessionCookie || !auth) {
|
|
1609
|
-
return
|
|
1640
|
+
return json({ error: "No autorizado" }, { status: 401 });
|
|
1610
1641
|
}
|
|
1611
1642
|
let decodedToken;
|
|
1612
1643
|
try {
|
|
1613
1644
|
decodedToken = await auth.verifySessionCookie(sessionCookie, true);
|
|
1614
1645
|
} catch {
|
|
1615
|
-
return
|
|
1646
|
+
return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
|
|
1616
1647
|
}
|
|
1617
1648
|
try {
|
|
1618
1649
|
const { token, amount, vat, description, devReference } = await request.json();
|
|
1619
1650
|
if (!token || !amount || !devReference) {
|
|
1620
|
-
return
|
|
1651
|
+
return json(
|
|
1621
1652
|
{ error: "token, amount y devReference son requeridos" },
|
|
1622
1653
|
{ status: 400 }
|
|
1623
1654
|
);
|
|
@@ -1633,13 +1664,13 @@ function createTestChargeHandler(deps) {
|
|
|
1633
1664
|
});
|
|
1634
1665
|
logger.log("Test charge result:", JSON.stringify(result, null, 2));
|
|
1635
1666
|
if (result.transaction && result.transaction.status === "success" && result.transaction.status_detail === 3) {
|
|
1636
|
-
return
|
|
1667
|
+
return json({
|
|
1637
1668
|
success: true,
|
|
1638
1669
|
transaction: result.transaction,
|
|
1639
1670
|
card: result.card
|
|
1640
1671
|
});
|
|
1641
1672
|
}
|
|
1642
|
-
return
|
|
1673
|
+
return json(
|
|
1643
1674
|
{
|
|
1644
1675
|
success: false,
|
|
1645
1676
|
error: result.transaction?.message || result.error?.description || "Pago rechazado",
|
|
@@ -1649,7 +1680,7 @@ function createTestChargeHandler(deps) {
|
|
|
1649
1680
|
);
|
|
1650
1681
|
} catch (error) {
|
|
1651
1682
|
logger.error("Test charge error:", error);
|
|
1652
|
-
return
|
|
1683
|
+
return json(
|
|
1653
1684
|
{ error: "Error interno del servidor" },
|
|
1654
1685
|
{ status: 500 }
|
|
1655
1686
|
);
|
|
@@ -1657,12 +1688,64 @@ function createTestChargeHandler(deps) {
|
|
|
1657
1688
|
};
|
|
1658
1689
|
}
|
|
1659
1690
|
|
|
1691
|
+
// src/handlers/proxy.ts
|
|
1692
|
+
function createNuveiProxyHandler(deps = {}) {
|
|
1693
|
+
const logger = deps.logger ?? console;
|
|
1694
|
+
return async function POST(request) {
|
|
1695
|
+
if (request.method !== "POST") {
|
|
1696
|
+
return json({ error: "Method not allowed" }, { status: 405 });
|
|
1697
|
+
}
|
|
1698
|
+
let parsed;
|
|
1699
|
+
try {
|
|
1700
|
+
parsed = await request.json();
|
|
1701
|
+
} catch {
|
|
1702
|
+
return json({ error: "Invalid JSON body" }, { status: 400 });
|
|
1703
|
+
}
|
|
1704
|
+
const { path, method, body: nuveiBody } = parsed;
|
|
1705
|
+
const authToken = request.headers.get("x-nuvei-auth-token");
|
|
1706
|
+
if (!path || !method || !authToken) {
|
|
1707
|
+
return json(
|
|
1708
|
+
{ error: "Missing path, method, or x-nuvei-auth-token header" },
|
|
1709
|
+
{ status: 400 }
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
const env = request.headers.get("x-nuvei-env") || "prod";
|
|
1713
|
+
const baseUrl = env === "prod" ? "https://ccapi.paymentez.com" : "https://ccapi-stg.paymentez.com";
|
|
1714
|
+
const url = `${baseUrl}${path}`;
|
|
1715
|
+
try {
|
|
1716
|
+
const options = {
|
|
1717
|
+
method,
|
|
1718
|
+
headers: {
|
|
1719
|
+
"Content-Type": "application/json",
|
|
1720
|
+
"Auth-Token": authToken
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
if (nuveiBody && method === "POST") {
|
|
1724
|
+
options.body = JSON.stringify(nuveiBody);
|
|
1725
|
+
}
|
|
1726
|
+
const response = await fetch(url, options);
|
|
1727
|
+
const responseBody = await response.text();
|
|
1728
|
+
return new Response(responseBody, {
|
|
1729
|
+
status: response.status,
|
|
1730
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
1731
|
+
});
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
logger.error("[nuveiProxy] Error:", err);
|
|
1734
|
+
return json(
|
|
1735
|
+
{ error: "Proxy error: " + (err instanceof Error ? err.message : String(err)) },
|
|
1736
|
+
{ status: 500 }
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1660
1742
|
exports.create3dsCallbackHandler = create3dsCallbackHandler;
|
|
1661
1743
|
exports.create3dsCompleteHandler = create3dsCompleteHandler;
|
|
1662
1744
|
exports.create3dsTimeoutHandler = create3dsTimeoutHandler;
|
|
1663
1745
|
exports.createCardsHandler = createCardsHandler;
|
|
1664
1746
|
exports.createChargeHandler = createChargeHandler;
|
|
1665
1747
|
exports.createInitCheckoutHandler = createInitCheckoutHandler;
|
|
1748
|
+
exports.createNuveiProxyHandler = createNuveiProxyHandler;
|
|
1666
1749
|
exports.createRefundHandler = createRefundHandler;
|
|
1667
1750
|
exports.createTestChargeHandler = createTestChargeHandler;
|
|
1668
1751
|
exports.createVerifyHandler = createVerifyHandler;
|