@pandait.tech/payment-nuvei 0.1.1 → 0.2.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.
@@ -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/handlers/charge.ts
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.cookies.get("__session")?.value;
234
+ const sessionCookie = getCookie(request, "__session");
216
235
  if (!sessionCookie || !auth) {
217
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
298
+ return json({ error: "Datos incompletos" }, { status: 400 });
280
299
  }
281
300
  if (decodedToken.uid !== userId) {
282
- return server.NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
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 server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json({
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 server.NextResponse.json({
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 server.NextResponse.json({
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 server.NextResponse.json({
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 server.NextResponse.json({
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 server.NextResponse.json({ error: threeDSErrorMsg }, { status: 400 });
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 server.NextResponse.json({ error: failedErrorMsg }, { status: 400 });
666
+ return json({ error: failedErrorMsg }, { status: 400 });
648
667
  } catch (error) {
649
668
  logger.error("Payment charge error:", error);
650
- return server.NextResponse.json(
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 server.NextResponse.json({ error: "Payload inv\xE1lido" }, { status: 400 });
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 server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
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 server.NextResponse.json({ received: true, verifiedFromWebhook: true });
819
+ return json({ received: true, verifiedFromWebhook: true });
801
820
  }
802
821
  } catch (err) {
803
822
  if (err.message !== "ALREADY_CALLED") {
@@ -891,17 +910,55 @@ function createWebhookHandler(deps) {
891
910
  );
892
911
  }
893
912
  }
894
- return server.NextResponse.json({ received: true });
913
+ return json({ received: true });
895
914
  } catch (error) {
896
915
  logger.error("Webhook processing error:", error);
897
- return server.NextResponse.json(
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
925
+ function pickCres(src) {
926
+ for (const key of Object.keys(src)) {
927
+ const k = key.toLowerCase();
928
+ if (k === "cres" || k === "cres_value" || k === "cresvalue" || k === "value" || k === "param.value" || k === "paramvalue") {
929
+ const v = src[key];
930
+ if (typeof v === "string" && v.length > 0) return v;
931
+ }
932
+ }
933
+ return src.cres || src.CRes || src.CRES || src.value || "";
934
+ }
935
+ function transStatusFromCres(cres, logger) {
936
+ try {
937
+ const base64 = cres.replace(/-/g, "+").replace(/_/g, "/");
938
+ const pad = base64.length % 4;
939
+ const padded = pad ? base64 + "=".repeat(4 - pad) : base64;
940
+ const decoded = JSON.parse(
941
+ Buffer.from(padded, "base64").toString("utf-8")
942
+ );
943
+ if (decoded && typeof decoded.transStatus === "string") {
944
+ return decoded.transStatus;
945
+ }
946
+ } catch (e) {
947
+ logger.warn(
948
+ "[3ds-callback] Could not decode CRES payload:",
949
+ e instanceof Error ? e.message : e
950
+ );
951
+ }
952
+ return null;
953
+ }
954
+ function paramsToObject(params) {
955
+ const obj = {};
956
+ for (const [k, v] of params.entries()) obj[k] = v;
957
+ return obj;
958
+ }
904
959
  function buildCompletionPage(orderId, transStatus) {
960
+ const safeOrderId = String(orderId).replace(/[^a-zA-Z0-9_-]/g, "");
961
+ const safeTransStatus = String(transStatus).replace(/[^a-zA-Z0-9]/g, "");
905
962
  const html = `<!DOCTYPE html>
906
963
  <html><head><meta charset="utf-8"><title>3DS Verification</title></head>
907
964
  <body>
@@ -909,8 +966,8 @@ function buildCompletionPage(orderId, transStatus) {
909
966
  (function() {
910
967
  var message = {
911
968
  type: "3DS_COMPLETE",
912
- orderId: "${orderId}",
913
- transStatus: "${transStatus}"
969
+ orderId: "${safeOrderId}",
970
+ transStatus: "${safeTransStatus}"
914
971
  };
915
972
  try {
916
973
  if (window.parent && window.parent !== window) {
@@ -925,7 +982,7 @@ function buildCompletionPage(orderId, transStatus) {
925
982
  Verificando autenticaci&oacute;n...
926
983
  </p>
927
984
  </body></html>`;
928
- return new server.NextResponse(html, {
985
+ return new Response(html, {
929
986
  status: 200,
930
987
  headers: { "Content-Type": "text/html; charset=utf-8" }
931
988
  });
@@ -933,46 +990,68 @@ Verificando autenticaci&oacute;n...
933
990
  function create3dsCallbackHandler(deps) {
934
991
  const logger = deps.logger ?? console;
935
992
  const { db } = deps.firebase;
936
- async function storeCres(orderId, cres) {
937
- if (orderId && cres) {
938
- try {
939
- await db.collection("orders").doc(orderId).update({
940
- threeDSCres: cres,
941
- updatedAt: /* @__PURE__ */ new Date()
942
- });
943
- } catch (err) {
944
- logger.error("Failed to store cres on order:", err);
945
- }
993
+ async function persist(orderId, cres, transStatus) {
994
+ if (!orderId) return;
995
+ try {
996
+ const ref = db.collection("orders").doc(orderId);
997
+ const snap = await ref.get();
998
+ const status = snap.data()?.status;
999
+ if (!snap.exists || status !== "3ds-pending") {
1000
+ logger.warn(
1001
+ "[3ds-callback] rejected: order not in 3ds-pending state",
1002
+ { orderId, status }
1003
+ );
1004
+ return;
1005
+ }
1006
+ const update = { updatedAt: /* @__PURE__ */ new Date() };
1007
+ if (cres) update.threeDSCres = cres;
1008
+ if (transStatus) update.threeDSTransStatus = transStatus;
1009
+ await ref.update(update);
1010
+ } catch (err) {
1011
+ logger.error("[3ds-callback] failed to store cres on order:", err);
1012
+ }
1013
+ }
1014
+ function resolveTransStatus(cres, fallback) {
1015
+ if (cres) {
1016
+ const fromCres = transStatusFromCres(cres, logger);
1017
+ if (fromCres) return fromCres;
946
1018
  }
1019
+ return fallback;
947
1020
  }
948
1021
  const POST = async function POST2(request) {
949
- const { searchParams } = new URL(request.url);
950
- const orderId = searchParams.get("orderId") || "";
1022
+ const orderId = new URL(request.url).searchParams.get("orderId") || "";
951
1023
  let transStatus = "U";
952
1024
  let cres = "";
953
1025
  try {
954
1026
  const contentType = request.headers.get("content-type") || "";
1027
+ const raw = await request.text();
955
1028
  if (contentType.includes("application/x-www-form-urlencoded")) {
956
- const text = await request.text();
957
- const params = new URLSearchParams(text);
958
- transStatus = params.get("transStatus") || "U";
959
- cres = params.get("cres") || params.get("CRes") || "";
1029
+ const obj = paramsToObject(new URLSearchParams(raw));
1030
+ transStatus = obj.transStatus || obj.TransStatus || "U";
1031
+ cres = pickCres(obj);
960
1032
  } else {
961
- const body = await request.json();
962
- transStatus = body.transStatus || "U";
963
- cres = body.cres || body.CRes || "";
1033
+ try {
1034
+ const body = JSON.parse(raw);
1035
+ transStatus = body.transStatus || body.TransStatus || "U";
1036
+ cres = pickCres(body);
1037
+ } catch {
1038
+ }
1039
+ }
1040
+ if (!cres && raw) {
1041
+ cres = pickCres(paramsToObject(new URLSearchParams(raw)));
964
1042
  }
965
1043
  } catch {
966
1044
  }
967
- await storeCres(orderId, cres);
1045
+ transStatus = resolveTransStatus(cres, transStatus);
1046
+ await persist(orderId, cres, transStatus);
968
1047
  return buildCompletionPage(orderId, transStatus);
969
1048
  };
970
1049
  const GET = async function GET2(request) {
971
- const { searchParams } = new URL(request.url);
972
- const orderId = searchParams.get("orderId") || "";
973
- const transStatus = searchParams.get("transStatus") || "Y";
974
- const cres = searchParams.get("cres") || searchParams.get("CRes") || "";
975
- await storeCres(orderId, cres);
1050
+ const params = new URL(request.url).searchParams;
1051
+ const orderId = params.get("orderId") || "";
1052
+ const cres = params.get("cres") || params.get("CRes") || params.get("value") || "";
1053
+ const transStatus = resolveTransStatus(cres, params.get("transStatus") || "Y");
1054
+ await persist(orderId, cres, transStatus);
976
1055
  return buildCompletionPage(orderId, transStatus);
977
1056
  };
978
1057
  return { POST, GET };
@@ -989,32 +1068,32 @@ function create3dsCompleteHandler(deps) {
989
1068
  const { db, auth } = deps.firebase;
990
1069
  return async function POST(request) {
991
1070
  try {
992
- const sessionCookie = request.cookies.get("__session")?.value;
1071
+ const sessionCookie = getCookie(request, "__session");
993
1072
  if (!sessionCookie || !auth) {
994
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1073
+ return json({ error: "No autorizado" }, { status: 401 });
995
1074
  }
996
1075
  let decodedToken;
997
1076
  try {
998
1077
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
999
1078
  } catch {
1000
- return server.NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
1079
+ return json({ error: "Sesion invalida" }, { status: 401 });
1001
1080
  }
1002
1081
  const rawBody = await request.json();
1003
1082
  const parsed = ThreeDSCompleteSchema.safeParse(rawBody);
1004
1083
  if (!parsed.success) {
1005
- return server.NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
1084
+ return json({ error: "Datos incompletos" }, { status: 400 });
1006
1085
  }
1007
1086
  const { orderId, userId, type, nuveiTransactionId: bodyTxId, otpCode } = parsed.data;
1008
1087
  if (decodedToken.uid !== userId) {
1009
- return server.NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1088
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1010
1089
  }
1011
1090
  const orderDoc = await db.collection("orders").doc(orderId).get();
1012
1091
  if (!orderDoc.exists) {
1013
- return server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1092
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1014
1093
  }
1015
1094
  const orderData = orderDoc.data() ?? {};
1016
1095
  if (orderData.status === "paid") {
1017
- return server.NextResponse.json({
1096
+ return json({
1018
1097
  success: true,
1019
1098
  transactionId: orderData.paymentTransactionId,
1020
1099
  authorizationCode: orderData.authorizationCode || null,
@@ -1025,10 +1104,10 @@ function create3dsCompleteHandler(deps) {
1025
1104
  logger.log(
1026
1105
  `[3ds-complete] verify already called for order ${orderId}, returning current state`
1027
1106
  );
1028
- return server.NextResponse.json({ error: "Pago ya procesado" }, { status: 409 });
1107
+ return json({ error: "Pago ya procesado" }, { status: 409 });
1029
1108
  }
1030
1109
  if (orderData.status !== "3ds-pending" && orderData.status !== "otp-pending") {
1031
- return server.NextResponse.json(
1110
+ return json(
1032
1111
  { error: "Esta orden ya fue procesada" },
1033
1112
  { status: 409 }
1034
1113
  );
@@ -1055,29 +1134,29 @@ function create3dsCompleteHandler(deps) {
1055
1134
  }).catch(() => {
1056
1135
  });
1057
1136
  }
1058
- return server.NextResponse.json({ error: msg }, { status: 400 });
1137
+ return json({ error: msg }, { status: 400 });
1059
1138
  }
1060
1139
  const transactionId = orderData.nuveiTransactionId || bodyTxId;
1061
1140
  if (!transactionId) {
1062
- return server.NextResponse.json(
1141
+ return json(
1063
1142
  { error: "No se encontr\xF3 el ID de transacci\xF3n para verificar" },
1064
1143
  { status: 400 }
1065
1144
  );
1066
1145
  }
1067
1146
  if (type === "AUTHENTICATION_CONTINUE" && !orderData.threeDSCres && !orderData.isDeviceFingerprint) {
1068
1147
  logger.log(`[3ds-complete] No CRES yet for order ${orderId} \u2014 still pending`);
1069
- return server.NextResponse.json({ stillPending: true });
1148
+ return json({ stillPending: true });
1070
1149
  }
1071
1150
  const actualType = type === "AUTHENTICATION_CONTINUE" && orderData.threeDSCres ? "BY_CRES" : type;
1072
1151
  const cresValue = actualType === "BY_CRES" ? orderData.threeDSCres : void 0;
1073
1152
  if (actualType === "BY_CRES" && !cresValue) {
1074
- return server.NextResponse.json(
1153
+ return json(
1075
1154
  { error: "No se encontr\xF3 el valor de autenticaci\xF3n 3DS (cres)" },
1076
1155
  { status: 400 }
1077
1156
  );
1078
1157
  }
1079
1158
  if (type === "BY_OTP" && !otpCode) {
1080
- return server.NextResponse.json(
1159
+ return json(
1081
1160
  { error: "Debes ingresar el c\xF3digo OTP" },
1082
1161
  { status: 400 }
1083
1162
  );
@@ -1108,7 +1187,7 @@ function create3dsCompleteHandler(deps) {
1108
1187
  logger.log(
1109
1188
  `[3ds-complete] Still pending: txStatus=${txStatus} detail=${txStatusDetail}. Polling continues.`
1110
1189
  );
1111
- return server.NextResponse.json({ stillPending: true });
1190
+ return json({ stillPending: true });
1112
1191
  }
1113
1192
  if (isSuccess) {
1114
1193
  const hasValidAuthCode = typeof txAuthCode === "string" && txAuthCode.trim().length > 0 && txAuthCode !== "null";
@@ -1194,7 +1273,7 @@ function create3dsCompleteHandler(deps) {
1194
1273
  (err) => logger.error("[3ds-complete] Failed to delete card after payment:", err)
1195
1274
  );
1196
1275
  }
1197
- return server.NextResponse.json({
1276
+ return json({
1198
1277
  success: true,
1199
1278
  transactionId: txId,
1200
1279
  authorizationCode: hasValidAuthCode ? txAuthCode : null,
@@ -1212,7 +1291,7 @@ function create3dsCompleteHandler(deps) {
1212
1291
  updatedAt: /* @__PURE__ */ new Date(),
1213
1292
  threeDSCres: firestore.FieldValue.delete()
1214
1293
  });
1215
- return server.NextResponse.json({
1294
+ return json({
1216
1295
  challenge: true,
1217
1296
  challengeHtml,
1218
1297
  isDeviceFingerprint: false,
@@ -1242,48 +1321,50 @@ function create3dsCompleteHandler(deps) {
1242
1321
  }).catch(() => {
1243
1322
  });
1244
1323
  }
1245
- return server.NextResponse.json(
1324
+ return json(
1246
1325
  { error: "Pago rechazado tras autenticaci\xF3n 3DS." },
1247
1326
  { status: 400 }
1248
1327
  );
1249
1328
  } catch (error) {
1250
1329
  logger.error("3DS complete error:", error);
1251
- return server.NextResponse.json(
1330
+ return json(
1252
1331
  { error: "Error interno del servidor" },
1253
1332
  { status: 500 }
1254
1333
  );
1255
1334
  }
1256
1335
  };
1257
1336
  }
1337
+
1338
+ // src/handlers/3ds-timeout.ts
1258
1339
  function create3dsTimeoutHandler(deps) {
1259
1340
  const logger = deps.logger ?? console;
1260
1341
  const { db, auth } = deps.firebase;
1261
1342
  return async function POST(request) {
1262
- const sessionCookie = request.cookies.get("__session")?.value;
1343
+ const sessionCookie = getCookie(request, "__session");
1263
1344
  if (!sessionCookie || !auth) {
1264
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1345
+ return json({ error: "No autorizado" }, { status: 401 });
1265
1346
  }
1266
1347
  let decodedToken;
1267
1348
  try {
1268
1349
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1269
1350
  } catch {
1270
- return server.NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
1351
+ return json({ error: "Sesion invalida" }, { status: 401 });
1271
1352
  }
1272
1353
  const { orderId } = await request.json();
1273
1354
  if (!orderId || typeof orderId !== "string") {
1274
- return server.NextResponse.json({ error: "orderId requerido" }, { status: 400 });
1355
+ return json({ error: "orderId requerido" }, { status: 400 });
1275
1356
  }
1276
1357
  const orderRef = db.collection("orders").doc(orderId);
1277
1358
  const orderDoc = await orderRef.get();
1278
1359
  if (!orderDoc.exists) {
1279
- return server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1360
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1280
1361
  }
1281
1362
  const orderData = orderDoc.data() ?? {};
1282
1363
  if (decodedToken.uid !== orderData.userId) {
1283
- return server.NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1364
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1284
1365
  }
1285
1366
  if (orderData.status !== "3ds-pending") {
1286
- return server.NextResponse.json({ alreadyResolved: true });
1367
+ return json({ alreadyResolved: true });
1287
1368
  }
1288
1369
  await orderRef.update({
1289
1370
  status: "failed",
@@ -1304,48 +1385,50 @@ function create3dsTimeoutHandler(deps) {
1304
1385
  ...retryUrl ? { retryUrl } : {}
1305
1386
  }).catch((err) => logger.error("[3ds-timeout] Failed to send email:", err));
1306
1387
  }
1307
- return server.NextResponse.json({ ok: true });
1388
+ return json({ ok: true });
1308
1389
  };
1309
1390
  }
1391
+
1392
+ // src/handlers/refund.ts
1310
1393
  function createRefundHandler(deps) {
1311
1394
  const logger = deps.logger ?? console;
1312
1395
  const { db, auth } = deps.firebase;
1313
1396
  return async function POST(request) {
1314
1397
  try {
1315
- const sessionCookie = request.cookies.get("__session")?.value;
1398
+ const sessionCookie = getCookie(request, "__session");
1316
1399
  if (!sessionCookie || !auth) {
1317
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1400
+ return json({ error: "No autorizado" }, { status: 401 });
1318
1401
  }
1319
1402
  let decodedToken;
1320
1403
  try {
1321
1404
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1322
1405
  } catch {
1323
- return server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1406
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1324
1407
  }
1325
1408
  const { orderId } = await request.json();
1326
1409
  if (!orderId) {
1327
- return server.NextResponse.json({ error: "orderId requerido" }, { status: 400 });
1410
+ return json({ error: "orderId requerido" }, { status: 400 });
1328
1411
  }
1329
1412
  const orderDoc = await db.collection("orders").doc(orderId).get();
1330
1413
  if (!orderDoc.exists) {
1331
- return server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1414
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1332
1415
  }
1333
1416
  const order = orderDoc.data() ?? {};
1334
1417
  if (order.userId !== decodedToken.uid) {
1335
- return server.NextResponse.json(
1418
+ return json(
1336
1419
  { error: "No autorizado para esta orden" },
1337
1420
  { status: 403 }
1338
1421
  );
1339
1422
  }
1340
1423
  if (order.status !== "paid") {
1341
- return server.NextResponse.json(
1424
+ return json(
1342
1425
  { error: "Solo se pueden reembolsar \xF3rdenes pagadas" },
1343
1426
  { status: 400 }
1344
1427
  );
1345
1428
  }
1346
1429
  const paymentTransactionId = order.paymentTransactionId;
1347
1430
  if (!paymentTransactionId) {
1348
- return server.NextResponse.json(
1431
+ return json(
1349
1432
  { error: "No se encontr\xF3 ID de transacci\xF3n" },
1350
1433
  { status: 400 }
1351
1434
  );
@@ -1367,15 +1450,15 @@ function createRefundHandler(deps) {
1367
1450
  );
1368
1451
  }
1369
1452
  }
1370
- return server.NextResponse.json({ success: true, detail: result.detail });
1453
+ return json({ success: true, detail: result.detail });
1371
1454
  }
1372
- return server.NextResponse.json(
1455
+ return json(
1373
1456
  { error: result.detail || "Error al procesar reembolso" },
1374
1457
  { status: 400 }
1375
1458
  );
1376
1459
  } catch (error) {
1377
1460
  logger.error("Refund error:", error);
1378
- return server.NextResponse.json(
1461
+ return json(
1379
1462
  { error: "Error interno del servidor" },
1380
1463
  { status: 500 }
1381
1464
  );
@@ -1386,20 +1469,20 @@ function createInitCheckoutHandler(deps) {
1386
1469
  const logger = deps.logger ?? console;
1387
1470
  const { auth } = deps.firebase;
1388
1471
  return async function POST(request) {
1389
- const sessionCookie = request.cookies.get("__session")?.value;
1472
+ const sessionCookie = getCookie(request, "__session");
1390
1473
  if (!sessionCookie || !auth) {
1391
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1474
+ return json({ error: "No autorizado" }, { status: 401 });
1392
1475
  }
1393
1476
  let decodedToken;
1394
1477
  try {
1395
1478
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1396
1479
  } catch {
1397
- return server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1480
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1398
1481
  }
1399
1482
  try {
1400
1483
  const { amount, vat, description, devReference } = await request.json();
1401
1484
  if (!amount || !devReference) {
1402
- return server.NextResponse.json(
1485
+ return json(
1403
1486
  { error: "amount y devReference son requeridos" },
1404
1487
  { status: 400 }
1405
1488
  );
@@ -1427,36 +1510,38 @@ function createInitCheckoutHandler(deps) {
1427
1510
  }
1428
1511
  });
1429
1512
  if (result.error) {
1430
- return server.NextResponse.json(
1513
+ return json(
1431
1514
  { error: result.error.description || "Error al inicializar checkout" },
1432
1515
  { status: 400 }
1433
1516
  );
1434
1517
  }
1435
1518
  if (!result.reference) {
1436
- return server.NextResponse.json(
1519
+ return json(
1437
1520
  { error: "No se recibi\xF3 referencia de checkout" },
1438
1521
  { status: 500 }
1439
1522
  );
1440
1523
  }
1441
- return server.NextResponse.json({
1524
+ return json({
1442
1525
  reference: result.reference,
1443
1526
  checkoutUrl: result.checkout_url
1444
1527
  });
1445
1528
  } catch (error) {
1446
1529
  logger.error("Init checkout error:", error);
1447
- return server.NextResponse.json(
1530
+ return json(
1448
1531
  { error: "Error al inicializar checkout" },
1449
1532
  { status: 500 }
1450
1533
  );
1451
1534
  }
1452
1535
  };
1453
1536
  }
1537
+
1538
+ // src/handlers/cards.ts
1454
1539
  function createCardsHandler(deps) {
1455
1540
  const logger = deps.logger ?? console;
1456
1541
  const { db, auth } = deps.firebase;
1457
1542
  const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
1458
1543
  async function verifySession(request) {
1459
- const sessionCookie = request.cookies.get("__session")?.value;
1544
+ const sessionCookie = getCookie(request, "__session");
1460
1545
  if (!sessionCookie || !auth) return null;
1461
1546
  try {
1462
1547
  return await auth.verifySessionCookie(sessionCookie, true);
@@ -1467,7 +1552,7 @@ function createCardsHandler(deps) {
1467
1552
  const GET = async function GET2(request) {
1468
1553
  const decoded = await verifySession(request);
1469
1554
  if (!decoded) {
1470
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1555
+ return json({ error: "No autorizado" }, { status: 401 });
1471
1556
  }
1472
1557
  try {
1473
1558
  const result = await listCards(decoded.uid);
@@ -1491,12 +1576,12 @@ function createCardsHandler(deps) {
1491
1576
  const enrichedCards = cards.map(
1492
1577
  (c) => c.status === "review" && verifiedTokens.has(c.token) ? { ...c, status: "valid" } : c
1493
1578
  );
1494
- return server.NextResponse.json({ cards: enrichedCards });
1579
+ return json({ cards: enrichedCards });
1495
1580
  }
1496
- return server.NextResponse.json({ cards });
1581
+ return json({ cards });
1497
1582
  } catch (error) {
1498
1583
  logger.error("Error listing cards:", error);
1499
- return server.NextResponse.json(
1584
+ return json(
1500
1585
  { error: "Error al obtener tarjetas" },
1501
1586
  { status: 500 }
1502
1587
  );
@@ -1505,12 +1590,12 @@ function createCardsHandler(deps) {
1505
1590
  const DELETE = async function DELETE2(request) {
1506
1591
  const decoded = await verifySession(request);
1507
1592
  if (!decoded) {
1508
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1593
+ return json({ error: "No autorizado" }, { status: 401 });
1509
1594
  }
1510
1595
  try {
1511
1596
  const { token } = await request.json();
1512
1597
  if (!token) {
1513
- return server.NextResponse.json(
1598
+ return json(
1514
1599
  { error: "Token de tarjeta requerido" },
1515
1600
  { status: 400 }
1516
1601
  );
@@ -1520,7 +1605,7 @@ function createCardsHandler(deps) {
1520
1605
  const resultWithError = result;
1521
1606
  if (resultWithError.error) {
1522
1607
  logger.error("[cards/DELETE] Nuvei delete failed:", JSON.stringify(result));
1523
- return server.NextResponse.json(
1608
+ return json(
1524
1609
  { error: "No se pudo eliminar la tarjeta en Nuvei", detail: result },
1525
1610
  { status: 400 }
1526
1611
  );
@@ -1531,10 +1616,10 @@ function createCardsHandler(deps) {
1531
1616
  } catch {
1532
1617
  }
1533
1618
  }
1534
- return server.NextResponse.json(result);
1619
+ return json(result);
1535
1620
  } catch (error) {
1536
1621
  logger.error("Error deleting card:", error);
1537
- return server.NextResponse.json(
1622
+ return json(
1538
1623
  { error: "Error al eliminar tarjeta" },
1539
1624
  { status: 500 }
1540
1625
  );
@@ -1542,25 +1627,27 @@ function createCardsHandler(deps) {
1542
1627
  };
1543
1628
  return { GET, DELETE };
1544
1629
  }
1630
+
1631
+ // src/handlers/verify.ts
1545
1632
  function createVerifyHandler(deps) {
1546
1633
  const logger = deps.logger ?? console;
1547
1634
  const { db, auth } = deps.firebase;
1548
1635
  const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
1549
1636
  return async function POST(request) {
1550
- const sessionCookie = request.cookies.get("__session")?.value;
1637
+ const sessionCookie = getCookie(request, "__session");
1551
1638
  if (!sessionCookie || !auth) {
1552
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1639
+ return json({ error: "No autorizado" }, { status: 401 });
1553
1640
  }
1554
1641
  let decodedToken;
1555
1642
  try {
1556
1643
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1557
1644
  } catch {
1558
- return server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1645
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1559
1646
  }
1560
1647
  try {
1561
1648
  const { cardToken, transactionReference, value } = await request.json();
1562
1649
  if (!cardToken && !transactionReference || !value) {
1563
- return server.NextResponse.json(
1650
+ return json(
1564
1651
  { error: "transactionReference y value son requeridos" },
1565
1652
  { status: 400 }
1566
1653
  );
@@ -1571,7 +1658,7 @@ function createVerifyHandler(deps) {
1571
1658
  value
1572
1659
  });
1573
1660
  if (result.error) {
1574
- return server.NextResponse.json(
1661
+ return json(
1575
1662
  { error: result.error.description || "Error de verificaci\xF3n" },
1576
1663
  { status: 400 }
1577
1664
  );
@@ -1583,41 +1670,43 @@ function createVerifyHandler(deps) {
1583
1670
  logger.error("Failed to persist verified card:", err);
1584
1671
  }
1585
1672
  }
1586
- return server.NextResponse.json({
1673
+ return json({
1587
1674
  success: true,
1588
1675
  transaction: result.transaction
1589
1676
  });
1590
1677
  } catch (error) {
1591
1678
  logger.error("Card verify error:", error);
1592
- return server.NextResponse.json(
1679
+ return json(
1593
1680
  { error: "Error al verificar tarjeta" },
1594
1681
  { status: 500 }
1595
1682
  );
1596
1683
  }
1597
1684
  };
1598
1685
  }
1686
+
1687
+ // src/handlers/test-charge.ts
1599
1688
  function createTestChargeHandler(deps) {
1600
1689
  const logger = deps.logger ?? console;
1601
1690
  const { auth } = deps.firebase;
1602
1691
  const defaultDescription = deps.defaultDescription ?? "Test charge";
1603
1692
  return async function POST(request) {
1604
1693
  if (process.env.NUVEI_ENV === "prod") {
1605
- return server.NextResponse.json({ error: "Not available" }, { status: 404 });
1694
+ return json({ error: "Not available" }, { status: 404 });
1606
1695
  }
1607
- const sessionCookie = request.cookies.get("__session")?.value;
1696
+ const sessionCookie = getCookie(request, "__session");
1608
1697
  if (!sessionCookie || !auth) {
1609
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
1698
+ return json({ error: "No autorizado" }, { status: 401 });
1610
1699
  }
1611
1700
  let decodedToken;
1612
1701
  try {
1613
1702
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1614
1703
  } catch {
1615
- return server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1704
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1616
1705
  }
1617
1706
  try {
1618
1707
  const { token, amount, vat, description, devReference } = await request.json();
1619
1708
  if (!token || !amount || !devReference) {
1620
- return server.NextResponse.json(
1709
+ return json(
1621
1710
  { error: "token, amount y devReference son requeridos" },
1622
1711
  { status: 400 }
1623
1712
  );
@@ -1633,13 +1722,13 @@ function createTestChargeHandler(deps) {
1633
1722
  });
1634
1723
  logger.log("Test charge result:", JSON.stringify(result, null, 2));
1635
1724
  if (result.transaction && result.transaction.status === "success" && result.transaction.status_detail === 3) {
1636
- return server.NextResponse.json({
1725
+ return json({
1637
1726
  success: true,
1638
1727
  transaction: result.transaction,
1639
1728
  card: result.card
1640
1729
  });
1641
1730
  }
1642
- return server.NextResponse.json(
1731
+ return json(
1643
1732
  {
1644
1733
  success: false,
1645
1734
  error: result.transaction?.message || result.error?.description || "Pago rechazado",
@@ -1649,7 +1738,7 @@ function createTestChargeHandler(deps) {
1649
1738
  );
1650
1739
  } catch (error) {
1651
1740
  logger.error("Test charge error:", error);
1652
- return server.NextResponse.json(
1741
+ return json(
1653
1742
  { error: "Error interno del servidor" },
1654
1743
  { status: 500 }
1655
1744
  );
@@ -1657,12 +1746,64 @@ function createTestChargeHandler(deps) {
1657
1746
  };
1658
1747
  }
1659
1748
 
1749
+ // src/handlers/proxy.ts
1750
+ function createNuveiProxyHandler(deps = {}) {
1751
+ const logger = deps.logger ?? console;
1752
+ return async function POST(request) {
1753
+ if (request.method !== "POST") {
1754
+ return json({ error: "Method not allowed" }, { status: 405 });
1755
+ }
1756
+ let parsed;
1757
+ try {
1758
+ parsed = await request.json();
1759
+ } catch {
1760
+ return json({ error: "Invalid JSON body" }, { status: 400 });
1761
+ }
1762
+ const { path, method, body: nuveiBody } = parsed;
1763
+ const authToken = request.headers.get("x-nuvei-auth-token");
1764
+ if (!path || !method || !authToken) {
1765
+ return json(
1766
+ { error: "Missing path, method, or x-nuvei-auth-token header" },
1767
+ { status: 400 }
1768
+ );
1769
+ }
1770
+ const env = request.headers.get("x-nuvei-env") || "prod";
1771
+ const baseUrl = env === "prod" ? "https://ccapi.paymentez.com" : "https://ccapi-stg.paymentez.com";
1772
+ const url = `${baseUrl}${path}`;
1773
+ try {
1774
+ const options = {
1775
+ method,
1776
+ headers: {
1777
+ "Content-Type": "application/json",
1778
+ "Auth-Token": authToken
1779
+ }
1780
+ };
1781
+ if (nuveiBody && method === "POST") {
1782
+ options.body = JSON.stringify(nuveiBody);
1783
+ }
1784
+ const response = await fetch(url, options);
1785
+ const responseBody = await response.text();
1786
+ return new Response(responseBody, {
1787
+ status: response.status,
1788
+ headers: { "content-type": "application/json; charset=utf-8" }
1789
+ });
1790
+ } catch (err) {
1791
+ logger.error("[nuveiProxy] Error:", err);
1792
+ return json(
1793
+ { error: "Proxy error: " + (err instanceof Error ? err.message : String(err)) },
1794
+ { status: 500 }
1795
+ );
1796
+ }
1797
+ };
1798
+ }
1799
+
1660
1800
  exports.create3dsCallbackHandler = create3dsCallbackHandler;
1661
1801
  exports.create3dsCompleteHandler = create3dsCompleteHandler;
1662
1802
  exports.create3dsTimeoutHandler = create3dsTimeoutHandler;
1663
1803
  exports.createCardsHandler = createCardsHandler;
1664
1804
  exports.createChargeHandler = createChargeHandler;
1665
1805
  exports.createInitCheckoutHandler = createInitCheckoutHandler;
1806
+ exports.createNuveiProxyHandler = createNuveiProxyHandler;
1666
1807
  exports.createRefundHandler = createRefundHandler;
1667
1808
  exports.createTestChargeHandler = createTestChargeHandler;
1668
1809
  exports.createVerifyHandler = createVerifyHandler;