@ar-agents/mercadopago 0.9.0 → 0.11.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.
package/dist/index.cjs CHANGED
@@ -3,6 +3,75 @@
3
3
  var ai = require('ai');
4
4
  var zod = require('zod');
5
5
 
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // src/crypto.ts
17
+ var crypto_exports = {};
18
+ __export(crypto_exports, {
19
+ hmacSha256Hex: () => hmacSha256Hex,
20
+ sha256Hex: () => sha256Hex,
21
+ timingSafeEqualHex: () => timingSafeEqualHex
22
+ });
23
+ async function hmacSha256Hex(secret, message) {
24
+ const keyMaterial = await subtle.importKey(
25
+ "raw",
26
+ encoder.encode(secret),
27
+ { name: "HMAC", hash: "SHA-256" },
28
+ false,
29
+ ["sign"]
30
+ );
31
+ const sigBuf = await subtle.sign(
32
+ "HMAC",
33
+ keyMaterial,
34
+ encoder.encode(message)
35
+ );
36
+ return bufferToHex(sigBuf);
37
+ }
38
+ async function sha256Hex(input) {
39
+ const digest = await subtle.digest("SHA-256", encoder.encode(input));
40
+ return bufferToHex(digest);
41
+ }
42
+ function timingSafeEqualHex(a, b) {
43
+ if (a.length !== b.length) return false;
44
+ let diff = 0;
45
+ for (let i = 0; i < a.length; i++) {
46
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
47
+ }
48
+ return diff === 0;
49
+ }
50
+ function bufferToHex(buf) {
51
+ const bytes = new Uint8Array(buf);
52
+ let hex = "";
53
+ for (let i = 0; i < bytes.length; i++) {
54
+ const b = bytes[i];
55
+ hex += (b < 16 ? "0" : "") + b.toString(16);
56
+ }
57
+ return hex;
58
+ }
59
+ var subtle, encoder;
60
+ var init_crypto = __esm({
61
+ "src/crypto.ts"() {
62
+ subtle = (() => {
63
+ const c = globalThis.crypto;
64
+ if (!c?.subtle) {
65
+ throw new Error(
66
+ "@ar-agents/mercadopago: Web Crypto API is not available in this runtime. Use Node 18+, Vercel Edge Runtime, Cloudflare Workers, or any modern browser."
67
+ );
68
+ }
69
+ return c.subtle;
70
+ })();
71
+ encoder = new TextEncoder();
72
+ }
73
+ });
74
+
6
75
  // src/errors.ts
