@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.
@@ -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,16 +910,18 @@ 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
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&oacute;n...
926
947
  </p>
927
948
  </body></html>`;
928
- return new server.NextResponse(html, {
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.cookies.get("__session")?.value;
1013
+ const sessionCookie = getCookie(request, "__session");
993
1014
  if (!sessionCookie || !auth) {
994
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
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 server.NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
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 server.NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
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 server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
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 server.NextResponse.json({
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 server.NextResponse.json({ error: "Pago ya procesado" }, { status: 409 });
1049
+ return json({ error: "Pago ya procesado" }, { status: 409 });
1029
1050
  }
1030
1051
  if (orderData.status !== "3ds-pending" && orderData.status !== "otp-pending") {
1031
- return server.NextResponse.json(
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 server.NextResponse.json({ error: msg }, { status: 400 });
1079
+ return json({ error: msg }, { status: 400 });
1059
1080
  }
1060
1081
  const transactionId = orderData.nuveiTransactionId || bodyTxId;
1061
1082
  if (!transactionId) {
1062
- return server.NextResponse.json(
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 server.NextResponse.json({ stillPending: true });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json({ stillPending: true });
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 server.NextResponse.json({
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 server.NextResponse.json({
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 server.NextResponse.json(
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 server.NextResponse.json(
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.cookies.get("__session")?.value;
1285
+ const sessionCookie = getCookie(request, "__session");
1263
1286
  if (!sessionCookie || !auth) {
1264
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
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 server.NextResponse.json({ error: "orderId requerido" }, { status: 400 });
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 server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
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 server.NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1306
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1284
1307
  }
1285
1308
  if (orderData.status !== "3ds-pending") {
1286
- return server.NextResponse.json({ alreadyResolved: true });
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 server.NextResponse.json({ ok: true });
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.cookies.get("__session")?.value;
1340
+ const sessionCookie = getCookie(request, "__session");
1316
1341
  if (!sessionCookie || !auth) {
1317
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
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 server.NextResponse.json({ error: "orderId requerido" }, { status: 400 });
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 server.NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json({ success: true, detail: result.detail });
1395
+ return json({ success: true, detail: result.detail });
1371
1396
  }
1372
- return server.NextResponse.json(
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 server.NextResponse.json(
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.cookies.get("__session")?.value;
1414
+ const sessionCookie = getCookie(request, "__session");
1390
1415
  if (!sessionCookie || !auth) {
1391
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(
1461
+ return json(
1437
1462
  { error: "No se recibi\xF3 referencia de checkout" },
1438
1463
  { status: 500 }
1439
1464
  );
1440
1465
  }
1441
- return server.NextResponse.json({
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 server.NextResponse.json(
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.cookies.get("__session")?.value;
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 server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ cards: enrichedCards });
1521
+ return json({ cards: enrichedCards });
1495
1522
  }
1496
- return server.NextResponse.json({ cards });
1523
+ return json({ cards });
1497
1524
  } catch (error) {
1498
1525
  logger.error("Error listing cards:", error);
1499
- return server.NextResponse.json(
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 server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json(result);
1561
+ return json(result);
1535
1562
  } catch (error) {
1536
1563
  logger.error("Error deleting card:", error);
1537
- return server.NextResponse.json(
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.cookies.get("__session")?.value;
1579
+ const sessionCookie = getCookie(request, "__session");
1551
1580
  if (!sessionCookie || !auth) {
1552
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
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 server.NextResponse.json(
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 server.NextResponse.json(
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 server.NextResponse.json({
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 server.NextResponse.json(
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 server.NextResponse.json({ error: "Not available" }, { status: 404 });
1636
+ return json({ error: "Not available" }, { status: 404 });
1606
1637
  }
1607
- const sessionCookie = request.cookies.get("__session")?.value;
1638
+ const sessionCookie = getCookie(request, "__session");
1608
1639
  if (!sessionCookie || !auth) {
1609
- return server.NextResponse.json({ error: "No autorizado" }, { status: 401 });
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 server.NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
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 server.NextResponse.json(
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 server.NextResponse.json({
1667
+ return json({
1637
1668
  success: true,
1638
1669
  transaction: result.transaction,
1639
1670
  card: result.card
1640
1671
  });
1641
1672
  }
1642
- return server.NextResponse.json(
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 server.NextResponse.json(
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;