@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.
- package/README.md +29 -0
- package/dist/adapters/index.cjs +54 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +35 -0
- package/dist/adapters/index.d.ts +35 -0
- package/dist/adapters/index.js +51 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/handlers/index.cjs +273 -132
- package/dist/handlers/index.cjs.map +1 -1
- package/dist/handlers/index.d.cts +34 -121
- package/dist/handlers/index.d.ts +34 -121
- package/dist/handlers/index.js +273 -133
- package/dist/handlers/index.js.map +1 -1
- package/package.json +17 -9
package/dist/handlers/index.js
CHANGED
|
@@ -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/
|
|
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
|
|
228
|
+
const sessionCookie = getCookie(request, "__session");
|
|
210
229
|
if (!sessionCookie || !auth) {
|
|
211
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
292
|
+
return json({ error: "Datos incompletos" }, { status: 400 });
|
|
274
293
|
}
|
|
275
294
|
if (decodedToken.uid !== userId) {
|
|
276
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
660
|
+
return json({ error: failedErrorMsg }, { status: 400 });
|
|
642
661
|
} catch (error) {
|
|
643
662
|
logger.error("Payment charge error:", error);
|
|
644
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
907
|
+
return json({ received: true });
|
|
889
908
|
} catch (error) {
|
|
890
909
|
logger.error("Webhook processing error:", error);
|
|
891
|
-
return
|
|
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: "${
|
|
907
|
-
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ón...
|
|
920
977
|
</p>
|
|
921
978
|
</body></html>`;
|
|
922
|
-
return new
|
|
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ón...
|
|
|
927
984
|
function create3dsCallbackHandler(deps) {
|
|
928
985
|
const logger = deps.logger ?? console;
|
|
929
986
|
const { db } = deps.firebase;
|
|
930
|
-
async function
|
|
931
|
-
if (orderId
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
|
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
|
|
951
|
-
|
|
952
|
-
|
|
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
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
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
|
|
966
|
-
const orderId =
|
|
967
|
-
const
|
|
968
|
-
const
|
|
969
|
-
await
|
|
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
|
|
1065
|
+
const sessionCookie = getCookie(request, "__session");
|
|
987
1066
|
if (!sessionCookie || !auth) {
|
|
988
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1101
|
+
return json({ error: "Pago ya procesado" }, { status: 409 });
|
|
1023
1102
|
}
|
|
1024
1103
|
if (orderData.status !== "3ds-pending" && orderData.status !== "otp-pending") {
|
|
1025
|
-
return
|
|
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
|
|
1131
|
+
return json({ error: msg }, { status: 400 });
|
|
1053
1132
|
}
|
|
1054
1133
|
const transactionId = orderData.nuveiTransactionId || bodyTxId;
|
|
1055
1134
|
if (!transactionId) {
|
|
1056
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1337
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1257
1338
|
if (!sessionCookie || !auth) {
|
|
1258
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1358
|
+
return json({ error: "Usuario no coincide" }, { status: 403 });
|
|
1278
1359
|
}
|
|
1279
1360
|
if (orderData.status !== "3ds-pending") {
|
|
1280
|
-
return
|
|
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
|
|
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
|
|
1392
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1310
1393
|
if (!sessionCookie || !auth) {
|
|
1311
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1447
|
+
return json({ success: true, detail: result.detail });
|
|
1365
1448
|
}
|
|
1366
|
-
return
|
|
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
|
|
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
|
|
1466
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1384
1467
|
if (!sessionCookie || !auth) {
|
|
1385
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1513
|
+
return json(
|
|
1431
1514
|
{ error: "No se recibi\xF3 referencia de checkout" },
|
|
1432
1515
|
{ status: 500 }
|
|
1433
1516
|
);
|
|
1434
1517
|
}
|
|
1435
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1573
|
+
return json({ cards: enrichedCards });
|
|
1489
1574
|
}
|
|
1490
|
-
return
|
|
1575
|
+
return json({ cards });
|
|
1491
1576
|
} catch (error) {
|
|
1492
1577
|
logger.error("Error listing cards:", error);
|
|
1493
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1613
|
+
return json(result);
|
|
1529
1614
|
} catch (error) {
|
|
1530
1615
|
logger.error("Error deleting card:", error);
|
|
1531
|
-
return
|
|
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
|
|
1631
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1545
1632
|
if (!sessionCookie || !auth) {
|
|
1546
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1688
|
+
return json({ error: "Not available" }, { status: 404 });
|
|
1600
1689
|
}
|
|
1601
|
-
const sessionCookie = request
|
|
1690
|
+
const sessionCookie = getCookie(request, "__session");
|
|
1602
1691
|
if (!sessionCookie || !auth) {
|
|
1603
|
-
return
|
|
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
|
|
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
|
|
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
|
|
1719
|
+
return json({
|
|
1631
1720
|
success: true,
|
|
1632
1721
|
transaction: result.transaction,
|
|
1633
1722
|
card: result.card
|
|
1634
1723
|
});
|
|
1635
1724
|
}
|
|
1636
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|