7
76
  var MercadoPagoError = class extends Error {
8
77
  constructor(message, status, endpoint, mpResponse) {
@@ -1334,53 +1403,8 @@ var MercadoPagoClient = class {
1334
1403
  }
1335
1404
  };
1336
1405
 
1337
- // src/crypto.ts
1338
- var subtle = (() => {
1339
- const c = globalThis.crypto;
1340
- if (!c?.subtle) {
1341
- throw new Error(
1342
- "@ar-agents/mercadopago: Web Crypto API is not available in this runtime. Use Node 18+, Vercel Edge Runtime, Cloudflare Workers, or any modern browser."
1343
- );
1344
- }
1345
- return c.subtle;
1346
- })();
1347
- var encoder = new TextEncoder();
1348
- async function hmacSha256Hex(secret, message) {
1349
- const keyMaterial = await subtle.importKey(
1350
- "raw",
1351
- encoder.encode(secret),
1352
- { name: "HMAC", hash: "SHA-256" },
1353
- false,
1354
- ["sign"]
1355
- );
1356
- const sigBuf = await subtle.sign(
1357
- "HMAC",
1358
- keyMaterial,
1359
- encoder.encode(message)
1360
- );
1361
- return bufferToHex(sigBuf);
1362
- }
1363
- async function sha256Hex(input) {
1364
- const digest = await subtle.digest("SHA-256", encoder.encode(input));
1365
- return bufferToHex(digest);
1366
- }
1367
- function timingSafeEqualHex(a, b) {
1368
- if (a.length !== b.length) return false;
1369
- let diff = 0;
1370
- for (let i = 0; i < a.length; i++) {
1371
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1372
- }
1373
- return diff === 0;
1374
- }
1375
- function bufferToHex(buf) {
1376
- const bytes = new Uint8Array(buf);
1377
- let hex = "";
1378
- for (let i = 0; i < bytes.length; i++) {
1379
- const b = bytes[i];
1380
- hex += (b < 16 ? "0" : "") + b.toString(16);
1381
- }
1382
- return hex;
1383
- }
1406
+ // src/tools.ts
1407
+ init_crypto();
1384
1408
  zod.z.enum(["MLA", "MLB", "MLM", "MCO", "MLC", "MLU"]);
1385
1409
  var CurrencyIdSchema = zod.z.enum(["ARS", "USD", "BRL", "MXN"]);
1386
1410
  var FrequencyTypeSchema = zod.z.enum(["months", "days"]);
@@ -1884,6 +1908,180 @@ function isExpiringSoon(expirationMs, skewSeconds = 300) {
1884
1908
  return Date.now() + skewSeconds * 1e3 >= expirationMs;
1885
1909
  }
1886
1910
 
1911
+ // src/ar-issuer-promos.ts
1912
+ var AHORA_PROGRAM_PROMOS = [
1913
+ {
1914
+ issuer: "*",
1915
+ // any AR-resident card
1916
+ paymentMethodId: "*",
1917
+ installments: 3,
1918
+ description: "Ahora 3 \u2014 3 cuotas sin inter\xE9s (programa nacional)",
1919
+ categories: ["electronics", "appliances", "clothing", "general"]
1920
+ },
1921
+ {
1922
+ issuer: "*",
1923
+ paymentMethodId: "*",
1924
+ installments: 6,
1925
+ description: "Ahora 6 \u2014 6 cuotas sin inter\xE9s (programa nacional, electrodom\xE9sticos l\xEDnea blanca)",
1926
+ categories: ["appliances"]
1927
+ },
1928
+ {
1929
+ issuer: "*",
1930
+ paymentMethodId: "*",
1931
+ installments: 12,
1932
+ description: "Ahora 12 \u2014 12 cuotas sin inter\xE9s (programa nacional)",
1933
+ categories: ["electronics", "appliances", "clothing"]
1934
+ },
1935
+ {
1936
+ issuer: "*",
1937
+ paymentMethodId: "*",
1938
+ installments: 18,
1939
+ description: "Ahora 18 \u2014 18 cuotas sin inter\xE9s (turismo nacional + electrodom\xE9sticos)",
1940
+ categories: ["appliances", "travel"]
1941
+ },
1942
+ {
1943
+ issuer: "*",
1944
+ paymentMethodId: "*",
1945
+ installments: 24,
1946
+ description: "Ahora 24 \u2014 24 cuotas sin inter\xE9s (electrodom\xE9sticos l\xEDnea blanca premium)",
1947
+ categories: ["appliances"]
1948
+ }
1949
+ ];
1950
+ var AR_ISSUER_PROMOS = [
1951
+ // Naranja X
1952
+ {
1953
+ issuer: "Naranja X",
1954
+ paymentMethodId: "naranja",
1955
+ installments: 3,
1956
+ description: "Naranja Z (Plan Z) \u2014 3 cuotas con CFT promocional, todos los rubros"
1957
+ },
1958
+ {
1959
+ issuer: "Naranja X",
1960
+ paymentMethodId: "naranja",
1961
+ installments: 6,
1962
+ description: "Naranja \u2014 6 cuotas sin inter\xE9s con comercios adheridos (electro/indumentaria)",
1963
+ daysOfWeek: ["thu"],
1964
+ categories: ["electronics", "appliances", "clothing"]
1965
+ },
1966
+ // Galicia
1967
+ {
1968
+ issuer: "Banco Galicia",
1969
+ paymentMethodId: "visa",
1970
+ installments: 12,
1971
+ description: "Galicia Eminent / Quiero! \u2014 12 cuotas sin inter\xE9s en supermercados (jueves)",
1972
+ daysOfWeek: ["thu"],
1973
+ categories: ["supermarket"]
1974
+ },
1975
+ {
1976
+ issuer: "Banco Galicia",
1977
+ paymentMethodId: "master",
1978
+ installments: 6,
1979
+ description: "Galicia \u2014 6 cuotas sin inter\xE9s en gastronom\xEDa (viernes y s\xE1bados)",
1980
+ daysOfWeek: ["fri", "sat"]
1981
+ },
1982
+ // Santander
1983
+ {
1984
+ issuer: "Banco Santander",
1985
+ paymentMethodId: "visa",
1986
+ installments: 6,
1987
+ description: "Santander Black / Platinum \u2014 6 cuotas sin inter\xE9s en cines + viajes",
1988
+ categories: ["travel"]
1989
+ },
1990
+ {
1991
+ issuer: "Banco Santander",
1992
+ paymentMethodId: "amex",
1993
+ installments: 9,
1994
+ description: "Santander American Express \u2014 9 cuotas sin inter\xE9s en supermercados (martes y mi\xE9rcoles)",
1995
+ daysOfWeek: ["tue", "wed"],
1996
+ categories: ["supermarket"]
1997
+ },
1998
+ // Macro
1999
+ {
2000
+ issuer: "Banco Macro",
2001
+ paymentMethodId: "visa",
2002
+ installments: 6,
2003
+ description: "Macro Selecta / Premia \u2014 6 cuotas sin inter\xE9s en farmacias y librer\xEDas",
2004
+ categories: ["health", "education"]
2005
+ },
2006
+ // BBVA
2007
+ {
2008
+ issuer: "BBVA Banco Franc\xE9s",
2009
+ paymentMethodId: "visa",
2010
+ installments: 3,
2011
+ description: "BBVA Lat / Black \u2014 3 cuotas sin inter\xE9s en restaurantes (lunes a mi\xE9rcoles)",
2012
+ daysOfWeek: ["mon", "tue", "wed"]
2013
+ },
2014
+ // ICBC
2015
+ {
2016
+ issuer: "ICBC",
2017
+ paymentMethodId: "visa",
2018
+ installments: 6,
2019
+ description: "ICBC Cuenta Corriente \u2014 6 cuotas sin inter\xE9s en electro y indumentaria",
2020
+ categories: ["electronics", "appliances", "clothing"]
2021
+ },
2022
+ // Patagonia
2023
+ {
2024
+ issuer: "Banco Patagonia",
2025
+ paymentMethodId: "visa",
2026
+ installments: 3,
2027
+ description: "Patagonia 365 / Eminent \u2014 3 cuotas sin inter\xE9s en supermercados (s\xE1bados)",
2028
+ daysOfWeek: ["sat"],
2029
+ categories: ["supermarket"]
2030
+ },
2031
+ // Banco Nación
2032
+ {
2033
+ issuer: "Banco de la Naci\xF3n Argentina",
2034
+ paymentMethodId: "visa",
2035
+ installments: 12,
2036
+ description: "BNA \u2014 12 cuotas sin inter\xE9s con plan 'Ahora 12' del programa nacional",
2037
+ categories: ["electronics", "appliances", "clothing"]
2038
+ },
2039
+ // Banco Provincia
2040
+ {
2041
+ issuer: "Banco de la Provincia de Buenos Aires",
2042
+ paymentMethodId: "visa",
2043
+ installments: 6,
2044
+ description: "Cuenta DNI \u2014 6 cuotas sin inter\xE9s (mensual cap aplica)",
2045
+ maxAmountArs: 2e5
2046
+ },
2047
+ // Banco Ciudad
2048
+ {
2049
+ issuer: "Banco de la Ciudad de Buenos Aires",
2050
+ paymentMethodId: "visa",
2051
+ installments: 12,
2052
+ description: "Banco Ciudad \u2014 12 cuotas sin inter\xE9s en electrodom\xE9sticos (Plan Sue\xF1os)",
2053
+ categories: ["appliances"]
2054
+ }
2055
+ ];
2056
+ function findApplicablePromos(args) {
2057
+ const date = args.date ?? /* @__PURE__ */ new Date();
2058
+ const dayOfWeek = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"][date.getDay()];
2059
+ const candidates = [
2060
+ ...args.includeAhoraProgram !== false ? AHORA_PROGRAM_PROMOS : [],
2061
+ ...AR_ISSUER_PROMOS
2062
+ ];
2063
+ return candidates.filter((promo) => {
2064
+ if (args.issuer !== void 0 && promo.issuer !== "*" && promo.issuer !== args.issuer) {
2065
+ return false;
2066
+ }
2067
+ if (args.paymentMethodId !== void 0 && promo.paymentMethodId !== "*" && promo.paymentMethodId !== args.paymentMethodId) {
2068
+ return false;
2069
+ }
2070
+ if (promo.daysOfWeek && promo.daysOfWeek.length > 0 && !promo.daysOfWeek.includes(dayOfWeek)) {
2071
+ return false;
2072
+ }
2073
+ if (args.category !== void 0 && promo.categories && promo.categories.length > 0 && !promo.categories.includes(args.category)) {
2074
+ return false;
2075
+ }
2076
+ if (args.amountArs !== void 0 && promo.minAmountArs !== void 0 && args.amountArs < promo.minAmountArs) {
2077
+ return false;
2078
+ }
2079
+ if (promo.startDate && date < new Date(promo.startDate)) return false;
2080
+ if (promo.endDate && date > new Date(promo.endDate)) return false;
2081
+ return true;
2082
+ });
2083
+ }
2084
+
1887
2085
  // src/helpers.ts
1888
2086
  function computeMarketplaceFee(amountArs, rule) {
1889
2087
  if (amountArs <= 0) return 0;
@@ -2121,6 +2319,487 @@ function explainPaymentStatus(payment) {
2121
2319
  }
2122
2320
  }
2123
2321
 
2322
+ // src/pagination.ts
2323
+ async function* paginate(fetchPage, opts) {
2324
+ const pageSize = opts.pageSize ?? 100;
2325
+ const concurrency = Math.max(1, opts.concurrency ?? 1);
2326
+ let yielded = 0;
2327
+ let offset = 0;
2328
+ let knownTotal = void 0;
2329
+ while (true) {
2330
+ if (opts.maxItems !== void 0 && yielded >= opts.maxItems) return;
2331
+ const inFlight = [];
2332
+ for (let i = 0; i < concurrency; i++) {
2333
+ const pageOffset = offset + i * pageSize;
2334
+ if (knownTotal !== void 0 && pageOffset >= knownTotal) break;
2335
+ inFlight.push(fetchPage(pageOffset, pageSize));
2336
+ }
2337
+ if (inFlight.length === 0) return;
2338
+ const pages = await Promise.all(inFlight);
2339
+ let allEmpty = true;
2340
+ for (const page of pages) {
2341
+ const items = opts.extractItems(page);
2342
+ const total = opts.extractTotal?.(page);
2343
+ if (total !== void 0) knownTotal = total;
2344
+ if (items.length > 0) allEmpty = false;
2345
+ for (const item of items) {
2346
+ if (opts.maxItems !== void 0 && yielded >= opts.maxItems) return;
2347
+ yield item;
2348
+ yielded++;
2349
+ }
2350
+ }
2351
+ if (allEmpty) return;
2352
+ offset += pages.length * pageSize;
2353
+ if (knownTotal !== void 0 && offset >= knownTotal) return;
2354
+ }
2355
+ }
2356
+ async function collect(iter) {
2357
+ const out = [];
2358
+ for await (const item of iter) out.push(item);
2359
+ return out;
2360
+ }
2361
+ function paginatePayments(client, filter = {}, opts = {}) {
2362
+ return paginate(
2363
+ (offset, limit) => client.searchPayments({ ...filter, offset, limit }),
2364
+ {
2365
+ extractItems: (p) => p.results ?? [],
2366
+ extractTotal: (p) => p.paging?.total,
2367
+ ...opts
2368
+ }
2369
+ );
2370
+ }
2371
+ function paginateSubscriptions(client, filter = {}, opts = {}) {
2372
+ return paginate(
2373
+ (offset, limit) => client.searchPreapprovals({ ...filter, offset, limit }),
2374
+ {
2375
+ extractItems: (p) => p.results,
2376
+ extractTotal: (p) => p.paging.total,
2377
+ ...opts
2378
+ }
2379
+ );
2380
+ }
2381
+ function paginateAccountMovements(client, filter = {}, opts = {}) {
2382
+ return paginate(
2383
+ (offset, limit) => client.listAccountMovements({ ...filter, offset, limit }),
2384
+ {
2385
+ extractItems: (p) => p.movements,
2386
+ extractTotal: (p) => p.paging.total,
2387
+ ...opts
2388
+ }
2389
+ );
2390
+ }
2391
+ function paginateSettlements(client, filter = {}, opts = {}) {
2392
+ return paginate(
2393
+ (offset, limit) => client.listSettlements({ ...filter, offset, limit }),
2394
+ {
2395
+ extractItems: (p) => p.settlements,
2396
+ extractTotal: (p) => p.paging.total,
2397
+ ...opts
2398
+ }
2399
+ );
2400
+ }
2401
+ function paginateMerchantOrders(client, filter = {}, opts = {}) {
2402
+ return paginate(
2403
+ (offset, limit) => client.searchMerchantOrders({ ...filter, offset, limit }),
2404
+ {
2405
+ extractItems: (p) => p.elements,
2406
+ extractTotal: (p) => p.paging.total,
2407
+ ...opts
2408
+ }
2409
+ );
2410
+ }
2411
+ function paginateSubscriptionPlans(client, filter = {}, opts = {}) {
2412
+ return paginate(
2413
+ (offset, limit) => client.listSubscriptionPlans({ ...filter, offset, limit }),
2414
+ {
2415
+ extractItems: (p) => p.results,
2416
+ extractTotal: (p) => p.paging.total,
2417
+ ...opts
2418
+ }
2419
+ );
2420
+ }
2421
+ function paginateSubscriptionPayments(client, preapprovalId, opts = {}) {
2422
+ return paginate(
2423
+ (offset, limit) => client.listSubscriptionPayments(preapprovalId, { offset, limit }),
2424
+ {
2425
+ extractItems: (p) => p.results,
2426
+ extractTotal: (p) => p.paging.total,
2427
+ ...opts
2428
+ }
2429
+ );
2430
+ }
2431
+
2432
+ // src/tax-id.ts
2433
+ function validateTaxId(input, type) {
2434
+ switch (type) {
2435
+ case "DNI":
2436
+ return validateAR_DNI(input);
2437
+ case "CUIT":
2438
+ case "CUIL":
2439
+ return validateAR_CUIT(input, type);
2440
+ case "CPF":
2441
+ return validateBR_CPF(input);
2442
+ case "CNPJ":
2443
+ return validateBR_CNPJ(input);
2444
+ case "RFC":
2445
+ return validateMX_RFC(input);
2446
+ case "RUT_CL":
2447
+ return validateCL_RUT(input);
2448
+ case "NIT":
2449
+ return validateCO_NIT(input);
2450
+ case "RUT_UY":
2451
+ return validateUY_RUT(input);
2452
+ case "RUC":
2453
+ return validatePE_RUC(input);
2454
+ }
2455
+ }
2456
+ function validateAR_DNI(input) {
2457
+ const normalized = (input ?? "").replace(/\D/g, "");
2458
+ if (normalized.length === 0) {
2459
+ return failure(normalized, "DNI", "AR", "DNI vac\xEDo.");
2460
+ }
2461
+ if (normalized.length < 7 || normalized.length > 8) {
2462
+ return failure(
2463
+ normalized,
2464
+ "DNI",
2465
+ "AR",
2466
+ `Debe tener 7 u 8 d\xEDgitos; recib\xED ${normalized.length}.`
2467
+ );
2468
+ }
2469
+ return {
2470
+ valid: true,
2471
+ normalized,
2472
+ formatted: normalized.replace(/^(\d{1,2})(\d{3})(\d{3})$/, "$1.$2.$3"),
2473
+ type: "DNI",
2474
+ country: "AR",
2475
+ error: null
2476
+ };
2477
+ }
2478
+ var AR_CHECK_WEIGHTS = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
2479
+ function validateAR_CUIT(input, type) {
2480
+ const normalized = (input ?? "").replace(/\D/g, "");
2481
+ if (normalized.length !== 11) {
2482
+ return failure(
2483
+ normalized,
2484
+ type,
2485
+ "AR",
2486
+ `Debe tener 11 d\xEDgitos; recib\xED ${normalized.length}.`
2487
+ );
2488
+ }
2489
+ const sum = AR_CHECK_WEIGHTS.reduce(
2490
+ (acc, w, i) => acc + w * Number(normalized[i]),
2491
+ 0
2492
+ );
2493
+ const remainder = sum % 11;
2494
+ const expected = remainder === 0 ? 0 : remainder === 1 ? 9 : 11 - remainder;
2495
+ const actual = Number(normalized[10]);
2496
+ if (actual !== expected) {
2497
+ return failure(
2498
+ normalized,
2499
+ type,
2500
+ "AR",
2501
+ `D\xEDgito verificador inv\xE1lido. Esperado: ${expected}, recibido: ${actual}.`
2502
+ );
2503
+ }
2504
+ return {
2505
+ valid: true,
2506
+ normalized,
2507
+ formatted: `${normalized.slice(0, 2)}-${normalized.slice(2, 10)}-${normalized.slice(10)}`,
2508
+ type,
2509
+ country: "AR",
2510
+ error: null
2511
+ };
2512
+ }
2513
+ function validateBR_CPF(input) {
2514
+ const normalized = (input ?? "").replace(/\D/g, "");
2515
+ if (normalized.length !== 11) {
2516
+ return failure(
2517
+ normalized,
2518
+ "CPF",
2519
+ "BR",
2520
+ `Debe tener 11 d\xEDgitos; recib\xED ${normalized.length}.`
2521
+ );
2522
+ }
2523
+ if (/^(\d)\1{10}$/.test(normalized)) {
2524
+ return failure(normalized, "CPF", "BR", "CPF inv\xE1lido (todos los d\xEDgitos iguales).");
2525
+ }
2526
+ const computeDigit = (slice, weights) => {
2527
+ const sum = weights.reduce((acc, w, i) => acc + w * Number(slice[i]), 0);
2528
+ const r = sum * 10 % 11;
2529
+ return r === 10 ? 0 : r;
2530
+ };
2531
+ const d1 = computeDigit(normalized.slice(0, 9), [10, 9, 8, 7, 6, 5, 4, 3, 2]);
2532
+ const d2 = computeDigit(
2533
+ normalized.slice(0, 10),
2534
+ [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]
2535
+ );
2536
+ if (d1 !== Number(normalized[9]) || d2 !== Number(normalized[10])) {
2537
+ return failure(normalized, "CPF", "BR", "D\xEDgitos verificadores inv\xE1lidos.");
2538
+ }
2539
+ return {
2540
+ valid: true,
2541
+ normalized,
2542
+ formatted: normalized.replace(
2543
+ /^(\d{3})(\d{3})(\d{3})(\d{2})$/,
2544
+ "$1.$2.$3-$4"
2545
+ ),
2546
+ type: "CPF",
2547
+ country: "BR",
2548
+ error: null
2549
+ };
2550
+ }
2551
+ function validateBR_CNPJ(input) {
2552
+ const normalized = (input ?? "").replace(/\D/g, "");
2553
+ if (normalized.length !== 14) {
2554
+ return failure(
2555
+ normalized,
2556
+ "CNPJ",
2557
+ "BR",
2558
+ `Debe tener 14 d\xEDgitos; recib\xED ${normalized.length}.`
2559
+ );
2560
+ }
2561
+ if (/^(\d)\1{13}$/.test(normalized)) {
2562
+ return failure(normalized, "CNPJ", "BR", "CNPJ inv\xE1lido (todos los d\xEDgitos iguales).");
2563
+ }
2564
+ const computeDigit = (slice, weights) => {
2565
+ const sum = weights.reduce((acc, w, i) => acc + w * Number(slice[i]), 0);
2566
+ const r = sum % 11;
2567
+ return r < 2 ? 0 : 11 - r;
2568
+ };
2569
+ const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
2570
+ const w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
2571
+ const d1 = computeDigit(normalized.slice(0, 12), w1);
2572
+ const d2 = computeDigit(normalized.slice(0, 13), w2);
2573
+ if (d1 !== Number(normalized[12]) || d2 !== Number(normalized[13])) {
2574
+ return failure(normalized, "CNPJ", "BR", "D\xEDgitos verificadores inv\xE1lidos.");
2575
+ }
2576
+ return {
2577
+ valid: true,
2578
+ normalized,
2579
+ formatted: normalized.replace(
2580
+ /^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/,
2581
+ "$1.$2.$3/$4-$5"
2582
+ ),
2583
+ type: "CNPJ",
2584
+ country: "BR",
2585
+ error: null
2586
+ };
2587
+ }
2588
+ function validateMX_RFC(input) {
2589
+ const normalized = (input ?? "").trim().toUpperCase().replace(/[\s-]/g, "");
2590
+ const pfPattern = /^[A-ZÑ&]{4}\d{6}[A-Z\d]{3}$/;
2591
+ const pmPattern = /^[A-ZÑ&]{3}\d{6}[A-Z\d]{3}$/;
2592
+ if (!pfPattern.test(normalized) && !pmPattern.test(normalized)) {
2593
+ return failure(
2594
+ normalized,
2595
+ "RFC",
2596
+ "MX",
2597
+ "RFC mal formado. Persona f\xEDsica: 4 letras + YYMMDD + 3 alfanum\xE9ricos. Persona moral: 3 letras + YYMMDD + 3 alfanum\xE9ricos."
2598
+ );
2599
+ }
2600
+ return {
2601
+ valid: true,
2602
+ normalized,
2603
+ formatted: normalized,
2604
+ type: "RFC",
2605
+ country: "MX",
2606
+ error: null
2607
+ };
2608
+ }
2609
+ function validateCL_RUT(input) {
2610
+ const cleaned = (input ?? "").replace(/[^0-9kK]/g, "").toUpperCase();
2611
+ if (cleaned.length < 2) {
2612
+ return failure(cleaned, "RUT_CL", "CL", "RUT vac\xEDo o muy corto.");
2613
+ }
2614
+ const body = cleaned.slice(0, -1);
2615
+ const dv = cleaned.slice(-1);
2616
+ if (!/^\d+$/.test(body)) {
2617
+ return failure(cleaned, "RUT_CL", "CL", "Cuerpo del RUT debe ser num\xE9rico.");
2618
+ }
2619
+ let sum = 0;
2620
+ let multiplier = 2;
2621
+ for (let i = body.length - 1; i >= 0; i--) {
2622
+ sum += Number(body[i]) * multiplier;
2623
+ multiplier = multiplier === 7 ? 2 : multiplier + 1;
2624
+ }
2625
+ const r = 11 - sum % 11;
2626
+ const expected = r === 11 ? "0" : r === 10 ? "K" : String(r);
2627
+ if (dv !== expected) {
2628
+ return failure(
2629
+ cleaned,
2630
+ "RUT_CL",
2631
+ "CL",
2632
+ `D\xEDgito verificador inv\xE1lido. Esperado: ${expected}, recibido: ${dv}.`
2633
+ );
2634
+ }
2635
+ return {
2636
+ valid: true,
2637
+ normalized: cleaned,
2638
+ formatted: `${formatThousands(body)}-${dv}`,
2639
+ type: "RUT_CL",
2640
+ country: "CL",
2641
+ error: null
2642
+ };
2643
+ }
2644
+ var CO_NIT_WEIGHTS = [3, 7, 13, 17, 19, 23, 29, 37, 41, 43, 47, 53, 59, 67, 71];
2645
+ function validateCO_NIT(input) {
2646
+ const cleaned = (input ?? "").replace(/[^0-9-]/g, "");
2647
+ const parts = cleaned.split("-");
2648
+ if (parts.length !== 2 || !/^\d+$/.test(parts[0]) || !/^\d$/.test(parts[1])) {
2649
+ return failure(
2650
+ cleaned,
2651
+ "NIT",
2652
+ "CO",
2653
+ "NIT debe tener formato: d\xEDgitos + '-' + d\xEDgito verificador."
2654
+ );
2655
+ }
2656
+ const body = parts[0];
2657
+ const dv = Number(parts[1]);
2658
+ if (body.length > CO_NIT_WEIGHTS.length) {
2659
+ return failure(cleaned, "NIT", "CO", "NIT excesivamente largo.");
2660
+ }
2661
+ let sum = 0;
2662
+ for (let i = 0; i < body.length; i++) {
2663
+ sum += Number(body[body.length - 1 - i]) * CO_NIT_WEIGHTS[i];
2664
+ }
2665
+ const r = sum % 11;
2666
+ const expected = r > 1 ? 11 - r : r;
2667
+ if (dv !== expected) {
2668
+ return failure(
2669
+ cleaned,
2670
+ "NIT",
2671
+ "CO",
2672
+ `D\xEDgito verificador inv\xE1lido. Esperado: ${expected}, recibido: ${dv}.`
2673
+ );
2674
+ }
2675
+ return {
2676
+ valid: true,
2677
+ normalized: body + parts[1],
2678
+ formatted: cleaned,
2679
+ type: "NIT",
2680
+ country: "CO",
2681
+ error: null
2682
+ };
2683
+ }
2684
+ var UY_RUT_WEIGHTS = [4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
2685
+ function validateUY_RUT(input) {
2686
+ const normalized = (input ?? "").replace(/\D/g, "");
2687
+ if (normalized.length !== 12) {
2688
+ return failure(
2689
+ normalized,
2690
+ "RUT_UY",
2691
+ "UY",
2692
+ `Debe tener 12 d\xEDgitos; recib\xED ${normalized.length}.`
2693
+ );
2694
+ }
2695
+ let sum = 0;
2696
+ for (let i = 0; i < 11; i++) {
2697
+ sum += Number(normalized[i]) * UY_RUT_WEIGHTS[i];
2698
+ }
2699
+ const r = sum % 11;
2700
+ const expected = r === 0 ? 0 : 11 - r;
2701
+ if (Number(normalized[11]) !== expected) {
2702
+ return failure(
2703
+ normalized,
2704
+ "RUT_UY",
2705
+ "UY",
2706
+ `D\xEDgito verificador inv\xE1lido. Esperado: ${expected}, recibido: ${normalized[11]}.`
2707
+ );
2708
+ }
2709
+ return {
2710
+ valid: true,
2711
+ normalized,
2712
+ formatted: normalized.replace(
2713
+ /^(\d{2})(\d{6})(\d{3})(\d{1})$/,
2714
+ "$1$2$3$4"
2715
+ ),
2716
+ type: "RUT_UY",
2717
+ country: "UY",
2718
+ error: null
2719
+ };
2720
+ }
2721
+ var PE_RUC_WEIGHTS = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
2722
+ function validatePE_RUC(input) {
2723
+ const normalized = (input ?? "").replace(/\D/g, "");
2724
+ if (normalized.length !== 11) {
2725
+ return failure(
2726
+ normalized,
2727
+ "RUC",
2728
+ "PE",
2729
+ `Debe tener 11 d\xEDgitos; recib\xED ${normalized.length}.`
2730
+ );
2731
+ }
2732
+ const prefix = normalized.slice(0, 2);
2733
+ if (!["10", "15", "17", "20"].includes(prefix)) {
2734
+ return failure(
2735
+ normalized,
2736
+ "RUC",
2737
+ "PE",
2738
+ `Prefijo ${prefix} no v\xE1lido. Debe ser 10, 15, 17 o 20.`
2739
+ );
2740
+ }
2741
+ let sum = 0;
2742
+ for (let i = 0; i < 10; i++) {
2743
+ sum += Number(normalized[i]) * PE_RUC_WEIGHTS[i];
2744
+ }
2745
+ const r = 11 - sum % 11;
2746
+ const expected = r === 11 ? 0 : r === 10 ? 1 : r;
2747
+ if (Number(normalized[10]) !== expected) {
2748
+ return failure(
2749
+ normalized,
2750
+ "RUC",
2751
+ "PE",
2752
+ `D\xEDgito verificador inv\xE1lido. Esperado: ${expected}, recibido: ${normalized[10]}.`
2753
+ );
2754
+ }
2755
+ return {
2756
+ valid: true,
2757
+ normalized,
2758
+ formatted: normalized,
2759
+ type: "RUC",
2760
+ country: "PE",
2761
+ error: null
2762
+ };
2763
+ }
2764
+ function failure(normalized, type, country, error) {
2765
+ return {
2766
+ valid: false,
2767
+ normalized,
2768
+ formatted: null,
2769
+ type,
2770
+ country,
2771
+ error
2772
+ };
2773
+ }
2774
+ function formatThousands(s) {
2775
+ return s.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
2776
+ }
2777
+ function detectAndValidate(input, country) {
2778
+ const cleaned = input.replace(/\D/g, "");
2779
+ switch (country) {
2780
+ case "AR": {
2781
+ if (cleaned.length === 7 || cleaned.length === 8) return validateTaxId(input, "DNI");
2782
+ if (cleaned.length === 11) return validateTaxId(input, "CUIT");
2783
+ return null;
2784
+ }
2785
+ case "BR": {
2786
+ if (cleaned.length === 11) return validateTaxId(input, "CPF");
2787
+ if (cleaned.length === 14) return validateTaxId(input, "CNPJ");
2788
+ return null;
2789
+ }
2790
+ case "MX":
2791
+ return validateTaxId(input, "RFC");
2792
+ case "CL":
2793
+ return validateTaxId(input, "RUT_CL");
2794
+ case "CO":
2795
+ return validateTaxId(input, "NIT");
2796
+ case "UY":
2797
+ return validateTaxId(input, "RUT_UY");
2798
+ case "PE":
2799
+ return validateTaxId(input, "RUC");
2800
+ }
2801
+ }
2802
+
2124
2803
  // src/test-cards.ts
2125
2804
  var TEST_CARDS_AR = {
2126
2805
  VISA_CREDIT: {
@@ -2248,8 +2927,40 @@ function analyze3DS(payment) {
2248
2927
  description: "No se pudo determinar el estado 3DS \u2014 revisar payment.three_d_secure_mode + payment.status_detail manualmente."
2249
2928
  };
2250
2929
  }
2930
+ async function confirmChallengeAndPoll(client, paymentId, options = {}) {
2931
+ const maxAttempts = options.maxAttempts ?? 5;
2932
+ const interval = options.pollIntervalMs ?? 1e3;
2933
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2934
+ if (options.signal?.aborted) {
2935
+ const payment3 = await client.getPayment(paymentId);
2936
+ return { payment: payment3, threeDs: analyze3DS(payment3), resolved: false, attempts: attempt };
2937
+ }
2938
+ const payment2 = await client.getPayment(paymentId);
2939
+ const threeDs = analyze3DS(payment2);
2940
+ const stillWaiting = threeDs.status === "challenge_required" || payment2.status === "pending" || payment2.status === "in_process";
2941
+ if (!stillWaiting) {
2942
+ return { payment: payment2, threeDs, resolved: true, attempts: attempt };
2943
+ }
2944
+ if (attempt < maxAttempts) {
2945
+ await new Promise((resolve) => {
2946
+ const timer = setTimeout(resolve, interval);
2947
+ options.signal?.addEventListener(
2948
+ "abort",
2949
+ () => {
2950
+ clearTimeout(timer);
2951
+ resolve(void 0);
2952
+ },
2953
+ { once: true }
2954
+ );
2955
+ });
2956
+ }
2957
+ }
2958
+ const payment = await client.getPayment(paymentId);
2959
+ return { payment, threeDs: analyze3DS(payment), resolved: false, attempts: maxAttempts };
2960
+ }
2251
2961
 
2252
2962
  // src/webhook.ts
2963
+ init_crypto();
2253
2964
  function parseWebhookEvent(body, searchParams) {
2254
2965
  const parseResult = WebhookBodySchema.safeParse(body ?? {});
2255
2966
  const parsedBody = parseResult.success ? parseResult.data : {};
@@ -2401,7 +3112,16 @@ var DEFAULT_DESCRIPTIONS = {
2401
3112
  compute_marketplace_fee: "PURE HELPER (no network) \u2014 given a transaction amount + fee rule (% or flat ARS, with optional min/max floors), returns the exact `marketplace_fee` value in ARS to pass to create_order or create_payment_preference. USE WHEN your platform takes a commission and you need to compute the exact fee per transaction. Examples: { percent: 5, minArs: 50, maxArs: 5000 } for percentage with floor + cap; { flatArs: 200, percent: 2 } for fixed + percentage.",
2402
3113
  explain_payment_status: "PURE HELPER (no network) \u2014 given a Payment object (from get_payment / create_payment / handle_webhook), returns { summary, recommendedAction, final, paid, retryable } in Spanish. Translates MP's cryptic status_detail codes to plain Spanish + actionable guidance ('reintentar con otra tarjeta' vs 'esperar webhook' vs 'estado final'). USE THIS instead of having to memorize 30+ status_detail codes \u2014 surface summary + recommendedAction directly to the user.",
2403
3114
  // ── v0.9 — Health check + observability ──────────────────────────────────
2404
- mp_health_check: "Liveness probe against MP. Returns { ok, latencyMs, userId, circuit }. USE THIS as the first call in long-running agent workflows to verify (a) network path to MP is up, (b) accessToken is valid, (c) MP is responding. Circuit-breaker state included when configured \u2014 surface to ops dashboards. Returns ok=false instead of throwing \u2014 safe to call in monitoring loops without try/catch."
3115
+ mp_health_check: "Liveness probe against MP. Returns { ok, latencyMs, userId, circuit }. USE THIS as the first call in long-running agent workflows to verify (a) network path to MP is up, (b) accessToken is valid, (c) MP is responding. Circuit-breaker state included when configured \u2014 surface to ops dashboards. Returns ok=false instead of throwing \u2014 safe to call in monitoring loops without try/catch.",
3116
+ // ── v0.10 — AR issuer cuotas promos (pure) ───────────────────────────────
3117
+ find_applicable_promos: "PURE HELPER (no network, sub-ms) \u2014 returns the 'cuotas sin inter\xE9s' promotions applicable to a given (issuer, paymentMethodId, amount, category, date) tuple. Includes the federal Ahora 3/6/12/18/24/30 program AND issuer-specific deals (Naranja con Galicia los jueves, Santander Amex en supermercados los martes, etc.). USE THIS BEFORE checkout to surface 'pag\xE1 en 12 cuotas sin inter\xE9s con tu Galicia' hints to the buyer \u2014 drives conversion. Returns an array of CuotasPromo objects; the `description` field is in Spanish and ALWAYS surface verbatim. Catalog updated quarterly.",
3118
+ // ── v0.10 — 3DS challenge resolution ────────────────────────────────────
3119
+ confirm_3ds_challenge: "After the buyer completes a 3DS challenge (redirected back from challengeUrl), call this to poll MP and confirm whether the payment is now resolved. Polls get_payment up to N times with exponential backoff. Returns { payment, threeDs, resolved, attempts }. USE THIS as the FINAL step in the 3DS flow (after analyze_payment_3ds detected a challenge_required). Without confirming, the payment stays in 'pending' indefinitely from the buyer's perspective.",
3120
+ // ── v0.10 — Auto-paginate variants ──────────────────────────────────────
3121
+ search_payments_all: "Collect ALL payments matching a filter \u2014 auto-paginates under the hood. Returns an array (NOT paginated) so the agent doesn't have to manage offset/limit loops manually. SAFETY: pass `max_items` to cap; without it, MP traversal is bounded by the toolkit's internal max (10,000 items) to prevent runaway iterations. USE WHEN the agent needs to enumerate everything (e.g., monthly reconciliation 'all approved payments in March'). For agent flows that only need 'first N matches', pass `max_items` directly.",
3122
+ list_settlements_all: "Collect ALL settlements matching a filter \u2014 auto-paginates. Pass `max_items` to cap. Use for monthly bank-conciliation reports.",
3123
+ // ── v0.11 — TaxID validation cross-LATAM (pure) ──────────────────────────
3124
+ validate_tax_id: "PURE HELPER (no network, sub-ms) \u2014 validates a tax ID against the appropriate country algorithm. Supports AR (DNI/CUIT/CUIL with modulo-11), BR (CPF/CNPJ with two-step weighted modulo), MX (RFC structure), CL (RUT with K digit), CO (NIT modulo-11), UY (RUT 12-digit checksum), PE (RUC 11-digit + prefix validation). Returns { valid, normalized, formatted, type, country, error }. USE THIS BEFORE submitting buyer identification to MP \u2014 invalid tax IDs cause 4xx rejections. Surface the Spanish error verbatim."
2405
3125
  };
2406
3126
  function mercadoPagoTools(client, options) {
2407
3127
  const desc = (name) => options.descriptions?.[name] ?? DEFAULT_DESCRIPTIONS[name];
@@ -2529,7 +3249,45 @@ function mercadoPagoTools(client, options) {
2529
3249
  type: zod.z.enum(["DNI", "CUIT", "CUIL"]),
2530
3250
  number: zod.z.string()
2531
3251
  }).optional().describe("Payer identification \u2014 required for some payment types in AR"),
2532
- statement_descriptor: zod.z.string().max(13).optional().describe("Shows on buyer's card statement (max 13 chars)")
3252
+ statement_descriptor: zod.z.string().max(13).optional().describe("Shows on buyer's card statement (max 13 chars)"),
3253
+ // v0.11 — fraud scoring enrichment fields
3254
+ additional_info: zod.z.object({
3255
+ ip_address: zod.z.string().optional().describe(
3256
+ "Buyer's IP address (from req.headers X-Forwarded-For). STRONGLY RECOMMENDED for card payments \u2014 improves MP fraud scoring confidence and reduces false-positive rejections (3-5x lower per RG 5286/2023)."
3257
+ ),
3258
+ referral_url: zod.z.string().url().optional().describe("Page the buyer came from"),
3259
+ payer: zod.z.object({
3260
+ first_name: zod.z.string().optional(),
3261
+ last_name: zod.z.string().optional(),
3262
+ phone: zod.z.object({ area_code: zod.z.string().optional(), number: zod.z.string().optional() }).optional(),
3263
+ address: zod.z.object({
3264
+ zip_code: zod.z.string().optional(),
3265
+ street_name: zod.z.string().optional(),
3266
+ street_number: zod.z.number().optional()
3267
+ }).optional(),
3268
+ registration_date: zod.z.string().optional().describe("ISO 8601 \u2014 when the buyer registered on YOUR platform"),
3269
+ authentication_type: zod.z.string().optional(),
3270
+ is_prime_user: zod.z.boolean().optional(),
3271
+ is_first_purchase_online: zod.z.boolean().optional(),
3272
+ last_purchase: zod.z.string().optional()
3273
+ }).optional(),
3274
+ shipments: zod.z.object({
3275
+ receiver_address: zod.z.object({
3276
+ zip_code: zod.z.string().optional(),
3277
+ street_name: zod.z.string().optional(),
3278
+ street_number: zod.z.number().optional(),
3279
+ floor: zod.z.string().optional(),
3280
+ apartment: zod.z.string().optional(),
3281
+ city_name: zod.z.string().optional(),
3282
+ state_name: zod.z.string().optional(),
3283
+ country_name: zod.z.string().optional()
3284
+ }).optional(),
3285
+ express_shipment: zod.z.boolean().optional(),
3286
+ local_pickup: zod.z.boolean().optional()
3287
+ }).optional()
3288
+ }).optional().describe(
3289
+ "Fraud scoring enrichment. Pass IP address + payer profile + shipping address for materially better approval rates on card payments."
3290
+ )
2533
3291
  }),
2534
3292
  execute: async (input) => {
2535
3293
  const payment = await client.createPayment({
@@ -2542,6 +3300,7 @@ function mercadoPagoTools(client, options) {
2542
3300
  ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
2543
3301
  ...input.identification !== void 0 ? { identification: input.identification } : {},
2544
3302
  ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
3303
+ ...input.additional_info !== void 0 ? { additionalInfo: input.additional_info } : {},
2545
3304
  ...options.notificationUrl !== void 0 ? { notificationUrl: options.notificationUrl } : {},
2546
3305
  // Deterministic idempotency key — safe to retry, same inputs always
2547
3306
  // produce the same key (MP dedupes on its side).
@@ -4200,6 +4959,129 @@ function mercadoPagoTools(client, options) {
4200
4959
  ...explanation
4201
4960
  };
4202
4961
  }
4962
+ }),
4963
+ // ─────────────────────────────────────────────────────────────────────
4964
+ // v0.10 — AR issuer promos (pure)
4965
+ // ─────────────────────────────────────────────────────────────────────
4966
+ find_applicable_promos: ai.tool({
4967
+ description: desc("find_applicable_promos"),
4968
+ inputSchema: zod.z.object({
4969
+ issuer: zod.z.string().optional().describe("Issuer name (e.g. 'Banco Galicia')"),
4970
+ payment_method_id: zod.z.string().optional().describe("e.g. 'visa', 'master', 'naranja'"),
4971
+ amount_ars: zod.z.number().positive().optional(),
4972
+ category: zod.z.enum([
4973
+ "electronics",
4974
+ "appliances",
4975
+ "clothing",
4976
+ "supermarket",
4977
+ "travel",
4978
+ "education",
4979
+ "health",
4980
+ "general"
4981
+ ]).optional(),
4982
+ date: zod.z.string().datetime().optional(),
4983
+ include_ahora_program: zod.z.boolean().optional()
4984
+ }),
4985
+ execute: async (input) => {
4986
+ const args = {};
4987
+ if (input.issuer !== void 0) args.issuer = input.issuer;
4988
+ if (input.payment_method_id !== void 0) args.paymentMethodId = input.payment_method_id;
4989
+ if (input.amount_ars !== void 0) args.amountArs = input.amount_ars;
4990
+ if (input.category !== void 0) args.category = input.category;
4991
+ if (input.date !== void 0) args.date = new Date(input.date);
4992
+ if (input.include_ahora_program !== void 0) args.includeAhoraProgram = input.include_ahora_program;
4993
+ const promos = findApplicablePromos(args);
4994
+ return { ok: true, count: promos.length, promos };
4995
+ }
4996
+ }),
4997
+ // ─────────────────────────────────────────────────────────────────────
4998
+ // v0.10 — 3DS challenge resolution (poll-and-confirm)
4999
+ // ─────────────────────────────────────────────────────────────────────
5000
+ confirm_3ds_challenge: ai.tool({
5001
+ description: desc("confirm_3ds_challenge"),
5002
+ inputSchema: zod.z.object({
5003
+ payment_id: zod.z.string(),
5004
+ max_attempts: zod.z.number().int().positive().max(20).optional(),
5005
+ poll_interval_ms: zod.z.number().int().positive().max(1e4).optional()
5006
+ }),
5007
+ execute: async ({ payment_id, max_attempts, poll_interval_ms }) => {
5008
+ const args = {};
5009
+ if (max_attempts !== void 0) args.maxAttempts = max_attempts;
5010
+ if (poll_interval_ms !== void 0) args.pollIntervalMs = poll_interval_ms;
5011
+ return confirmChallengeAndPoll(client, payment_id, args);
5012
+ }
5013
+ }),
5014
+ // ─────────────────────────────────────────────────────────────────────
5015
+ // v0.10 — Auto-paginate variants (collect-all)
5016
+ // ─────────────────────────────────────────────────────────────────────
5017
+ search_payments_all: ai.tool({
5018
+ description: desc("search_payments_all"),
5019
+ inputSchema: zod.z.object({
5020
+ status: zod.z.string().optional(),
5021
+ external_reference: zod.z.string().optional(),
5022
+ from: zod.z.string().optional(),
5023
+ to: zod.z.string().optional(),
5024
+ max_items: zod.z.number().int().positive().max(1e4).optional().describe("Cap on total items returned (default 10,000 hard limit).")
5025
+ }),
5026
+ execute: async ({ max_items, ...filter }) => {
5027
+ const filterClean = {};
5028
+ for (const [k, v] of Object.entries(filter)) {
5029
+ if (v !== void 0) filterClean[k] = v;
5030
+ }
5031
+ const opts = {};
5032
+ if (max_items !== void 0) opts.maxItems = max_items;
5033
+ else opts.maxItems = 1e4;
5034
+ const all = await collect(paginatePayments(client, filterClean, opts));
5035
+ return { ok: true, count: all.length, payments: all };
5036
+ }
5037
+ }),
5038
+ list_settlements_all: ai.tool({
5039
+ description: desc("list_settlements_all"),
5040
+ inputSchema: zod.z.object({
5041
+ from: zod.z.string().optional(),
5042
+ to: zod.z.string().optional(),
5043
+ status: zod.z.string().optional(),
5044
+ max_items: zod.z.number().int().positive().max(1e4).optional()
5045
+ }),
5046
+ execute: async ({ max_items, ...filter }) => {
5047
+ const filterClean = {};
5048
+ if (filter.from !== void 0) filterClean.from = filter.from;
5049
+ if (filter.to !== void 0) filterClean.to = filter.to;
5050
+ if (filter.status !== void 0) filterClean.status = filter.status;
5051
+ const opts = {};
5052
+ if (max_items !== void 0) opts.maxItems = max_items;
5053
+ else opts.maxItems = 1e4;
5054
+ const all = await collect(paginateSettlements(client, filterClean, opts));
5055
+ return { ok: true, count: all.length, settlements: all };
5056
+ }
5057
+ }),
5058
+ // ─────────────────────────────────────────────────────────────────────
5059
+ // v0.11 — TaxID validation cross-LATAM (pure)
5060
+ // ─────────────────────────────────────────────────────────────────────
5061
+ validate_tax_id: ai.tool({
5062
+ description: desc("validate_tax_id"),
5063
+ inputSchema: zod.z.object({
5064
+ tax_id: zod.z.string().min(1).describe(
5065
+ "The tax ID to validate. Accepts any format with or without separators (20-41758101-5, 20.41758101.5, 20417581015 all work for AR CUIT)."
5066
+ ),
5067
+ type: zod.z.enum([
5068
+ "DNI",
5069
+ "CUIT",
5070
+ "CUIL",
5071
+ "CPF",
5072
+ "CNPJ",
5073
+ "RFC",
5074
+ "RUT_CL",
5075
+ "NIT",
5076
+ "RUT_UY",
5077
+ "RUC"
5078
+ ]).describe(
5079
+ "TaxID type. AR: DNI/CUIT/CUIL. BR: CPF (persona f\xEDsica) / CNPJ (persona jur\xEDdica). MX: RFC. CL: RUT_CL. CO: NIT. UY: RUT_UY. PE: RUC."
5080
+ )
5081
+ }),
5082
+ execute: async ({ tax_id, type }) => {
5083
+ return validateTaxId(tax_id, type);
5084
+ }
4203
5085
  })
4204
5086
  };
4205
5087
  }
