@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,9 +1,28 @@
1
- import { NextResponse } from 'next/server';
2
1
  import { FieldValue } from 'firebase-admin/firestore';
3
2
  import crypto from 'crypto';
4
3
  import { z } from 'zod';
5
4
 
6
- // src/handlers/charge.ts
5
+ // src/http.ts
6
+ function json(data, init) {
7
+ const headers = new Headers(init?.headers);
8
+ if (!headers.has("content-type")) {
9
+ headers.set("content-type", "application/json; charset=utf-8");
10
+ }
11
+ return new Response(JSON.stringify(data), { ...init, headers });
12
+ }
13
+ function getCookie(request, name) {
14
+ const header = request.headers.get("cookie");
15
+ if (!header) return void 0;
16
+ for (const part of header.split(";")) {
17
+ const eq = part.indexOf("=");
18
+ if (eq === -1) continue;
19
+ const key = part.slice(0, eq).trim();
20
+ if (key === name) {
21
+ return decodeURIComponent(part.slice(eq + 1).trim());
22
+ }
23
+ }
24
+ return void 0;
25
+ }
7
26
  var NUVEI_DOMAIN = "paymentez.com";
8
27
  function getBaseUrl() {
9
28
  const env = process.env.NUVEI_ENV === "prod" ? "prod" : "stg";
@@ -206,15 +225,15 @@ function createChargeHandler(deps) {
206
225
  const { db, auth } = deps.firebase;
207
226
  return async function POST(request) {
208
227
  try {
209
- const sessionCookie = request.cookies.get("__session")?.value;
228
+ const sessionCookie = getCookie(request, "__session");
210
229
  if (!sessionCookie || !auth) {
211
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
230
+ return json({ error: "No autorizado" }, { status: 401 });
212
231
  }
213
232
  let decodedToken;
214
233
  try {
215
234
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
216
235
  } catch {
217
- return NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
236
+ return json({ error: "Sesion invalida" }, { status: 401 });
218
237
  }
219
238
  const clientIpForLimit = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "unknown";
220
239
  if (deps.rateLimit) {
@@ -224,7 +243,7 @@ function createChargeHandler(deps) {
224
243
  15 * 60 * 1e3
225
244
  );
226
245
  if (!allowed) {
227
- return NextResponse.json(
246
+ return json(
228
247
  { error: "Demasiados intentos. Intent\xE1 de nuevo en unos minutos." },
229
248
  { status: 429 }
230
249
  );
@@ -234,7 +253,7 @@ function createChargeHandler(deps) {
234
253
  if (deps.turnstile) {
235
254
  const turnstileToken = body.turnstileToken;
236
255
  if (!turnstileToken) {
237
- return NextResponse.json(
256
+ return json(
238
257
  { error: "Verificaci\xF3n de seguridad faltante. Recarg\xE1 la p\xE1gina." },
239
258
  { status: 403 }
240
259
  );
@@ -244,7 +263,7 @@ function createChargeHandler(deps) {
244
263
  clientIpForLimit === "unknown" ? void 0 : clientIpForLimit
245
264
  );
246
265
  if (!turnstileResult.success) {
247
- return NextResponse.json(
266
+ return json(
248
267
  { error: "No pudimos verificar que sos humano. Recarg\xE1 la p\xE1gina." },
249
268
  { status: 403 }
250
269
  );
@@ -270,18 +289,18 @@ function createChargeHandler(deps) {
270
289
  userId: userId?.substring(0, 8) + "..."
271
290
  });
272
291
  if (!token || !orderId || !amount) {
273
- return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
292
+ return json({ error: "Datos incompletos" }, { status: 400 });
274
293
  }
275
294
  if (decodedToken.uid !== userId) {
276
- return NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
295
+ return json({ error: "Usuario no coincide" }, { status: 403 });
277
296
  }
278
297
  const orderDoc = await db.collection("orders").doc(orderId).get();
279
298
  if (!orderDoc.exists) {
280
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
299
+ return json({ error: "Orden no encontrada" }, { status: 404 });
281
300
  }
282
301
  const orderData = orderDoc.data() ?? {};
283
302
  if (orderData.status !== "pending") {
284
- return NextResponse.json(
303
+ return json(
285
304
  { error: "Esta orden ya fue procesada" },
286
305
  { status: 409 }
287
306
  );
@@ -295,7 +314,7 @@ function createChargeHandler(deps) {
295
314
  });
296
315
  if (customResult.handled) {
297
316
  if (!customResult.valid) {
298
- return NextResponse.json(
317
+ return json(
299
318
  { error: customResult.error },
300
319
  { status: customResult.status }
301
320
  );
@@ -309,7 +328,7 @@ function createChargeHandler(deps) {
309
328
  for (const item of orderItems) {
310
329
  const productDoc = await db.collection("products").doc(item.productId).get();
311
330
  if (!productDoc.exists || !productDoc.data()?.isActive) {
312
- return NextResponse.json(
331
+ return json(
313
332
  { error: `El producto "${item.name}" ya no est\xE1 disponible` },
314
333
  { status: 409 }
315
334
  );
@@ -318,7 +337,7 @@ function createChargeHandler(deps) {
318
337
  const isDigital = productData.isDigital === true;
319
338
  const stock = productData.stock ?? 0;
320
339
  if (!isDigital && stock < item.quantity) {
321
- return NextResponse.json(
340
+ return json(
322
341
  {
323
342
  error: `Stock insuficiente para "${item.name}" (disponible: ${stock})`
324
343
  },
@@ -331,7 +350,7 @@ function createChargeHandler(deps) {
331
350
  });
332
351
  const expectedItemSubtotal = display.finalSubtotal;
333
352
  if (Math.abs(expectedItemSubtotal - item.price) > 0.02) {
334
- return NextResponse.json(
353
+ return json(
335
354
  {
336
355
  error: `El precio de "${item.name}" cambi\xF3. Recarg\xE1 la p\xE1gina para ver el precio actualizado.`
337
356
  },
@@ -346,7 +365,7 @@ function createChargeHandler(deps) {
346
365
  if (orderPromotionId && orderDiscount > 0) {
347
366
  const promoDoc = await db.collection("promotions").doc(orderPromotionId).get();
348
367
  if (!promoDoc.exists || !promoDoc.data()?.isActive) {
349
- return NextResponse.json(
368
+ return json(
350
369
  { error: "El cupon aplicado ya no es valido. Remuevelo y vuelve a intentar." },
351
370
  { status: 409 }
352
371
  );
@@ -356,13 +375,13 @@ function createChargeHandler(deps) {
356
375
  const validFrom = promo.rules.validFrom.toDate ? promo.rules.validFrom.toDate() : new Date(promo.rules.validFrom);
357
376
  const validUntil = promo.rules.validUntil.toDate ? promo.rules.validUntil.toDate() : new Date(promo.rules.validUntil);
358
377
  if (now < validFrom || now > validUntil) {
359
- return NextResponse.json(
378
+ return json(
360
379
  { error: "El cupon aplicado ha expirado. Remuevelo y vuelve a intentar." },
361
380
  { status: 409 }
362
381
  );
363
382
  }
364
383
  if (promo.rules.maxTotalUses && promo.currentUses >= promo.rules.maxTotalUses) {
365
- return NextResponse.json(
384
+ return json(
366
385
  { error: "El cupon aplicado ya alcanzo su limite de usos." },
367
386
  { status: 409 }
368
387
  );
@@ -381,7 +400,7 @@ function createChargeHandler(deps) {
381
400
  const verifiedVat = Math.round(verifiedDiscountedSubtotal * 0.15 * 100) / 100;
382
401
  const verifiedTotal = Math.round((verifiedDiscountedSubtotal + verifiedVat) * 100) / 100;
383
402
  if (verifiedTotal !== amount) {
384
- return NextResponse.json(
403
+ return json(
385
404
  {
386
405
  error: "El monto no coincide con los precios actuales. Actualiza tu carrito."
387
406
  },
@@ -504,7 +523,7 @@ function createChargeHandler(deps) {
504
523
  (err) => logger.error("[charge] Failed to delete card after payment:", err)
505
524
  );
506
525
  }
507
- return NextResponse.json({
526
+ return json({
508
527
  success: true,
509
528
  transactionId: nuveiData.transaction.id,
510
529
  authorizationCode: hasValidAuthCode ? rawAuthCode : null,
@@ -519,7 +538,7 @@ function createChargeHandler(deps) {
519
538
  chargeResponseAt: /* @__PURE__ */ new Date(),
520
539
  updatedAt: /* @__PURE__ */ new Date()
521
540
  });
522
- return NextResponse.json({
541
+ return json({
523
542
  review: true,
524
543
  orderId,
525
544
  transactionId: nuveiData.transaction.id
@@ -536,7 +555,7 @@ function createChargeHandler(deps) {
536
555
  ...persistDeleteOnPaid,
537
556
  updatedAt: /* @__PURE__ */ new Date()
538
557
  });
539
- return NextResponse.json({
558
+ return json({
540
559
  challenge: true,
541
560
  challengeHtml: hiddenIframeHtml,
542
561
  isDeviceFingerprint: true,
@@ -552,7 +571,7 @@ function createChargeHandler(deps) {
552
571
  ...persistDeleteOnPaid,
553
572
  updatedAt: /* @__PURE__ */ new Date()
554
573
  });
555
- return NextResponse.json({
574
+ return json({
556
575
  otpRequired: true,
557
576
  orderId,
558
577
  nuveiTransactionId: nuveiData.transaction.id,
@@ -575,7 +594,7 @@ function createChargeHandler(deps) {
575
594
  ...persistDeleteOnPaid,
576
595
  updatedAt: /* @__PURE__ */ new Date()
577
596
  });
578
- return NextResponse.json({
597
+ return json({
579
598
  challenge: true,
580
599
  challengeHtml,
581
600
  isDeviceFingerprint: false,
@@ -610,7 +629,7 @@ function createChargeHandler(deps) {
610
629
  }).catch(() => {
611
630
  });
612
631
  }
613
- return NextResponse.json({ error: threeDSErrorMsg }, { status: 400 });
632
+ return json({ error: threeDSErrorMsg }, { status: 400 });
614
633
  }
615
634
  const failedErrorMsg = getNuveiUserMessage(
616
635
  nuveiData.transaction?.status_detail,
@@ -638,10 +657,10 @@ function createChargeHandler(deps) {
638
657
  }).catch(() => {
639
658
  });
640
659
  }
641
- return NextResponse.json({ error: failedErrorMsg }, { status: 400 });
660
+ return json({ error: failedErrorMsg }, { status: 400 });
642
661
  } catch (error) {
643
662
  logger.error("Payment charge error:", error);
644
- return NextResponse.json(
663
+ return json(
645
664
  { error: "Error interno del servidor" },
646
665
  { status: 500 }
647
666
  );
@@ -675,7 +694,7 @@ function createWebhookHandler(deps) {
675
694
  logger.log("[webhook] Full payload:", JSON.stringify(payload));
676
695
  const { transaction } = payload;
677
696
  if (!transaction?.id || !transaction?.dev_reference) {
678
- return NextResponse.json({ error: "Payload inv\xE1lido" }, { status: 400 });
697
+ return json({ error: "Payload inv\xE1lido" }, { status: 400 });
679
698
  }
680
699
  const incomingCres = extractCres(payload);
681
700
  if (incomingCres) {
@@ -697,7 +716,7 @@ function createWebhookHandler(deps) {
697
716
  const orderDoc = await orderRef.get();
698
717
  if (!orderDoc.exists) {
699
718
  logger.error(`Webhook: Order ${orderId} not found`);
700
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
719
+ return json({ error: "Orden no encontrada" }, { status: 404 });
701
720
  }
702
721
  const orderDataEarly = orderDoc.data() ?? {};
703
722
  if (incomingCres && orderDataEarly.status === "3ds-pending" && !orderDataEarly.verifyCalledAt) {
@@ -791,7 +810,7 @@ function createWebhookHandler(deps) {
791
810
  );
792
811
  }
793
812
  }
794
- return NextResponse.json({ received: true, verifiedFromWebhook: true });
813
+ return json({ received: true, verifiedFromWebhook: true });
795
814
  }
796
815
  } catch (err) {
797
816
  if (err.message !== "ALREADY_CALLED") {
@@ -885,17 +904,55 @@ function createWebhookHandler(deps) {
885
904
  );
886
905
  }
887
906
  }
888
- return NextResponse.json({ received: true });
907
+ return json({ received: true });
889
908
  } catch (error) {
890
909
  logger.error("Webhook processing error:", error);
891
- return NextResponse.json(
910
+ return json(
892
911
  { error: "Error procesando webhook" },
893
912
  { status: 500 }
894
913
  );
895
914
  }
896
915
  };
897
916
  }
917
+
918
+ // src/handlers/3ds-callback.ts
919
+ function pickCres(src) {
920
+ for (const key of Object.keys(src)) {
921
+ const k = key.toLowerCase();
922
+ if (k === "cres" || k === "cres_value" || k === "cresvalue" || k === "value" || k === "param.value" || k === "paramvalue") {
923
+ const v = src[key];
924
+ if (typeof v === "string" && v.length > 0) return v;
925
+ }
926
+ }
927
+ return src.cres || src.CRes || src.CRES || src.value || "";
928
+ }
929
+ function transStatusFromCres(cres, logger) {
930
+ try {
931
+ const base64 = cres.replace(/-/g, "+").replace(/_/g, "/");
932
+ const pad = base64.length % 4;
933
+ const padded = pad ? base64 + "=".repeat(4 - pad) : base64;
934
+ const decoded = JSON.parse(
935
+ Buffer.from(padded, "base64").toString("utf-8")
936
+ );
937
+ if (decoded && typeof decoded.transStatus === "string") {
938
+ return decoded.transStatus;
939
+ }
940
+ } catch (e) {
941
+ logger.warn(
942
+ "[3ds-callback] Could not decode CRES payload:",
943
+ e instanceof Error ? e.message : e
944
+ );
945
+ }
946
+ return null;
947
+ }
948
+ function paramsToObject(params) {
949
+ const obj = {};
950
+ for (const [k, v] of params.entries()) obj[k] = v;
951
+ return obj;
952
+ }
898
953
  function buildCompletionPage(orderId, transStatus) {
954
+ const safeOrderId = String(orderId).replace(/[^a-zA-Z0-9_-]/g, "");
955
+ const safeTransStatus = String(transStatus).replace(/[^a-zA-Z0-9]/g, "");
899
956
  const html = `<!DOCTYPE html>
900
957
  <html><head><meta charset="utf-8"><title>3DS Verification</title></head>
901
958
  <body>
@@ -903,8 +960,8 @@ function buildCompletionPage(orderId, transStatus) {
903
960
  (function() {
904
961
  var message = {
905
962
  type: "3DS_COMPLETE",
906
- orderId: "${orderId}",
907
- transStatus: "${transStatus}"
963
+ orderId: "${safeOrderId}",
964
+ transStatus: "${safeTransStatus}"
908
965
  };
909
966
  try {
910
967
  if (window.parent && window.parent !== window) {
@@ -919,7 +976,7 @@ function buildCompletionPage(orderId, transStatus) {
919
976
  Verificando autenticaci&oacute;n...
920
977
  </p>
921
978
  </body></html>`;
922
- return new NextResponse(html, {
979
+ return new Response(html, {
923
980
  status: 200,
924
981
  headers: { "Content-Type": "text/html; charset=utf-8" }
925
982
  });
@@ -927,46 +984,68 @@ Verificando autenticaci&oacute;n...
927
984
  function create3dsCallbackHandler(deps) {
928
985
  const logger = deps.logger ?? console;
929
986
  const { db } = deps.firebase;
930
- async function storeCres(orderId, cres) {
931
- if (orderId && cres) {
932
- try {
933
- await db.collection("orders").doc(orderId).update({
934
- threeDSCres: cres,
935
- updatedAt: /* @__PURE__ */ new Date()
936
- });
937
- } catch (err) {
938
- logger.error("Failed to store cres on order:", err);
939
- }
987
+ async function persist(orderId, cres, transStatus) {
988
+ if (!orderId) return;
989
+ try {
990
+ const ref = db.collection("orders").doc(orderId);
991
+ const snap = await ref.get();
992
+ const status = snap.data()?.status;
993
+ if (!snap.exists || status !== "3ds-pending") {
994
+ logger.warn(
995
+ "[3ds-callback] rejected: order not in 3ds-pending state",
996
+ { orderId, status }
997
+ );
998
+ return;
999
+ }
1000
+ const update = { updatedAt: /* @__PURE__ */ new Date() };
1001
+ if (cres) update.threeDSCres = cres;
1002
+ if (transStatus) update.threeDSTransStatus = transStatus;
1003
+ await ref.update(update);
1004
+ } catch (err) {
1005
+ logger.error("[3ds-callback] failed to store cres on order:", err);
1006
+ }
1007
+ }
1008
+ function resolveTransStatus(cres, fallback) {
1009
+ if (cres) {
1010
+ const fromCres = transStatusFromCres(cres, logger);
1011
+ if (fromCres) return fromCres;
940
1012
  }
1013
+ return fallback;
941
1014
  }
942
1015
  const POST = async function POST2(request) {
943
- const { searchParams } = new URL(request.url);
944
- const orderId = searchParams.get("orderId") || "";
1016
+ const orderId = new URL(request.url).searchParams.get("orderId") || "";
945
1017
  let transStatus = "U";
946
1018
  let cres = "";
947
1019
  try {
948
1020
  const contentType = request.headers.get("content-type") || "";
1021
+ const raw = await request.text();
949
1022
  if (contentType.includes("application/x-www-form-urlencoded")) {
950
- const text = await request.text();
951
- const params = new URLSearchParams(text);
952
- transStatus = params.get("transStatus") || "U";
953
- cres = params.get("cres") || params.get("CRes") || "";
1023
+ const obj = paramsToObject(new URLSearchParams(raw));
1024
+ transStatus = obj.transStatus || obj.TransStatus || "U";
1025
+ cres = pickCres(obj);
954
1026
  } else {
955
- const body = await request.json();
956
- transStatus = body.transStatus || "U";
957
- cres = body.cres || body.CRes || "";
1027
+ try {
1028
+ const body = JSON.parse(raw);
1029
+ transStatus = body.transStatus || body.TransStatus || "U";
1030
+ cres = pickCres(body);
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ if (!cres && raw) {
1035
+ cres = pickCres(paramsToObject(new URLSearchParams(raw)));
958
1036
  }
959
1037
  } catch {
960
1038
  }
961
- await storeCres(orderId, cres);
1039
+ transStatus = resolveTransStatus(cres, transStatus);
1040
+ await persist(orderId, cres, transStatus);
962
1041
  return buildCompletionPage(orderId, transStatus);
963
1042
  };
964
1043
  const GET = async function GET2(request) {
965
- const { searchParams } = new URL(request.url);
966
- const orderId = searchParams.get("orderId") || "";
967
- const transStatus = searchParams.get("transStatus") || "Y";
968
- const cres = searchParams.get("cres") || searchParams.get("CRes") || "";
969
- await storeCres(orderId, cres);
1044
+ const params = new URL(request.url).searchParams;
1045
+ const orderId = params.get("orderId") || "";
1046
+ const cres = params.get("cres") || params.get("CRes") || params.get("value") || "";
1047
+ const transStatus = resolveTransStatus(cres, params.get("transStatus") || "Y");
1048
+ await persist(orderId, cres, transStatus);
970
1049
  return buildCompletionPage(orderId, transStatus);
971
1050
  };
972
1051
  return { POST, GET };
@@ -983,32 +1062,32 @@ function create3dsCompleteHandler(deps) {
983
1062
  const { db, auth } = deps.firebase;
984
1063
  return async function POST(request) {
985
1064
  try {
986
- const sessionCookie = request.cookies.get("__session")?.value;
1065
+ const sessionCookie = getCookie(request, "__session");
987
1066
  if (!sessionCookie || !auth) {
988
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1067
+ return json({ error: "No autorizado" }, { status: 401 });
989
1068
  }
990
1069
  let decodedToken;
991
1070
  try {
992
1071
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
993
1072
  } catch {
994
- return NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
1073
+ return json({ error: "Sesion invalida" }, { status: 401 });
995
1074
  }
996
1075
  const rawBody = await request.json();
997
1076
  const parsed = ThreeDSCompleteSchema.safeParse(rawBody);
998
1077
  if (!parsed.success) {
999
- return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
1078
+ return json({ error: "Datos incompletos" }, { status: 400 });
1000
1079
  }
1001
1080
  const { orderId, userId, type, nuveiTransactionId: bodyTxId, otpCode } = parsed.data;
1002
1081
  if (decodedToken.uid !== userId) {
1003
- return NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1082
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1004
1083
  }
1005
1084
  const orderDoc = await db.collection("orders").doc(orderId).get();
1006
1085
  if (!orderDoc.exists) {
1007
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1086
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1008
1087
  }
1009
1088
  const orderData = orderDoc.data() ?? {};
1010
1089
  if (orderData.status === "paid") {
1011
- return NextResponse.json({
1090
+ return json({
1012
1091
  success: true,
1013
1092
  transactionId: orderData.paymentTransactionId,
1014
1093
  authorizationCode: orderData.authorizationCode || null,
@@ -1019,10 +1098,10 @@ function create3dsCompleteHandler(deps) {
1019
1098
  logger.log(
1020
1099
  `[3ds-complete] verify already called for order ${orderId}, returning current state`
1021
1100
  );
1022
- return NextResponse.json({ error: "Pago ya procesado" }, { status: 409 });
1101
+ return json({ error: "Pago ya procesado" }, { status: 409 });
1023
1102
  }
1024
1103
  if (orderData.status !== "3ds-pending" && orderData.status !== "otp-pending") {
1025
- return NextResponse.json(
1104
+ return json(
1026
1105
  { error: "Esta orden ya fue procesada" },
1027
1106
  { status: 409 }
1028
1107
  );
@@ -1049,29 +1128,29 @@ function create3dsCompleteHandler(deps) {
1049
1128
  }).catch(() => {
1050
1129
  });
1051
1130
  }
1052
- return NextResponse.json({ error: msg }, { status: 400 });
1131
+ return json({ error: msg }, { status: 400 });
1053
1132
  }
1054
1133
  const transactionId = orderData.nuveiTransactionId || bodyTxId;
1055
1134
  if (!transactionId) {
1056
- return NextResponse.json(
1135
+ return json(
1057
1136
  { error: "No se encontr\xF3 el ID de transacci\xF3n para verificar" },
1058
1137
  { status: 400 }
1059
1138
  );
1060
1139
  }
1061
1140
  if (type === "AUTHENTICATION_CONTINUE" && !orderData.threeDSCres && !orderData.isDeviceFingerprint) {
1062
1141
  logger.log(`[3ds-complete] No CRES yet for order ${orderId} \u2014 still pending`);
1063
- return NextResponse.json({ stillPending: true });
1142
+ return json({ stillPending: true });
1064
1143
  }
1065
1144
  const actualType = type === "AUTHENTICATION_CONTINUE" && orderData.threeDSCres ? "BY_CRES" : type;
1066
1145
  const cresValue = actualType === "BY_CRES" ? orderData.threeDSCres : void 0;
1067
1146
  if (actualType === "BY_CRES" && !cresValue) {
1068
- return NextResponse.json(
1147
+ return json(
1069
1148
  { error: "No se encontr\xF3 el valor de autenticaci\xF3n 3DS (cres)" },
1070
1149
  { status: 400 }
1071
1150
  );
1072
1151
  }
1073
1152
  if (type === "BY_OTP" && !otpCode) {
1074
- return NextResponse.json(
1153
+ return json(
1075
1154
  { error: "Debes ingresar el c\xF3digo OTP" },
1076
1155
  { status: 400 }
1077
1156
  );
@@ -1102,7 +1181,7 @@ function create3dsCompleteHandler(deps) {
1102
1181
  logger.log(
1103
1182
  `[3ds-complete] Still pending: txStatus=${txStatus} detail=${txStatusDetail}. Polling continues.`
1104
1183
  );
1105
- return NextResponse.json({ stillPending: true });
1184
+ return json({ stillPending: true });
1106
1185
  }
1107
1186
  if (isSuccess) {
1108
1187
  const hasValidAuthCode = typeof txAuthCode === "string" && txAuthCode.trim().length > 0 && txAuthCode !== "null";
@@ -1188,7 +1267,7 @@ function create3dsCompleteHandler(deps) {
1188
1267
  (err) => logger.error("[3ds-complete] Failed to delete card after payment:", err)
1189
1268
  );
1190
1269
  }
1191
- return NextResponse.json({
1270
+ return json({
1192
1271
  success: true,
1193
1272
  transactionId: txId,
1194
1273
  authorizationCode: hasValidAuthCode ? txAuthCode : null,
@@ -1206,7 +1285,7 @@ function create3dsCompleteHandler(deps) {
1206
1285
  updatedAt: /* @__PURE__ */ new Date(),
1207
1286
  threeDSCres: FieldValue.delete()
1208
1287
  });
1209
- return NextResponse.json({
1288
+ return json({
1210
1289
  challenge: true,
1211
1290
  challengeHtml,
1212
1291
  isDeviceFingerprint: false,
@@ -1236,48 +1315,50 @@ function create3dsCompleteHandler(deps) {
1236
1315
  }).catch(() => {
1237
1316
  });
1238
1317
  }
1239
- return NextResponse.json(
1318
+ return json(
1240
1319
  { error: "Pago rechazado tras autenticaci\xF3n 3DS." },
1241
1320
  { status: 400 }
1242
1321
  );
1243
1322
  } catch (error) {
1244
1323
  logger.error("3DS complete error:", error);
1245
- return NextResponse.json(
1324
+ return json(
1246
1325
  { error: "Error interno del servidor" },
1247
1326
  { status: 500 }
1248
1327
  );
1249
1328
  }
1250
1329
  };
1251
1330
  }
1331
+
1332
+ // src/handlers/3ds-timeout.ts
1252
1333
  function create3dsTimeoutHandler(deps) {
1253
1334
  const logger = deps.logger ?? console;
1254
1335
  const { db, auth } = deps.firebase;
1255
1336
  return async function POST(request) {
1256
- const sessionCookie = request.cookies.get("__session")?.value;
1337
+ const sessionCookie = getCookie(request, "__session");
1257
1338
  if (!sessionCookie || !auth) {
1258
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1339
+ return json({ error: "No autorizado" }, { status: 401 });
1259
1340
  }
1260
1341
  let decodedToken;
1261
1342
  try {
1262
1343
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1263
1344
  } catch {
1264
- return NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
1345
+ return json({ error: "Sesion invalida" }, { status: 401 });
1265
1346
  }
1266
1347
  const { orderId } = await request.json();
1267
1348
  if (!orderId || typeof orderId !== "string") {
1268
- return NextResponse.json({ error: "orderId requerido" }, { status: 400 });
1349
+ return json({ error: "orderId requerido" }, { status: 400 });
1269
1350
  }
1270
1351
  const orderRef = db.collection("orders").doc(orderId);
1271
1352
  const orderDoc = await orderRef.get();
1272
1353
  if (!orderDoc.exists) {
1273
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1354
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1274
1355
  }
1275
1356
  const orderData = orderDoc.data() ?? {};
1276
1357
  if (decodedToken.uid !== orderData.userId) {
1277
- return NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1358
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1278
1359
  }
1279
1360
  if (orderData.status !== "3ds-pending") {
1280
- return NextResponse.json({ alreadyResolved: true });
1361
+ return json({ alreadyResolved: true });
1281
1362
  }
1282
1363
  await orderRef.update({
1283
1364
  status: "failed",
@@ -1298,48 +1379,50 @@ function create3dsTimeoutHandler(deps) {
1298
1379
  ...retryUrl ? { retryUrl } : {}
1299
1380
  }).catch((err) => logger.error("[3ds-timeout] Failed to send email:", err));
1300
1381
  }
1301
- return NextResponse.json({ ok: true });
1382
+ return json({ ok: true });
1302
1383
  };
1303
1384
  }
1385
+
1386
+ // src/handlers/refund.ts
1304
1387
  function createRefundHandler(deps) {
1305
1388
  const logger = deps.logger ?? console;
1306
1389
  const { db, auth } = deps.firebase;
1307
1390
  return async function POST(request) {
1308
1391
  try {
1309
- const sessionCookie = request.cookies.get("__session")?.value;
1392
+ const sessionCookie = getCookie(request, "__session");
1310
1393
  if (!sessionCookie || !auth) {
1311
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1394
+ return json({ error: "No autorizado" }, { status: 401 });
1312
1395
  }
1313
1396
  let decodedToken;
1314
1397
  try {
1315
1398
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1316
1399
  } catch {
1317
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1400
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1318
1401
  }
1319
1402
  const { orderId } = await request.json();
1320
1403
  if (!orderId) {
1321
- return NextResponse.json({ error: "orderId requerido" }, { status: 400 });
1404
+ return json({ error: "orderId requerido" }, { status: 400 });
1322
1405
  }
1323
1406
  const orderDoc = await db.collection("orders").doc(orderId).get();
1324
1407
  if (!orderDoc.exists) {
1325
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1408
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1326
1409
  }
1327
1410
  const order = orderDoc.data() ?? {};
1328
1411
  if (order.userId !== decodedToken.uid) {
1329
- return NextResponse.json(
1412
+ return json(
1330
1413
  { error: "No autorizado para esta orden" },
1331
1414
  { status: 403 }
1332
1415
  );
1333
1416
  }
1334
1417
  if (order.status !== "paid") {
1335
- return NextResponse.json(
1418
+ return json(
1336
1419
  { error: "Solo se pueden reembolsar \xF3rdenes pagadas" },
1337
1420
  { status: 400 }
1338
1421
  );
1339
1422
  }
1340
1423
  const paymentTransactionId = order.paymentTransactionId;
1341
1424
  if (!paymentTransactionId) {
1342
- return NextResponse.json(
1425
+ return json(
1343
1426
  { error: "No se encontr\xF3 ID de transacci\xF3n" },
1344
1427
  { status: 400 }
1345
1428
  );
@@ -1361,15 +1444,15 @@ function createRefundHandler(deps) {
1361
1444
  );
1362
1445
  }
1363
1446
  }
1364
- return NextResponse.json({ success: true, detail: result.detail });
1447
+ return json({ success: true, detail: result.detail });
1365
1448
  }
1366
- return NextResponse.json(
1449
+ return json(
1367
1450
  { error: result.detail || "Error al procesar reembolso" },
1368
1451
  { status: 400 }
1369
1452
  );
1370
1453
  } catch (error) {
1371
1454
  logger.error("Refund error:", error);
1372
- return NextResponse.json(
1455
+ return json(
1373
1456
  { error: "Error interno del servidor" },
1374
1457
  { status: 500 }
1375
1458
  );
@@ -1380,20 +1463,20 @@ function createInitCheckoutHandler(deps) {
1380
1463
  const logger = deps.logger ?? console;
1381
1464
  const { auth } = deps.firebase;
1382
1465
  return async function POST(request) {
1383
- const sessionCookie = request.cookies.get("__session")?.value;
1466
+ const sessionCookie = getCookie(request, "__session");
1384
1467
  if (!sessionCookie || !auth) {
1385
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1468
+ return json({ error: "No autorizado" }, { status: 401 });
1386
1469
  }
1387
1470
  let decodedToken;
1388
1471
  try {
1389
1472
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1390
1473
  } catch {
1391
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1474
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1392
1475
  }
1393
1476
  try {
1394
1477
  const { amount, vat, description, devReference } = await request.json();
1395
1478
  if (!amount || !devReference) {
1396
- return NextResponse.json(
1479
+ return json(
1397
1480
  { error: "amount y devReference son requeridos" },
1398
1481
  { status: 400 }
1399
1482
  );
@@ -1421,36 +1504,38 @@ function createInitCheckoutHandler(deps) {
1421
1504
  }
1422
1505
  });
1423
1506
  if (result.error) {
1424
- return NextResponse.json(
1507
+ return json(
1425
1508
  { error: result.error.description || "Error al inicializar checkout" },
1426
1509
  { status: 400 }
1427
1510
  );
1428
1511
  }
1429
1512
  if (!result.reference) {
1430
- return NextResponse.json(
1513
+ return json(
1431
1514
  { error: "No se recibi\xF3 referencia de checkout" },
1432
1515
  { status: 500 }
1433
1516
  );
1434
1517
  }
1435
- return NextResponse.json({
1518
+ return json({
1436
1519
  reference: result.reference,
1437
1520
  checkoutUrl: result.checkout_url
1438
1521
  });
1439
1522
  } catch (error) {
1440
1523
  logger.error("Init checkout error:", error);
1441
- return NextResponse.json(
1524
+ return json(
1442
1525
  { error: "Error al inicializar checkout" },
1443
1526
  { status: 500 }
1444
1527
  );
1445
1528
  }
1446
1529
  };
1447
1530
  }
1531
+
1532
+ // src/handlers/cards.ts
1448
1533
  function createCardsHandler(deps) {
1449
1534
  const logger = deps.logger ?? console;
1450
1535
  const { db, auth } = deps.firebase;
1451
1536
  const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
1452
1537
  async function verifySession(request) {
1453
- const sessionCookie = request.cookies.get("__session")?.value;
1538
+ const sessionCookie = getCookie(request, "__session");
1454
1539
  if (!sessionCookie || !auth) return null;
1455
1540
  try {
1456
1541
  return await auth.verifySessionCookie(sessionCookie, true);
@@ -1461,7 +1546,7 @@ function createCardsHandler(deps) {
1461
1546
  const GET = async function GET2(request) {
1462
1547
  const decoded = await verifySession(request);
1463
1548
  if (!decoded) {
1464
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1549
+ return json({ error: "No autorizado" }, { status: 401 });
1465
1550
  }
1466
1551
  try {
1467
1552
  const result = await listCards(decoded.uid);
@@ -1485,12 +1570,12 @@ function createCardsHandler(deps) {
1485
1570
  const enrichedCards = cards.map(
1486
1571
  (c) => c.status === "review" && verifiedTokens.has(c.token) ? { ...c, status: "valid" } : c
1487
1572
  );
1488
- return NextResponse.json({ cards: enrichedCards });
1573
+ return json({ cards: enrichedCards });
1489
1574
  }
1490
- return NextResponse.json({ cards });
1575
+ return json({ cards });
1491
1576
  } catch (error) {
1492
1577
  logger.error("Error listing cards:", error);
1493
- return NextResponse.json(
1578
+ return json(
1494
1579
  { error: "Error al obtener tarjetas" },
1495
1580
  { status: 500 }
1496
1581
  );
@@ -1499,12 +1584,12 @@ function createCardsHandler(deps) {
1499
1584
  const DELETE = async function DELETE2(request) {
1500
1585
  const decoded = await verifySession(request);
1501
1586
  if (!decoded) {
1502
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1587
+ return json({ error: "No autorizado" }, { status: 401 });
1503
1588
  }
1504
1589
  try {
1505
1590
  const { token } = await request.json();
1506
1591
  if (!token) {
1507
- return NextResponse.json(
1592
+ return json(
1508
1593
  { error: "Token de tarjeta requerido" },
1509
1594
  { status: 400 }
1510
1595
  );
@@ -1514,7 +1599,7 @@ function createCardsHandler(deps) {
1514
1599
  const resultWithError = result;
1515
1600
  if (resultWithError.error) {
1516
1601
  logger.error("[cards/DELETE] Nuvei delete failed:", JSON.stringify(result));
1517
- return NextResponse.json(
1602
+ return json(
1518
1603
  { error: "No se pudo eliminar la tarjeta en Nuvei", detail: result },
1519
1604
  { status: 400 }
1520
1605
  );
@@ -1525,10 +1610,10 @@ function createCardsHandler(deps) {
1525
1610
  } catch {
1526
1611
  }
1527
1612
  }
1528
- return NextResponse.json(result);
1613
+ return json(result);
1529
1614
  } catch (error) {
1530
1615
  logger.error("Error deleting card:", error);
1531
- return NextResponse.json(
1616
+ return json(
1532
1617
  { error: "Error al eliminar tarjeta" },
1533
1618
  { status: 500 }
1534
1619
  );
@@ -1536,25 +1621,27 @@ function createCardsHandler(deps) {
1536
1621
  };
1537
1622
  return { GET, DELETE };
1538
1623
  }
1624
+
1625
+ // src/handlers/verify.ts
1539
1626
  function createVerifyHandler(deps) {
1540
1627
  const logger = deps.logger ?? console;
1541
1628
  const { db, auth } = deps.firebase;
1542
1629
  const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
1543
1630
  return async function POST(request) {
1544
- const sessionCookie = request.cookies.get("__session")?.value;
1631
+ const sessionCookie = getCookie(request, "__session");
1545
1632
  if (!sessionCookie || !auth) {
1546
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1633
+ return json({ error: "No autorizado" }, { status: 401 });
1547
1634
  }
1548
1635
  let decodedToken;
1549
1636
  try {
1550
1637
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1551
1638
  } catch {
1552
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1639
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1553
1640
  }
1554
1641
  try {
1555
1642
  const { cardToken, transactionReference, value } = await request.json();
1556
1643
  if (!cardToken && !transactionReference || !value) {
1557
- return NextResponse.json(
1644
+ return json(
1558
1645
  { error: "transactionReference y value son requeridos" },
1559
1646
  { status: 400 }
1560
1647
  );
@@ -1565,7 +1652,7 @@ function createVerifyHandler(deps) {
1565
1652
  value
1566
1653
  });
1567
1654
  if (result.error) {
1568
- return NextResponse.json(
1655
+ return json(
1569
1656
  { error: result.error.description || "Error de verificaci\xF3n" },
1570
1657
  { status: 400 }
1571
1658
  );
@@ -1577,41 +1664,43 @@ function createVerifyHandler(deps) {
1577
1664
  logger.error("Failed to persist verified card:", err);
1578
1665
  }
1579
1666
  }
1580
- return NextResponse.json({
1667
+ return json({
1581
1668
  success: true,
1582
1669
  transaction: result.transaction
1583
1670
  });
1584
1671
  } catch (error) {
1585
1672
  logger.error("Card verify error:", error);
1586
- return NextResponse.json(
1673
+ return json(
1587
1674
  { error: "Error al verificar tarjeta" },
1588
1675
  { status: 500 }
1589
1676
  );
1590
1677
  }
1591
1678
  };
1592
1679
  }
1680
+
1681
+ // src/handlers/test-charge.ts
1593
1682
  function createTestChargeHandler(deps) {
1594
1683
  const logger = deps.logger ?? console;
1595
1684
  const { auth } = deps.firebase;
1596
1685
  const defaultDescription = deps.defaultDescription ?? "Test charge";
1597
1686
  return async function POST(request) {
1598
1687
  if (process.env.NUVEI_ENV === "prod") {
1599
- return NextResponse.json({ error: "Not available" }, { status: 404 });
1688
+ return json({ error: "Not available" }, { status: 404 });
1600
1689
  }
1601
- const sessionCookie = request.cookies.get("__session")?.value;
1690
+ const sessionCookie = getCookie(request, "__session");
1602
1691
  if (!sessionCookie || !auth) {
1603
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1692
+ return json({ error: "No autorizado" }, { status: 401 });
1604
1693
  }
1605
1694
  let decodedToken;
1606
1695
  try {
1607
1696
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1608
1697
  } catch {
1609
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1698
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1610
1699
  }
1611
1700
  try {
1612
1701
  const { token, amount, vat, description, devReference } = await request.json();
1613
1702
  if (!token || !amount || !devReference) {
1614
- return NextResponse.json(
1703
+ return json(
1615
1704
  { error: "token, amount y devReference son requeridos" },
1616
1705
  { status: 400 }
1617
1706
  );
@@ -1627,13 +1716,13 @@ function createTestChargeHandler(deps) {
1627
1716
  });
1628
1717
  logger.log("Test charge result:", JSON.stringify(result, null, 2));
1629
1718
  if (result.transaction && result.transaction.status === "success" && result.transaction.status_detail === 3) {
1630
- return NextResponse.json({
1719
+ return json({
1631
1720
  success: true,
1632
1721
  transaction: result.transaction,
1633
1722
  card: result.card
1634
1723
  });
1635
1724
  }
1636
- return NextResponse.json(
1725
+ return json(
1637
1726
  {
1638
1727
  success: false,
1639
1728
  error: result.transaction?.message || result.error?.description || "Pago rechazado",
@@ -1643,7 +1732,7 @@ function createTestChargeHandler(deps) {
1643
1732
  );
1644
1733
  } catch (error) {
1645
1734
  logger.error("Test charge error:", error);
1646
- return NextResponse.json(
1735
+ return json(
1647
1736
  { error: "Error interno del servidor" },
1648
1737
  { status: 500 }
1649
1738
  );
@@ -1651,6 +1740,57 @@ function createTestChargeHandler(deps) {
1651
1740
  };
1652
1741
  }
1653
1742
 
1654
- export { create3dsCallbackHandler, create3dsCompleteHandler, create3dsTimeoutHandler, createCardsHandler, createChargeHandler, createInitCheckoutHandler, createRefundHandler, createTestChargeHandler, createVerifyHandler, createWebhookHandler };
1743
+ // src/handlers/proxy.ts
1744
+ function createNuveiProxyHandler(deps = {}) {
1745
+ const logger = deps.logger ?? console;
1746
+ return async function POST(request) {
1747
+ if (request.method !== "POST") {
1748
+ return json({ error: "Method not allowed" }, { status: 405 });
1749
+ }
1750
+ let parsed;
1751
+ try {
1752
+ parsed = await request.json();
1753
+ } catch {
1754
+ return json({ error: "Invalid JSON body" }, { status: 400 });
1755
+ }
1756
+ const { path, method, body: nuveiBody } = parsed;
1757
+ const authToken = request.headers.get("x-nuvei-auth-token");
1758
+ if (!path || !method || !authToken) {
1759
+ return json(
1760
+ { error: "Missing path, method, or x-nuvei-auth-token header" },
1761
+ { status: 400 }
1762
+ );
1763
+ }
1764
+ const env = request.headers.get("x-nuvei-env") || "prod";
1765
+ const baseUrl = env === "prod" ? "https://ccapi.paymentez.com" : "https://ccapi-stg.paymentez.com";
1766
+ const url = `${baseUrl}${path}`;
1767
+ try {
1768
+ const options = {
1769
+ method,
1770
+ headers: {
1771
+ "Content-Type": "application/json",
1772
+ "Auth-Token": authToken
1773
+ }
1774
+ };
1775
+ if (nuveiBody && method === "POST") {
1776
+ options.body = JSON.stringify(nuveiBody);
1777
+ }
1778
+ const response = await fetch(url, options);
1779
+ const responseBody = await response.text();
1780
+ return new Response(responseBody, {
1781
+ status: response.status,
1782
+ headers: { "content-type": "application/json; charset=utf-8" }
1783
+ });
1784
+ } catch (err) {
1785
+ logger.error("[nuveiProxy] Error:", err);
1786
+ return json(
1787
+ { error: "Proxy error: " + (err instanceof Error ? err.message : String(err)) },
1788
+ { status: 500 }
1789
+ );
1790
+ }
1791
+ };
1792
+ }
1793
+
1794
+ export { create3dsCallbackHandler, create3dsCompleteHandler, create3dsTimeoutHandler, createCardsHandler, createChargeHandler, createInitCheckoutHandler, createNuveiProxyHandler, createRefundHandler, createTestChargeHandler, createVerifyHandler, createWebhookHandler };
1655
1795
  //# sourceMappingURL=index.js.map
1656
1796
  //# sourceMappingURL=index.js.map