@pandait.tech/payment-nuvei 0.1.1 → 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,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,16 +904,18 @@ 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
898
919
  function buildCompletionPage(orderId, transStatus) {
899
920
  const html = `<!DOCTYPE html>
900
921
  <html><head><meta charset="utf-8"><title>3DS Verification</title></head>
@@ -919,7 +940,7 @@ function buildCompletionPage(orderId, transStatus) {
919
940
  Verificando autenticaci&oacute;n...
920
941
  </p>
921
942
  </body></html>`;
922
- return new NextResponse(html, {
943
+ return new Response(html, {
923
944
  status: 200,
924
945
  headers: { "Content-Type": "text/html; charset=utf-8" }
925
946
  });
@@ -983,32 +1004,32 @@ function create3dsCompleteHandler(deps) {
983
1004
  const { db, auth } = deps.firebase;
984
1005
  return async function POST(request) {
985
1006
  try {
986
- const sessionCookie = request.cookies.get("__session")?.value;
1007
+ const sessionCookie = getCookie(request, "__session");
987
1008
  if (!sessionCookie || !auth) {
988
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1009
+ return json({ error: "No autorizado" }, { status: 401 });
989
1010
  }
990
1011
  let decodedToken;
991
1012
  try {
992
1013
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
993
1014
  } catch {
994
- return NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
1015
+ return json({ error: "Sesion invalida" }, { status: 401 });
995
1016
  }
996
1017
  const rawBody = await request.json();
997
1018
  const parsed = ThreeDSCompleteSchema.safeParse(rawBody);
998
1019
  if (!parsed.success) {
999
- return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
1020
+ return json({ error: "Datos incompletos" }, { status: 400 });
1000
1021
  }
1001
1022
  const { orderId, userId, type, nuveiTransactionId: bodyTxId, otpCode } = parsed.data;
1002
1023
  if (decodedToken.uid !== userId) {
1003
- return NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1024
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1004
1025
  }
1005
1026
  const orderDoc = await db.collection("orders").doc(orderId).get();
1006
1027
  if (!orderDoc.exists) {
1007
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1028
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1008
1029
  }
1009
1030
  const orderData = orderDoc.data() ?? {};
1010
1031
  if (orderData.status === "paid") {
1011
- return NextResponse.json({
1032
+ return json({
1012
1033
  success: true,
1013
1034
  transactionId: orderData.paymentTransactionId,
1014
1035
  authorizationCode: orderData.authorizationCode || null,
@@ -1019,10 +1040,10 @@ function create3dsCompleteHandler(deps) {
1019
1040
  logger.log(
1020
1041
  `[3ds-complete] verify already called for order ${orderId}, returning current state`
1021
1042
  );
1022
- return NextResponse.json({ error: "Pago ya procesado" }, { status: 409 });
1043
+ return json({ error: "Pago ya procesado" }, { status: 409 });
1023
1044
  }
1024
1045
  if (orderData.status !== "3ds-pending" && orderData.status !== "otp-pending") {
1025
- return NextResponse.json(
1046
+ return json(
1026
1047
  { error: "Esta orden ya fue procesada" },
1027
1048
  { status: 409 }
1028
1049
  );
@@ -1049,29 +1070,29 @@ function create3dsCompleteHandler(deps) {
1049
1070
  }).catch(() => {
1050
1071
  });
1051
1072
  }
1052
- return NextResponse.json({ error: msg }, { status: 400 });
1073
+ return json({ error: msg }, { status: 400 });
1053
1074
  }
1054
1075
  const transactionId = orderData.nuveiTransactionId || bodyTxId;
1055
1076
  if (!transactionId) {
1056
- return NextResponse.json(
1077
+ return json(
1057
1078
  { error: "No se encontr\xF3 el ID de transacci\xF3n para verificar" },
1058
1079
  { status: 400 }
1059
1080
  );
1060
1081
  }
1061
1082
  if (type === "AUTHENTICATION_CONTINUE" && !orderData.threeDSCres && !orderData.isDeviceFingerprint) {
1062
1083
  logger.log(`[3ds-complete] No CRES yet for order ${orderId} \u2014 still pending`);
1063
- return NextResponse.json({ stillPending: true });
1084
+ return json({ stillPending: true });
1064
1085
  }
1065
1086
  const actualType = type === "AUTHENTICATION_CONTINUE" && orderData.threeDSCres ? "BY_CRES" : type;
1066
1087
  const cresValue = actualType === "BY_CRES" ? orderData.threeDSCres : void 0;
1067
1088
  if (actualType === "BY_CRES" && !cresValue) {
1068
- return NextResponse.json(
1089
+ return json(
1069
1090
  { error: "No se encontr\xF3 el valor de autenticaci\xF3n 3DS (cres)" },
1070
1091
  { status: 400 }
1071
1092
  );
1072
1093
  }
1073
1094
  if (type === "BY_OTP" && !otpCode) {
1074
- return NextResponse.json(
1095
+ return json(
1075
1096
  { error: "Debes ingresar el c\xF3digo OTP" },
1076
1097
  { status: 400 }
1077
1098
  );
@@ -1102,7 +1123,7 @@ function create3dsCompleteHandler(deps) {
1102
1123
  logger.log(
1103
1124
  `[3ds-complete] Still pending: txStatus=${txStatus} detail=${txStatusDetail}. Polling continues.`
1104
1125
  );
1105
- return NextResponse.json({ stillPending: true });
1126
+ return json({ stillPending: true });
1106
1127
  }
1107
1128
  if (isSuccess) {
1108
1129
  const hasValidAuthCode = typeof txAuthCode === "string" && txAuthCode.trim().length > 0 && txAuthCode !== "null";
@@ -1188,7 +1209,7 @@ function create3dsCompleteHandler(deps) {
1188
1209
  (err) => logger.error("[3ds-complete] Failed to delete card after payment:", err)
1189
1210
  );
1190
1211
  }
1191
- return NextResponse.json({
1212
+ return json({
1192
1213
  success: true,
1193
1214
  transactionId: txId,
1194
1215
  authorizationCode: hasValidAuthCode ? txAuthCode : null,
@@ -1206,7 +1227,7 @@ function create3dsCompleteHandler(deps) {
1206
1227
  updatedAt: /* @__PURE__ */ new Date(),
1207
1228
  threeDSCres: FieldValue.delete()
1208
1229
  });
1209
- return NextResponse.json({
1230
+ return json({
1210
1231
  challenge: true,
1211
1232
  challengeHtml,
1212
1233
  isDeviceFingerprint: false,
@@ -1236,48 +1257,50 @@ function create3dsCompleteHandler(deps) {
1236
1257
  }).catch(() => {
1237
1258
  });
1238
1259
  }
1239
- return NextResponse.json(
1260
+ return json(
1240
1261
  { error: "Pago rechazado tras autenticaci\xF3n 3DS." },
1241
1262
  { status: 400 }
1242
1263
  );
1243
1264
  } catch (error) {
1244
1265
  logger.error("3DS complete error:", error);
1245
- return NextResponse.json(
1266
+ return json(
1246
1267
  { error: "Error interno del servidor" },
1247
1268
  { status: 500 }
1248
1269
  );
1249
1270
  }
1250
1271
  };
1251
1272
  }
1273
+
1274
+ // src/handlers/3ds-timeout.ts
1252
1275
  function create3dsTimeoutHandler(deps) {
1253
1276
  const logger = deps.logger ?? console;
1254
1277
  const { db, auth } = deps.firebase;
1255
1278
  return async function POST(request) {
1256
- const sessionCookie = request.cookies.get("__session")?.value;
1279
+ const sessionCookie = getCookie(request, "__session");
1257
1280
  if (!sessionCookie || !auth) {
1258
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1281
+ return json({ error: "No autorizado" }, { status: 401 });
1259
1282
  }
1260
1283
  let decodedToken;
1261
1284
  try {
1262
1285
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1263
1286
  } catch {
1264
- return NextResponse.json({ error: "Sesion invalida" }, { status: 401 });
1287
+ return json({ error: "Sesion invalida" }, { status: 401 });
1265
1288
  }
1266
1289
  const { orderId } = await request.json();
1267
1290
  if (!orderId || typeof orderId !== "string") {
1268
- return NextResponse.json({ error: "orderId requerido" }, { status: 400 });
1291
+ return json({ error: "orderId requerido" }, { status: 400 });
1269
1292
  }
1270
1293
  const orderRef = db.collection("orders").doc(orderId);
1271
1294
  const orderDoc = await orderRef.get();
1272
1295
  if (!orderDoc.exists) {
1273
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1296
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1274
1297
  }
1275
1298
  const orderData = orderDoc.data() ?? {};
1276
1299
  if (decodedToken.uid !== orderData.userId) {
1277
- return NextResponse.json({ error: "Usuario no coincide" }, { status: 403 });
1300
+ return json({ error: "Usuario no coincide" }, { status: 403 });
1278
1301
  }
1279
1302
  if (orderData.status !== "3ds-pending") {
1280
- return NextResponse.json({ alreadyResolved: true });
1303
+ return json({ alreadyResolved: true });
1281
1304
  }
1282
1305
  await orderRef.update({
1283
1306
  status: "failed",
@@ -1298,48 +1321,50 @@ function create3dsTimeoutHandler(deps) {
1298
1321
  ...retryUrl ? { retryUrl } : {}
1299
1322
  }).catch((err) => logger.error("[3ds-timeout] Failed to send email:", err));
1300
1323
  }
1301
- return NextResponse.json({ ok: true });
1324
+ return json({ ok: true });
1302
1325
  };
1303
1326
  }
1327
+
1328
+ // src/handlers/refund.ts
1304
1329
  function createRefundHandler(deps) {
1305
1330
  const logger = deps.logger ?? console;
1306
1331
  const { db, auth } = deps.firebase;
1307
1332
  return async function POST(request) {
1308
1333
  try {
1309
- const sessionCookie = request.cookies.get("__session")?.value;
1334
+ const sessionCookie = getCookie(request, "__session");
1310
1335
  if (!sessionCookie || !auth) {
1311
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1336
+ return json({ error: "No autorizado" }, { status: 401 });
1312
1337
  }
1313
1338
  let decodedToken;
1314
1339
  try {
1315
1340
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1316
1341
  } catch {
1317
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1342
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1318
1343
  }
1319
1344
  const { orderId } = await request.json();
1320
1345
  if (!orderId) {
1321
- return NextResponse.json({ error: "orderId requerido" }, { status: 400 });
1346
+ return json({ error: "orderId requerido" }, { status: 400 });
1322
1347
  }
1323
1348
  const orderDoc = await db.collection("orders").doc(orderId).get();
1324
1349
  if (!orderDoc.exists) {
1325
- return NextResponse.json({ error: "Orden no encontrada" }, { status: 404 });
1350
+ return json({ error: "Orden no encontrada" }, { status: 404 });
1326
1351
  }
1327
1352
  const order = orderDoc.data() ?? {};
1328
1353
  if (order.userId !== decodedToken.uid) {
1329
- return NextResponse.json(
1354
+ return json(
1330
1355
  { error: "No autorizado para esta orden" },
1331
1356
  { status: 403 }
1332
1357
  );
1333
1358
  }
1334
1359
  if (order.status !== "paid") {
1335
- return NextResponse.json(
1360
+ return json(
1336
1361
  { error: "Solo se pueden reembolsar \xF3rdenes pagadas" },
1337
1362
  { status: 400 }
1338
1363
  );
1339
1364
  }
1340
1365
  const paymentTransactionId = order.paymentTransactionId;
1341
1366
  if (!paymentTransactionId) {
1342
- return NextResponse.json(
1367
+ return json(
1343
1368
  { error: "No se encontr\xF3 ID de transacci\xF3n" },
1344
1369
  { status: 400 }
1345
1370
  );
@@ -1361,15 +1386,15 @@ function createRefundHandler(deps) {
1361
1386
  );
1362
1387
  }
1363
1388
  }
1364
- return NextResponse.json({ success: true, detail: result.detail });
1389
+ return json({ success: true, detail: result.detail });
1365
1390
  }
1366
- return NextResponse.json(
1391
+ return json(
1367
1392
  { error: result.detail || "Error al procesar reembolso" },
1368
1393
  { status: 400 }
1369
1394
  );
1370
1395
  } catch (error) {
1371
1396
  logger.error("Refund error:", error);
1372
- return NextResponse.json(
1397
+ return json(
1373
1398
  { error: "Error interno del servidor" },
1374
1399
  { status: 500 }
1375
1400
  );
@@ -1380,20 +1405,20 @@ function createInitCheckoutHandler(deps) {
1380
1405
  const logger = deps.logger ?? console;
1381
1406
  const { auth } = deps.firebase;
1382
1407
  return async function POST(request) {
1383
- const sessionCookie = request.cookies.get("__session")?.value;
1408
+ const sessionCookie = getCookie(request, "__session");
1384
1409
  if (!sessionCookie || !auth) {
1385
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1410
+ return json({ error: "No autorizado" }, { status: 401 });
1386
1411
  }
1387
1412
  let decodedToken;
1388
1413
  try {
1389
1414
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1390
1415
  } catch {
1391
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1416
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1392
1417
  }
1393
1418
  try {
1394
1419
  const { amount, vat, description, devReference } = await request.json();
1395
1420
  if (!amount || !devReference) {
1396
- return NextResponse.json(
1421
+ return json(
1397
1422
  { error: "amount y devReference son requeridos" },
1398
1423
  { status: 400 }
1399
1424
  );
@@ -1421,36 +1446,38 @@ function createInitCheckoutHandler(deps) {
1421
1446
  }
1422
1447
  });
1423
1448
  if (result.error) {
1424
- return NextResponse.json(
1449
+ return json(
1425
1450
  { error: result.error.description || "Error al inicializar checkout" },
1426
1451
  { status: 400 }
1427
1452
  );
1428
1453
  }
1429
1454
  if (!result.reference) {
1430
- return NextResponse.json(
1455
+ return json(
1431
1456
  { error: "No se recibi\xF3 referencia de checkout" },
1432
1457
  { status: 500 }
1433
1458
  );
1434
1459
  }
1435
- return NextResponse.json({
1460
+ return json({
1436
1461
  reference: result.reference,
1437
1462
  checkoutUrl: result.checkout_url
1438
1463
  });
1439
1464
  } catch (error) {
1440
1465
  logger.error("Init checkout error:", error);
1441
- return NextResponse.json(
1466
+ return json(
1442
1467
  { error: "Error al inicializar checkout" },
1443
1468
  { status: 500 }
1444
1469
  );
1445
1470
  }
1446
1471
  };
1447
1472
  }
1473
+
1474
+ // src/handlers/cards.ts
1448
1475
  function createCardsHandler(deps) {
1449
1476
  const logger = deps.logger ?? console;
1450
1477
  const { db, auth } = deps.firebase;
1451
1478
  const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
1452
1479
  async function verifySession(request) {
1453
- const sessionCookie = request.cookies.get("__session")?.value;
1480
+ const sessionCookie = getCookie(request, "__session");
1454
1481
  if (!sessionCookie || !auth) return null;
1455
1482
  try {
1456
1483
  return await auth.verifySessionCookie(sessionCookie, true);
@@ -1461,7 +1488,7 @@ function createCardsHandler(deps) {
1461
1488
  const GET = async function GET2(request) {
1462
1489
  const decoded = await verifySession(request);
1463
1490
  if (!decoded) {
1464
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1491
+ return json({ error: "No autorizado" }, { status: 401 });
1465
1492
  }
1466
1493
  try {
1467
1494
  const result = await listCards(decoded.uid);
@@ -1485,12 +1512,12 @@ function createCardsHandler(deps) {
1485
1512
  const enrichedCards = cards.map(
1486
1513
  (c) => c.status === "review" && verifiedTokens.has(c.token) ? { ...c, status: "valid" } : c
1487
1514
  );
1488
- return NextResponse.json({ cards: enrichedCards });
1515
+ return json({ cards: enrichedCards });
1489
1516
  }
1490
- return NextResponse.json({ cards });
1517
+ return json({ cards });
1491
1518
  } catch (error) {
1492
1519
  logger.error("Error listing cards:", error);
1493
- return NextResponse.json(
1520
+ return json(
1494
1521
  { error: "Error al obtener tarjetas" },
1495
1522
  { status: 500 }
1496
1523
  );
@@ -1499,12 +1526,12 @@ function createCardsHandler(deps) {
1499
1526
  const DELETE = async function DELETE2(request) {
1500
1527
  const decoded = await verifySession(request);
1501
1528
  if (!decoded) {
1502
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1529
+ return json({ error: "No autorizado" }, { status: 401 });
1503
1530
  }
1504
1531
  try {
1505
1532
  const { token } = await request.json();
1506
1533
  if (!token) {
1507
- return NextResponse.json(
1534
+ return json(
1508
1535
  { error: "Token de tarjeta requerido" },
1509
1536
  { status: 400 }
1510
1537
  );
@@ -1514,7 +1541,7 @@ function createCardsHandler(deps) {
1514
1541
  const resultWithError = result;
1515
1542
  if (resultWithError.error) {
1516
1543
  logger.error("[cards/DELETE] Nuvei delete failed:", JSON.stringify(result));
1517
- return NextResponse.json(
1544
+ return json(
1518
1545
  { error: "No se pudo eliminar la tarjeta en Nuvei", detail: result },
1519
1546
  { status: 400 }
1520
1547
  );
@@ -1525,10 +1552,10 @@ function createCardsHandler(deps) {
1525
1552
  } catch {
1526
1553
  }
1527
1554
  }
1528
- return NextResponse.json(result);
1555
+ return json(result);
1529
1556
  } catch (error) {
1530
1557
  logger.error("Error deleting card:", error);
1531
- return NextResponse.json(
1558
+ return json(
1532
1559
  { error: "Error al eliminar tarjeta" },
1533
1560
  { status: 500 }
1534
1561
  );
@@ -1536,25 +1563,27 @@ function createCardsHandler(deps) {
1536
1563
  };
1537
1564
  return { GET, DELETE };
1538
1565
  }
1566
+
1567
+ // src/handlers/verify.ts
1539
1568
  function createVerifyHandler(deps) {
1540
1569
  const logger = deps.logger ?? console;
1541
1570
  const { db, auth } = deps.firebase;
1542
1571
  const verifiedCardsEnabled = deps.enableVerifiedCardsTracking !== false;
1543
1572
  return async function POST(request) {
1544
- const sessionCookie = request.cookies.get("__session")?.value;
1573
+ const sessionCookie = getCookie(request, "__session");
1545
1574
  if (!sessionCookie || !auth) {
1546
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1575
+ return json({ error: "No autorizado" }, { status: 401 });
1547
1576
  }
1548
1577
  let decodedToken;
1549
1578
  try {
1550
1579
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1551
1580
  } catch {
1552
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1581
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1553
1582
  }
1554
1583
  try {
1555
1584
  const { cardToken, transactionReference, value } = await request.json();
1556
1585
  if (!cardToken && !transactionReference || !value) {
1557
- return NextResponse.json(
1586
+ return json(
1558
1587
  { error: "transactionReference y value son requeridos" },
1559
1588
  { status: 400 }
1560
1589
  );
@@ -1565,7 +1594,7 @@ function createVerifyHandler(deps) {
1565
1594
  value
1566
1595
  });
1567
1596
  if (result.error) {
1568
- return NextResponse.json(
1597
+ return json(
1569
1598
  { error: result.error.description || "Error de verificaci\xF3n" },
1570
1599
  { status: 400 }
1571
1600
  );
@@ -1577,41 +1606,43 @@ function createVerifyHandler(deps) {
1577
1606
  logger.error("Failed to persist verified card:", err);
1578
1607
  }
1579
1608
  }
1580
- return NextResponse.json({
1609
+ return json({
1581
1610
  success: true,
1582
1611
  transaction: result.transaction
1583
1612
  });
1584
1613
  } catch (error) {
1585
1614
  logger.error("Card verify error:", error);
1586
- return NextResponse.json(
1615
+ return json(
1587
1616
  { error: "Error al verificar tarjeta" },
1588
1617
  { status: 500 }
1589
1618
  );
1590
1619
  }
1591
1620
  };
1592
1621
  }
1622
+
1623
+ // src/handlers/test-charge.ts
1593
1624
  function createTestChargeHandler(deps) {
1594
1625
  const logger = deps.logger ?? console;
1595
1626
  const { auth } = deps.firebase;
1596
1627
  const defaultDescription = deps.defaultDescription ?? "Test charge";
1597
1628
  return async function POST(request) {
1598
1629
  if (process.env.NUVEI_ENV === "prod") {
1599
- return NextResponse.json({ error: "Not available" }, { status: 404 });
1630
+ return json({ error: "Not available" }, { status: 404 });
1600
1631
  }
1601
- const sessionCookie = request.cookies.get("__session")?.value;
1632
+ const sessionCookie = getCookie(request, "__session");
1602
1633
  if (!sessionCookie || !auth) {
1603
- return NextResponse.json({ error: "No autorizado" }, { status: 401 });
1634
+ return json({ error: "No autorizado" }, { status: 401 });
1604
1635
  }
1605
1636
  let decodedToken;
1606
1637
  try {
1607
1638
  decodedToken = await auth.verifySessionCookie(sessionCookie, true);
1608
1639
  } catch {
1609
- return NextResponse.json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1640
+ return json({ error: "Sesi\xF3n inv\xE1lida" }, { status: 401 });
1610
1641
  }
1611
1642
  try {
1612
1643
  const { token, amount, vat, description, devReference } = await request.json();
1613
1644
  if (!token || !amount || !devReference) {
1614
- return NextResponse.json(
1645
+ return json(
1615
1646
  { error: "token, amount y devReference son requeridos" },
1616
1647
  { status: 400 }
1617
1648
  );
@@ -1627,13 +1658,13 @@ function createTestChargeHandler(deps) {
1627
1658
  });
1628
1659
  logger.log("Test charge result:", JSON.stringify(result, null, 2));
1629
1660
  if (result.transaction && result.transaction.status === "success" && result.transaction.status_detail === 3) {
1630
- return NextResponse.json({
1661
+ return json({
1631
1662
  success: true,
1632
1663
  transaction: result.transaction,
1633
1664
  card: result.card
1634
1665
  });
1635
1666
  }
1636
- return NextResponse.json(
1667
+ return json(
1637
1668
  {
1638
1669
  success: false,
1639
1670
  error: result.transaction?.message || result.error?.description || "Pago rechazado",
@@ -1643,7 +1674,7 @@ function createTestChargeHandler(deps) {
1643
1674
  );
1644
1675
  } catch (error) {
1645
1676
  logger.error("Test charge error:", error);
1646
- return NextResponse.json(
1677
+ return json(
1647
1678
  { error: "Error interno del servidor" },
1648
1679
  { status: 500 }
1649
1680
  );
@@ -1651,6 +1682,57 @@ function createTestChargeHandler(deps) {
1651
1682
  };
1652
1683
  }
1653
1684
 
1654
- export { create3dsCallbackHandler, create3dsCompleteHandler, create3dsTimeoutHandler, createCardsHandler, createChargeHandler, createInitCheckoutHandler, createRefundHandler, createTestChargeHandler, createVerifyHandler, createWebhookHandler };
1685
+ // src/handlers/proxy.ts
1686
+ function createNuveiProxyHandler(deps = {}) {
1687
+ const logger = deps.logger ?? console;
1688
+ return async function POST(request) {
1689
+ if (request.method !== "POST") {
1690
+ return json({ error: "Method not allowed" }, { status: 405 });
1691
+ }
1692
+ let parsed;
1693
+ try {
1694
+ parsed = await request.json();
1695
+ } catch {
1696
+ return json({ error: "Invalid JSON body" }, { status: 400 });
1697
+ }
1698
+ const { path, method, body: nuveiBody } = parsed;
1699
+ const authToken = request.headers.get("x-nuvei-auth-token");
1700
+ if (!path || !method || !authToken) {
1701
+ return json(
1702
+ { error: "Missing path, method, or x-nuvei-auth-token header" },
1703
+ { status: 400 }
1704
+ );
1705
+ }
1706
+ const env = request.headers.get("x-nuvei-env") || "prod";
1707
+ const baseUrl = env === "prod" ? "https://ccapi.paymentez.com" : "https://ccapi-stg.paymentez.com";
1708
+ const url = `${baseUrl}${path}`;
1709
+ try {
1710
+ const options = {
1711
+ method,
1712
+ headers: {
1713
+ "Content-Type": "application/json",
1714
+ "Auth-Token": authToken
1715
+ }
1716
+ };
1717
+ if (nuveiBody && method === "POST") {
1718
+ options.body = JSON.stringify(nuveiBody);
1719
+ }
1720
+ const response = await fetch(url, options);
1721
+ const responseBody = await response.text();
1722
+ return new Response(responseBody, {
1723
+ status: response.status,
1724
+ headers: { "content-type": "application/json; charset=utf-8" }
1725
+ });
1726
+ } catch (err) {
1727
+ logger.error("[nuveiProxy] Error:", err);
1728
+ return json(
1729
+ { error: "Proxy error: " + (err instanceof Error ? err.message : String(err)) },
1730
+ { status: 500 }
1731
+ );
1732
+ }
1733
+ };
1734
+ }
1735
+
1736
+ export { create3dsCallbackHandler, create3dsCompleteHandler, create3dsTimeoutHandler, createCardsHandler, createChargeHandler, createInitCheckoutHandler, createNuveiProxyHandler, createRefundHandler, createTestChargeHandler, createVerifyHandler, createWebhookHandler };
1655
1737
  //# sourceMappingURL=index.js.map
1656
1738
  //# sourceMappingURL=index.js.map