@@ -4422,8 +5304,417 @@ var CircuitBreaker = class {
4422
5304
  }
4423
5305
  };
4424
5306
 
5307
+ // src/audit.ts
5308
+ var InMemoryAuditLog = class {
5309
+ entries = [];
5310
+ async append(entry) {
5311
+ this.entries.push(entry);
5312
+ }
5313
+ async query(filter) {
5314
+ const filtered = this.entries.filter((e) => {
5315
+ if (filter.actor && e.actor !== filter.actor) return false;
5316
+ if (filter.operation && e.operation !== filter.operation) return false;
5317
+ if (filter.tenantId && e.tenantId !== filter.tenantId) return false;
5318
+ if (filter.from && e.timestamp < filter.from) return false;
5319
+ if (filter.to && e.timestamp > filter.to) return false;
5320
+ return true;
5321
+ });
5322
+ return filter.limit ? filtered.slice(0, filter.limit) : filtered;
5323
+ }
5324
+ /** All entries (test helper, not part of the adapter interface). */
5325
+ all() {
5326
+ return [...this.entries];
5327
+ }
5328
+ reset() {
5329
+ this.entries.length = 0;
5330
+ }
5331
+ };
5332
+ var AuditLogger = class {
5333
+ adapter;
5334
+ defaultActor;
5335
+ redact;
5336
+ hash;
5337
+ constructor(options) {
5338
+ this.adapter = options.adapter;
5339
+ this.defaultActor = options.defaultActor ?? "unknown";
5340
+ this.redact = options.redact ?? true;
5341
+ this.hash = options.hashFn ?? defaultHasher;
5342
+ }
5343
+ /**
5344
+ * Wrap a tool execute() function with auto-audit. The returned function:
5345
+ * 1. Computes inputHash before the call.
5346
+ * 2. Invokes the original execute().
5347
+ * 3. On success, appends an entry with outcome="ok" + resourceId.
5348
+ * 4. On failure, appends an entry with outcome="error" + errorCode/Message.
5349
+ * 5. Re-throws the error transparently.
5350
+ */
5351
+ async record(args) {
5352
+ const t0 = Date.now();
5353
+ const inputHash = await this.hash(stableStringify(args.input));
5354
+ try {
5355
+ const result = await args.fn();
5356
+ const resourceId = (args.extractResourceId ?? defaultExtractResourceId)(result);
5357
+ const entry = {
5358
+ id: `mpaud-${(/* @__PURE__ */ new Date()).toISOString()}-${Math.random().toString(36).slice(2, 10)}`,
5359
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5360
+ operation: args.operation,
5361
+ actor: args.actor ?? this.defaultActor,
5362
+ inputHash,
5363
+ outcome: "ok",
5364
+ durationMs: Date.now() - t0
5365
+ };
5366
+ if (args.tenantId !== void 0) entry.tenantId = args.tenantId;
5367
+ if (resourceId !== void 0) entry.resourceId = resourceId;
5368
+ if (args.idempotencyKey !== void 0) entry.idempotencyKey = args.idempotencyKey;
5369
+ if (!this.redact) entry.inputRaw = args.input;
5370
+ await this.adapter.append(entry);
5371
+ return result;
5372
+ } catch (err) {
5373
+ const entry = {
5374
+ id: `mpaud-${(/* @__PURE__ */ new Date()).toISOString()}-${Math.random().toString(36).slice(2, 10)}`,
5375
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5376
+ operation: args.operation,
5377
+ actor: args.actor ?? this.defaultActor,
5378
+ inputHash,
5379
+ outcome: "error",
5380
+ errorCode: extractErrorCode(err),
5381
+ errorMessage: err instanceof Error ? err.message : String(err),
5382
+ durationMs: Date.now() - t0
5383
+ };
5384
+ if (args.tenantId !== void 0) entry.tenantId = args.tenantId;
5385
+ if (args.idempotencyKey !== void 0) entry.idempotencyKey = args.idempotencyKey;
5386
+ if (!this.redact) entry.inputRaw = args.input;
5387
+ await this.adapter.append(entry);
5388
+ throw err;
5389
+ }
5390
+ }
5391
+ };
5392
+ async function defaultHasher(input) {
5393
+ const { sha256Hex: sha256Hex2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
5394
+ return sha256Hex2(input);
5395
+ }
5396
+ function stableStringify(obj) {
5397
+ return JSON.stringify(obj, (_, v) => {
5398
+ if (v && typeof v === "object" && !Array.isArray(v)) {
5399
+ const sorted = {};
5400
+ for (const k of Object.keys(v).sort()) {
5401
+ sorted[k] = v[k];
5402
+ }
5403
+ return sorted;
5404
+ }
5405
+ return v;
5406
+ });
5407
+ }
5408
+ function defaultExtractResourceId(result) {
5409
+ if (!result || typeof result !== "object") return void 0;
5410
+ const r = result;
5411
+ for (const key of [
5412
+ "id",
5413
+ "payment_id",
5414
+ "subscription_id",
5415
+ "order_id",
5416
+ "preference_id",
5417
+ "customer_id",
5418
+ "refund_id",
5419
+ "store_id",
5420
+ "pos_id",
5421
+ "merchant_order_id",
5422
+ "intent_id",
5423
+ "device_id"
5424
+ ]) {
5425
+ const v = r[key];
5426
+ if (typeof v === "string" || typeof v === "number") return String(v);
5427
+ }
5428
+ return void 0;
5429
+ }
5430
+ function extractErrorCode(err) {
5431
+ if (err && typeof err === "object") {
5432
+ const e = err;
5433
+ return e.code ?? e.name ?? "unknown_error";
5434
+ }
5435
+ return "unknown_error";
5436
+ }
5437
+
5438
+ // src/webhook-dedup.ts
5439
+ var WebhookDedup = class {
5440
+ cache;
5441
+ ttlSeconds;
5442
+ onDuplicate;
5443
+ constructor(opts) {
5444
+ this.cache = opts.cache;
5445
+ this.ttlSeconds = opts.ttlSeconds ?? 7 * 24 * 3600;
5446
+ this.onDuplicate = opts.onDuplicate;
5447
+ }
5448
+ /**
5449
+ * Check whether a webhook delivery has been seen before. If new, mark it
5450
+ * as seen (so subsequent retries return shouldProcess=false). If seen,
5451
+ * return shouldProcess=false WITHOUT marking again.
5452
+ *
5453
+ * **Important**: this method is not atomic across concurrent calls — two
5454
+ * simultaneous deliveries with the same key may both pass shouldProcess=true.
5455
+ * For strict at-most-once processing, follow with a transaction or use a
5456
+ * cache that supports `setNX`-style semantics (Redis, Cloudflare KV with
5457
+ * conditional writes).
5458
+ *
5459
+ * For most webhook handlers this race is acceptable: even if two get
5460
+ * through, the downstream business logic (e.g., "charge if not already
5461
+ * charged") will be idempotent on its own.
5462
+ */
5463
+ async check(args) {
5464
+ const deliveryKey = this.deriveKey(args);
5465
+ const seen = await this.cache.get(deliveryKey);
5466
+ if (seen) {
5467
+ this.onDuplicate?.(deliveryKey);
5468
+ return { shouldProcess: false, deliveryKey };
5469
+ }
5470
+ await this.cache.set(deliveryKey, true, this.ttlSeconds);
5471
+ return { shouldProcess: true, deliveryKey };
5472
+ }
5473
+ /**
5474
+ * Manually mark a delivery as processed. Call this AFTER your business
5475
+ * logic succeeds — useful when you want to control when the dedup
5476
+ * marker is written (e.g., only on success).
5477
+ *
5478
+ * Combined with calling `check()` BEFORE the work, this gives "at-least-once"
5479
+ * semantics: failed processing → no marker → retry will be processed again.
5480
+ */
5481
+ async markProcessed(args) {
5482
+ const deliveryKey = this.deriveKey(args);
5483
+ await this.cache.set(deliveryKey, true, this.ttlSeconds);
5484
+ }
5485
+ /**
5486
+ * Variant of `check` that doesn't mark on first sight — caller must
5487
+ * explicitly `markProcessed` when their business logic succeeds.
5488
+ * Use this for at-least-once semantics (each delivery processed at
5489
+ * least once, possibly more if processing fails before mark).
5490
+ */
5491
+ async peekIsDuplicate(args) {
5492
+ const deliveryKey = this.deriveKey(args);
5493
+ const seen = await this.cache.get(deliveryKey);
5494
+ if (seen) this.onDuplicate?.(deliveryKey);
5495
+ return { shouldProcess: !seen, deliveryKey };
5496
+ }
5497
+ deriveKey(args) {
5498
+ return `mp:webhook:${args.topic}:${args.dataId}:${args.requestId ?? "noreqid"}`;
5499
+ }
5500
+ };
5501
+
5502
+ // src/rate-limiter.ts
5503
+ var RateLimitTimeoutError = class extends Error {
5504
+ constructor(waitedMs) {
5505
+ super(`Rate limit acquire timed out after ${waitedMs}ms.`);
5506
+ this.waitedMs = waitedMs;
5507
+ this.name = "RateLimitTimeoutError";
5508
+ }
5509
+ waitedMs;
5510
+ };
5511
+ var TokenBucketRateLimiter = class {
5512
+ tokens;
5513
+ lastRefill;
5514
+ capacity;
5515
+ refillPerSecond;
5516
+ adaptive;
5517
+ acquireTimeoutMs;
5518
+ now;
5519
+ constructor(opts = {}) {
5520
+ this.capacity = opts.capacity ?? 50;
5521
+ this.refillPerSecond = opts.refillPerSecond ?? 25;
5522
+ this.adaptive = opts.adaptive ?? true;
5523
+ this.acquireTimeoutMs = opts.acquireTimeoutMs ?? 3e4;
5524
+ this.now = opts.now ?? Date.now;
5525
+ this.tokens = this.capacity;
5526
+ this.lastRefill = this.now();
5527
+ }
5528
+ /**
5529
+ * Acquire a token. Resolves immediately if tokens are available;
5530
+ * otherwise waits until one is. Rejects with `RateLimitTimeoutError`
5531
+ * if the wait exceeds `acquireTimeoutMs`.
5532
+ */
5533
+ async acquire() {
5534
+ this.refill();
5535
+ if (this.tokens >= 1) {
5536
+ this.tokens -= 1;
5537
+ return;
5538
+ }
5539
+ const tokensNeeded = 1 - this.tokens;
5540
+ const waitMs = Math.ceil(tokensNeeded / this.refillPerSecond * 1e3);
5541
+ if (waitMs > this.acquireTimeoutMs) {
5542
+ throw new RateLimitTimeoutError(waitMs);
5543
+ }
5544
+ await sleep2(waitMs);
5545
+ this.refill();
5546
+ this.tokens -= 1;
5547
+ }
5548
+ /**
5549
+ * Best-effort acquire: returns true if a token was available, false
5550
+ * otherwise. Doesn't wait. Useful for "non-blocking" code paths that
5551
+ * want to fall back to a cached response or queue the request elsewhere.
5552
+ */
5553
+ tryAcquire() {
5554
+ this.refill();
5555
+ if (this.tokens >= 1) {
5556
+ this.tokens -= 1;
5557
+ return true;
5558
+ }
5559
+ return false;
5560
+ }
5561
+ /**
5562
+ * Adaptive learning hook — call after every API response with MP's
5563
+ * rate-limit headers to keep the bucket in sync with reality.
5564
+ */
5565
+ learnFromHeaders(headers) {
5566
+ if (!this.adaptive) return;
5567
+ if (headers.remaining === null) return;
5568
+ this.refill();
5569
+ if (headers.remaining < this.tokens) {
5570
+ this.tokens = Math.max(0, headers.remaining);
5571
+ }
5572
+ }
5573
+ /** Inspect the current bucket state. */
5574
+ getStats() {
5575
+ this.refill();
5576
+ return {
5577
+ tokens: this.tokens,
5578
+ capacity: this.capacity,
5579
+ refillPerSecond: this.refillPerSecond
5580
+ };
5581
+ }
5582
+ refill() {
5583
+ const now = this.now();
5584
+ const elapsedMs = now - this.lastRefill;
5585
+ if (elapsedMs <= 0) return;
5586
+ const refilled = elapsedMs / 1e3 * this.refillPerSecond;
5587
+ this.tokens = Math.min(this.capacity, this.tokens + refilled);
5588
+ this.lastRefill = now;
5589
+ }
5590
+ };
5591
+ function sleep2(ms) {
5592
+ return new Promise((resolve) => setTimeout(resolve, ms));
5593
+ }
5594
+
5595
+ // src/middleware.ts
5596
+ function compose(...middlewares) {
5597
+ return (tool2) => {
5598
+ return middlewares.reduceRight((wrapped, mw) => mw(wrapped), tool2);
5599
+ };
5600
+ }
5601
+ function withAuditLog(logger, operation, actor) {
5602
+ return (tool2) => {
5603
+ const original = tool2.execute;
5604
+ if (!original) return tool2;
5605
+ return {
5606
+ ...tool2,
5607
+ execute: (async (input, opts) => {
5608
+ return logger.record({
5609
+ operation,
5610
+ input,
5611
+ ...actor !== void 0 ? { actor } : {},
5612
+ fn: () => original(input, opts)
5613
+ });
5614
+ })
5615
+ };
5616
+ };
5617
+ }
5618
+ function withRateLimit(limiter) {
5619
+ return (tool2) => {
5620
+ const original = tool2.execute;
5621
+ if (!original) return tool2;
5622
+ return {
5623
+ ...tool2,
5624
+ execute: (async (input, opts) => {
5625
+ await limiter.acquire();
5626
+ return original(input, opts);
5627
+ })
5628
+ };
5629
+ };
5630
+ }
5631
+ function withMetrics(toolName, hook) {
5632
+ return (tool2) => {
5633
+ const original = tool2.execute;
5634
+ if (!original) return tool2;
5635
+ return {
5636
+ ...tool2,
5637
+ execute: (async (input, opts) => {
5638
+ const t0 = Date.now();
5639
+ try {
5640
+ const result = await original(input, opts);
5641
+ hook.onMetric({
5642
+ toolName,
5643
+ durationMs: Date.now() - t0,
5644
+ success: true
5645
+ });
5646
+ return result;
5647
+ } catch (err) {
5648
+ hook.onMetric({
5649
+ toolName,
5650
+ durationMs: Date.now() - t0,
5651
+ success: false,
5652
+ errorCode: extractErrorCode2(err)
5653
+ });
5654
+ throw err;
5655
+ }
5656
+ })
5657
+ };
5658
+ };
5659
+ }
5660
+ var defaultShouldRetry = (err) => {
5661
+ if (err && typeof err === "object" && "status" in err) {
5662
+ const status = err.status;
5663
+ if (typeof status === "number" && status >= 400 && status < 500) {
5664
+ return false;
5665
+ }
5666
+ }
5667
+ return true;
5668
+ };
5669
+ function withRetry(opts = {}) {
5670
+ const maxAttempts = opts.maxAttempts ?? 3;
5671
+ const baseBackoff = opts.baseBackoffMs ?? 250;
5672
+ const shouldRetry = opts.shouldRetry ?? defaultShouldRetry;
5673
+ return (tool2) => {
5674
+ const original = tool2.execute;
5675
+ if (!original) return tool2;
5676
+ return {
5677
+ ...tool2,
5678
+ execute: (async (input, opts2) => {
5679
+ let attempt = 0;
5680
+ while (true) {
5681
+ try {
5682
+ return await original(input, opts2);
5683
+ } catch (err) {
5684
+ attempt++;
5685
+ if (attempt >= maxAttempts || !shouldRetry(err, attempt)) {
5686
+ throw err;
5687
+ }
5688
+ const delayMs = baseBackoff * Math.pow(2, attempt - 1);
5689
+ opts.onRetry?.({ attempt, error: err, delayMs });
5690
+ await new Promise((r) => setTimeout(r, delayMs));
5691
+ }
5692
+ }
5693
+ })
5694
+ };
5695
+ };
5696
+ }
5697
+ function applyToAllTools(tools, middleware) {
5698
+ const out = {};
5699
+ for (const [name, tool2] of Object.entries(tools)) {
5700
+ out[name] = middleware(tool2);
5701
+ }
5702
+ return out;
5703
+ }
5704
+ function extractErrorCode2(err) {
5705
+ if (err && typeof err === "object") {
5706
+ const e = err;
5707
+ return e.code ?? e.name ?? "unknown_error";
5708
+ }
5709
+ return "unknown_error";
5710
+ }
5711
+
5712
+ exports.AHORA_PROGRAM_PROMOS = AHORA_PROGRAM_PROMOS;
5713
+ exports.AR_ISSUER_PROMOS = AR_ISSUER_PROMOS;
5714
+ exports.AuditLogger = AuditLogger;
4425
5715
  exports.CircuitBreaker = CircuitBreaker;
4426
5716
  exports.CircuitOpenError = CircuitOpenError;
5717
+ exports.InMemoryAuditLog = InMemoryAuditLog;
4427
5718
  exports.InMemoryIdempotencyCache = InMemoryIdempotencyCache;
4428
5719
  exports.InMemoryOAuthTokenStore = InMemoryOAuthTokenStore;
4429
5720
  exports.InMemoryStateAdapter = InMemoryStateAdapter;
@@ -4438,20 +5729,42 @@ exports.MercadoPagoPaymentRejectedError = MercadoPagoPaymentRejectedError;
4438
5729
  exports.MercadoPagoRateLimitError = MercadoPagoRateLimitError;
4439
5730
  exports.MercadoPagoSelfPaymentError = MercadoPagoSelfPaymentError;
4440
5731
  exports.MercadoPagoTimeoutError = MercadoPagoTimeoutError;
5732
+ exports.RateLimitTimeoutError = RateLimitTimeoutError;
4441
5733
  exports.TEST_CARDS_AR = TEST_CARDS_AR;
4442
5734
  exports.TEST_PAYERS_AR = TEST_PAYERS_AR;
5735
+ exports.TokenBucketRateLimiter = TokenBucketRateLimiter;
5736
+ exports.WebhookDedup = WebhookDedup;
4443
5737
  exports.analyze3DS = analyze3DS;
5738
+ exports.applyToAllTools = applyToAllTools;
4444
5739
  exports.buildAuthorizeUrl = buildAuthorizeUrl;
4445
5740
  exports.buildTestCardScenario = buildTestCardScenario;
4446
5741
  exports.classifyError = classifyError;
5742
+ exports.collect = collect;
5743
+ exports.compose = compose;
4447
5744
  exports.computeMarketplaceFee = computeMarketplaceFee;
5745
+ exports.confirmChallengeAndPoll = confirmChallengeAndPoll;
5746
+ exports.detectAndValidate = detectAndValidate;
4448
5747
  exports.exchangeCodeForToken = exchangeCodeForToken;
4449
5748
  exports.expirationTimeMs = expirationTimeMs;
4450
5749
  exports.explainPaymentStatus = explainPaymentStatus;
5750
+ exports.findApplicablePromos = findApplicablePromos;
4451
5751
  exports.isExpiringSoon = isExpiringSoon;
4452
5752
  exports.mercadoPagoTools = mercadoPagoTools;
5753
+ exports.paginate = paginate;
5754
+ exports.paginateAccountMovements = paginateAccountMovements;
5755
+ exports.paginateMerchantOrders = paginateMerchantOrders;
5756
+ exports.paginatePayments = paginatePayments;
5757
+ exports.paginateSettlements = paginateSettlements;
5758
+ exports.paginateSubscriptionPayments = paginateSubscriptionPayments;
5759
+ exports.paginateSubscriptionPlans = paginateSubscriptionPlans;
5760
+ exports.paginateSubscriptions = paginateSubscriptions;
4453
5761
  exports.parseWebhookEvent = parseWebhookEvent;
4454
5762
  exports.refreshAccessToken = refreshAccessToken;
5763
+ exports.validateTaxId = validateTaxId;
4455
5764
  exports.verifyWebhookSignature = verifyWebhookSignature;
5765
+ exports.withAuditLog = withAuditLog;
5766
+ exports.withMetrics = withMetrics;
5767
+ exports.withRateLimit = withRateLimit;
5768
+ exports.withRetry = withRetry;
4456
5769
  //# sourceMappingURL=index.cjs.map
4457
5770
  //# sourceMappingURL=index.cjs.map