@ar-agents/mercadopago 0.9.0 → 0.10.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/CHANGELOG.md +22 -0
- package/dist/index.cjs +796 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +714 -4
- package/dist/index.d.ts +714 -4
- package/dist/index.js +779 -49
- package/dist/index.js.map +1 -1
- package/dist/otel.cjs +102 -0
- package/dist/otel.cjs.map +1 -0
- package/dist/otel.d.cts +90 -0
- package/dist/otel.d.ts +90 -0
- package/dist/otel.js +100 -0
- package/dist/otel.js.map +1 -0
- package/package.json +19 -1
- package/tools.manifest.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,75 @@
|
|
|
1
1
|
import { tool } from 'ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/crypto.ts
|
|
15
|
+
var crypto_exports = {};
|
|
16
|
+
__export(crypto_exports, {
|
|
17
|
+
hmacSha256Hex: () => hmacSha256Hex,
|
|
18
|
+
sha256Hex: () => sha256Hex,
|
|
19
|
+
timingSafeEqualHex: () => timingSafeEqualHex
|
|
20
|
+
});
|
|
21
|
+
async function hmacSha256Hex(secret, message) {
|
|
22
|
+
const keyMaterial = await subtle.importKey(
|
|
23
|
+
"raw",
|
|
24
|
+
encoder.encode(secret),
|
|
25
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
26
|
+
false,
|
|
27
|
+
["sign"]
|
|
28
|
+
);
|
|
29
|
+
const sigBuf = await subtle.sign(
|
|
30
|
+
"HMAC",
|
|
31
|
+
keyMaterial,
|
|
32
|
+
encoder.encode(message)
|
|
33
|
+
);
|
|
34
|
+
return bufferToHex(sigBuf);
|
|
35
|
+
}
|
|
36
|
+
async function sha256Hex(input) {
|
|
37
|
+
const digest = await subtle.digest("SHA-256", encoder.encode(input));
|
|
38
|
+
return bufferToHex(digest);
|
|
39
|
+
}
|
|
40
|
+
function timingSafeEqualHex(a, b) {
|
|
41
|
+
if (a.length !== b.length) return false;
|
|
42
|
+
let diff = 0;
|
|
43
|
+
for (let i = 0; i < a.length; i++) {
|
|
44
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
45
|
+
}
|
|
46
|
+
return diff === 0;
|
|
47
|
+
}
|
|
48
|
+
function bufferToHex(buf) {
|
|
49
|
+
const bytes = new Uint8Array(buf);
|
|
50
|
+
let hex = "";
|
|
51
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
52
|
+
const b = bytes[i];
|
|
53
|
+
hex += (b < 16 ? "0" : "") + b.toString(16);
|
|
54
|
+
}
|
|
55
|
+
return hex;
|
|
56
|
+
}
|
|
57
|
+
var subtle, encoder;
|
|
58
|
+
var init_crypto = __esm({
|
|
59
|
+
"src/crypto.ts"() {
|
|
60
|
+
subtle = (() => {
|
|
61
|
+
const c = globalThis.crypto;
|
|
62
|
+
if (!c?.subtle) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"@ar-agents/mercadopago: Web Crypto API is not available in this runtime. Use Node 18+, Vercel Edge Runtime, Cloudflare Workers, or any modern browser."
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return c.subtle;
|
|
68
|
+
})();
|
|
69
|
+
encoder = new TextEncoder();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
4
73
|
// src/errors.ts
|
|
5
74
|
var MercadoPagoError = class extends Error {
|
|
6
75
|
constructor(message, status, endpoint, mpResponse) {
|
|
@@ -1332,53 +1401,8 @@ var MercadoPagoClient = class {
|
|
|
1332
1401
|
}
|
|
1333
1402
|
};
|
|
1334
1403
|
|
|
1335
|
-
// src/
|
|
1336
|
-
|
|
1337
|
-
const c = globalThis.crypto;
|
|
1338
|
-
if (!c?.subtle) {
|
|
1339
|
-
throw new Error(
|
|
1340
|
-
"@ar-agents/mercadopago: Web Crypto API is not available in this runtime. Use Node 18+, Vercel Edge Runtime, Cloudflare Workers, or any modern browser."
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
return c.subtle;
|
|
1344
|
-
})();
|
|
1345
|
-
var encoder = new TextEncoder();
|
|
1346
|
-
async function hmacSha256Hex(secret, message) {
|
|
1347
|
-
const keyMaterial = await subtle.importKey(
|
|
1348
|
-
"raw",
|
|
1349
|
-
encoder.encode(secret),
|
|
1350
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
1351
|
-
false,
|
|
1352
|
-
["sign"]
|
|
1353
|
-
);
|
|
1354
|
-
const sigBuf = await subtle.sign(
|
|
1355
|
-
"HMAC",
|
|
1356
|
-
keyMaterial,
|
|
1357
|
-
encoder.encode(message)
|
|
1358
|
-
);
|
|
1359
|
-
return bufferToHex(sigBuf);
|
|
1360
|
-
}
|
|
1361
|
-
async function sha256Hex(input) {
|
|
1362
|
-
const digest = await subtle.digest("SHA-256", encoder.encode(input));
|
|
1363
|
-
return bufferToHex(digest);
|
|
1364
|
-
}
|
|
1365
|
-
function timingSafeEqualHex(a, b) {
|
|
1366
|
-
if (a.length !== b.length) return false;
|
|
1367
|
-
let diff = 0;
|
|
1368
|
-
for (let i = 0; i < a.length; i++) {
|
|
1369
|
-
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1370
|
-
}
|
|
1371
|
-
return diff === 0;
|
|
1372
|
-
}
|
|
1373
|
-
function bufferToHex(buf) {
|
|
1374
|
-
const bytes = new Uint8Array(buf);
|
|
1375
|
-
let hex = "";
|
|
1376
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1377
|
-
const b = bytes[i];
|
|
1378
|
-
hex += (b < 16 ? "0" : "") + b.toString(16);
|
|
1379
|
-
}
|
|
1380
|
-
return hex;
|
|
1381
|
-
}
|
|
1404
|
+
// src/tools.ts
|
|
1405
|
+
init_crypto();
|
|
1382
1406
|
z.enum(["MLA", "MLB", "MLM", "MCO", "MLC", "MLU"]);
|
|
1383
1407
|
var CurrencyIdSchema = z.enum(["ARS", "USD", "BRL", "MXN"]);
|
|
1384
1408
|
var FrequencyTypeSchema = z.enum(["months", "days"]);
|
|
@@ -1882,6 +1906,180 @@ function isExpiringSoon(expirationMs, skewSeconds = 300) {
|
|
|
1882
1906
|
return Date.now() + skewSeconds * 1e3 >= expirationMs;
|
|
1883
1907
|
}
|
|
1884
1908
|
|
|
1909
|
+
// src/ar-issuer-promos.ts
|
|
1910
|
+
var AHORA_PROGRAM_PROMOS = [
|
|
1911
|
+
{
|
|
1912
|
+
issuer: "*",
|
|
1913
|
+
// any AR-resident card
|
|
1914
|
+
paymentMethodId: "*",
|
|
1915
|
+
installments: 3,
|
|
1916
|
+
description: "Ahora 3 \u2014 3 cuotas sin inter\xE9s (programa nacional)",
|
|
1917
|
+
categories: ["electronics", "appliances", "clothing", "general"]
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
issuer: "*",
|
|
1921
|
+
paymentMethodId: "*",
|
|
1922
|
+
installments: 6,
|
|
1923
|
+
description: "Ahora 6 \u2014 6 cuotas sin inter\xE9s (programa nacional, electrodom\xE9sticos l\xEDnea blanca)",
|
|
1924
|
+
categories: ["appliances"]
|
|
1925
|
+
},
|
|
1926
|
+
{
|
|
1927
|
+
issuer: "*",
|
|
1928
|
+
paymentMethodId: "*",
|
|
1929
|
+
installments: 12,
|
|
1930
|
+
description: "Ahora 12 \u2014 12 cuotas sin inter\xE9s (programa nacional)",
|
|
1931
|
+
categories: ["electronics", "appliances", "clothing"]
|
|
1932
|
+
},
|
|
1933
|
+
{
|
|
1934
|
+
issuer: "*",
|
|
1935
|
+
paymentMethodId: "*",
|
|
1936
|
+
installments: 18,
|
|
1937
|
+
description: "Ahora 18 \u2014 18 cuotas sin inter\xE9s (turismo nacional + electrodom\xE9sticos)",
|
|
1938
|
+
categories: ["appliances", "travel"]
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
issuer: "*",
|
|
1942
|
+
paymentMethodId: "*",
|
|
1943
|
+
installments: 24,
|
|
1944
|
+
description: "Ahora 24 \u2014 24 cuotas sin inter\xE9s (electrodom\xE9sticos l\xEDnea blanca premium)",
|
|
1945
|
+
categories: ["appliances"]
|
|
1946
|
+
}
|
|
1947
|
+
];
|
|
1948
|
+
var AR_ISSUER_PROMOS = [
|
|
1949
|
+
// Naranja X
|
|
1950
|
+
{
|
|
1951
|
+
issuer: "Naranja X",
|
|
1952
|
+
paymentMethodId: "naranja",
|
|
1953
|
+
installments: 3,
|
|
1954
|
+
description: "Naranja Z (Plan Z) \u2014 3 cuotas con CFT promocional, todos los rubros"
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
issuer: "Naranja X",
|
|
1958
|
+
paymentMethodId: "naranja",
|
|
1959
|
+
installments: 6,
|
|
1960
|
+
description: "Naranja \u2014 6 cuotas sin inter\xE9s con comercios adheridos (electro/indumentaria)",
|
|
1961
|
+
daysOfWeek: ["thu"],
|
|
1962
|
+
categories: ["electronics", "appliances", "clothing"]
|
|
1963
|
+
},
|
|
1964
|
+
// Galicia
|
|
1965
|
+
{
|
|
1966
|
+
issuer: "Banco Galicia",
|
|
1967
|
+
paymentMethodId: "visa",
|
|
1968
|
+
installments: 12,
|
|
1969
|
+
description: "Galicia Eminent / Quiero! \u2014 12 cuotas sin inter\xE9s en supermercados (jueves)",
|
|
1970
|
+
daysOfWeek: ["thu"],
|
|
1971
|
+
categories: ["supermarket"]
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
issuer: "Banco Galicia",
|
|
1975
|
+
paymentMethodId: "master",
|
|
1976
|
+
installments: 6,
|
|
1977
|
+
description: "Galicia \u2014 6 cuotas sin inter\xE9s en gastronom\xEDa (viernes y s\xE1bados)",
|
|
1978
|
+
daysOfWeek: ["fri", "sat"]
|
|
1979
|
+
},
|
|
1980
|
+
// Santander
|
|
1981
|
+
{
|
|
1982
|
+
issuer: "Banco Santander",
|
|
1983
|
+
paymentMethodId: "visa",
|
|
1984
|
+
installments: 6,
|
|
1985
|
+
description: "Santander Black / Platinum \u2014 6 cuotas sin inter\xE9s en cines + viajes",
|
|
1986
|
+
categories: ["travel"]
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
issuer: "Banco Santander",
|
|
1990
|
+
paymentMethodId: "amex",
|
|
1991
|
+
installments: 9,
|
|
1992
|
+
description: "Santander American Express \u2014 9 cuotas sin inter\xE9s en supermercados (martes y mi\xE9rcoles)",
|
|
1993
|
+
daysOfWeek: ["tue", "wed"],
|
|
1994
|
+
categories: ["supermarket"]
|
|
1995
|
+
},
|
|
1996
|
+
// Macro
|
|
1997
|
+
{
|
|
1998
|
+
issuer: "Banco Macro",
|
|
1999
|
+
paymentMethodId: "visa",
|
|
2000
|
+
installments: 6,
|
|
2001
|
+
description: "Macro Selecta / Premia \u2014 6 cuotas sin inter\xE9s en farmacias y librer\xEDas",
|
|
2002
|
+
categories: ["health", "education"]
|
|
2003
|
+
},
|
|
2004
|
+
// BBVA
|
|
2005
|
+
{
|
|
2006
|
+
issuer: "BBVA Banco Franc\xE9s",
|
|
2007
|
+
paymentMethodId: "visa",
|
|
2008
|
+
installments: 3,
|
|
2009
|
+
description: "BBVA Lat / Black \u2014 3 cuotas sin inter\xE9s en restaurantes (lunes a mi\xE9rcoles)",
|
|
2010
|
+
daysOfWeek: ["mon", "tue", "wed"]
|
|
2011
|
+
},
|
|
2012
|
+
// ICBC
|
|
2013
|
+
{
|
|
2014
|
+
issuer: "ICBC",
|
|
2015
|
+
paymentMethodId: "visa",
|
|
2016
|
+
installments: 6,
|
|
2017
|
+
description: "ICBC Cuenta Corriente \u2014 6 cuotas sin inter\xE9s en electro y indumentaria",
|
|
2018
|
+
categories: ["electronics", "appliances", "clothing"]
|
|
2019
|
+
},
|
|
2020
|
+
// Patagonia
|
|
2021
|
+
{
|
|
2022
|
+
issuer: "Banco Patagonia",
|
|
2023
|
+
paymentMethodId: "visa",
|
|
2024
|
+
installments: 3,
|
|
2025
|
+
description: "Patagonia 365 / Eminent \u2014 3 cuotas sin inter\xE9s en supermercados (s\xE1bados)",
|
|
2026
|
+
daysOfWeek: ["sat"],
|
|
2027
|
+
categories: ["supermarket"]
|
|
2028
|
+
},
|
|
2029
|
+
// Banco Nación
|
|
2030
|
+
{
|
|
2031
|
+
issuer: "Banco de la Naci\xF3n Argentina",
|
|
2032
|
+
paymentMethodId: "visa",
|
|
2033
|
+
installments: 12,
|
|
2034
|
+
description: "BNA \u2014 12 cuotas sin inter\xE9s con plan 'Ahora 12' del programa nacional",
|
|
2035
|
+
categories: ["electronics", "appliances", "clothing"]
|
|
2036
|
+
},
|
|
2037
|
+
// Banco Provincia
|
|
2038
|
+
{
|
|
2039
|
+
issuer: "Banco de la Provincia de Buenos Aires",
|
|
2040
|
+
paymentMethodId: "visa",
|
|
2041
|
+
installments: 6,
|
|
2042
|
+
description: "Cuenta DNI \u2014 6 cuotas sin inter\xE9s (mensual cap aplica)",
|
|
2043
|
+
maxAmountArs: 2e5
|
|
2044
|
+
},
|
|
2045
|
+
// Banco Ciudad
|
|
2046
|
+
{
|
|
2047
|
+
issuer: "Banco de la Ciudad de Buenos Aires",
|
|
2048
|
+
paymentMethodId: "visa",
|
|
2049
|
+
installments: 12,
|
|
2050
|
+
description: "Banco Ciudad \u2014 12 cuotas sin inter\xE9s en electrodom\xE9sticos (Plan Sue\xF1os)",
|
|
2051
|
+
categories: ["appliances"]
|
|
2052
|
+
}
|
|
2053
|
+
];
|
|
2054
|
+
function findApplicablePromos(args) {
|
|
2055
|
+
const date = args.date ?? /* @__PURE__ */ new Date();
|
|
2056
|
+
const dayOfWeek = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"][date.getDay()];
|
|
2057
|
+
const candidates = [
|
|
2058
|
+
...args.includeAhoraProgram !== false ? AHORA_PROGRAM_PROMOS : [],
|
|
2059
|
+
...AR_ISSUER_PROMOS
|
|
2060
|
+
];
|
|
2061
|
+
return candidates.filter((promo) => {
|
|
2062
|
+
if (args.issuer !== void 0 && promo.issuer !== "*" && promo.issuer !== args.issuer) {
|
|
2063
|
+
return false;
|
|
2064
|
+
}
|
|
2065
|
+
if (args.paymentMethodId !== void 0 && promo.paymentMethodId !== "*" && promo.paymentMethodId !== args.paymentMethodId) {
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
if (promo.daysOfWeek && promo.daysOfWeek.length > 0 && !promo.daysOfWeek.includes(dayOfWeek)) {
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
if (args.category !== void 0 && promo.categories && promo.categories.length > 0 && !promo.categories.includes(args.category)) {
|
|
2072
|
+
return false;
|
|
2073
|
+
}
|
|
2074
|
+
if (args.amountArs !== void 0 && promo.minAmountArs !== void 0 && args.amountArs < promo.minAmountArs) {
|
|
2075
|
+
return false;
|
|
2076
|
+
}
|
|
2077
|
+
if (promo.startDate && date < new Date(promo.startDate)) return false;
|
|
2078
|
+
if (promo.endDate && date > new Date(promo.endDate)) return false;
|
|
2079
|
+
return true;
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1885
2083
|
// src/helpers.ts
|
|
1886
2084
|
function computeMarketplaceFee(amountArs, rule) {
|
|
1887
2085
|
if (amountArs <= 0) return 0;
|
|
@@ -2119,6 +2317,116 @@ function explainPaymentStatus(payment) {
|
|
|
2119
2317
|
}
|
|
2120
2318
|
}
|
|
2121
2319
|
|
|
2320
|
+
// src/pagination.ts
|
|
2321
|
+
async function* paginate(fetchPage, opts) {
|
|
2322
|
+
const pageSize = opts.pageSize ?? 100;
|
|
2323
|
+
const concurrency = Math.max(1, opts.concurrency ?? 1);
|
|
2324
|
+
let yielded = 0;
|
|
2325
|
+
let offset = 0;
|
|
2326
|
+
let knownTotal = void 0;
|
|
2327
|
+
while (true) {
|
|
2328
|
+
if (opts.maxItems !== void 0 && yielded >= opts.maxItems) return;
|
|
2329
|
+
const inFlight = [];
|
|
2330
|
+
for (let i = 0; i < concurrency; i++) {
|
|
2331
|
+
const pageOffset = offset + i * pageSize;
|
|
2332
|
+
if (knownTotal !== void 0 && pageOffset >= knownTotal) break;
|
|
2333
|
+
inFlight.push(fetchPage(pageOffset, pageSize));
|
|
2334
|
+
}
|
|
2335
|
+
if (inFlight.length === 0) return;
|
|
2336
|
+
const pages = await Promise.all(inFlight);
|
|
2337
|
+
let allEmpty = true;
|
|
2338
|
+
for (const page of pages) {
|
|
2339
|
+
const items = opts.extractItems(page);
|
|
2340
|
+
const total = opts.extractTotal?.(page);
|
|
2341
|
+
if (total !== void 0) knownTotal = total;
|
|
2342
|
+
if (items.length > 0) allEmpty = false;
|
|
2343
|
+
for (const item of items) {
|
|
2344
|
+
if (opts.maxItems !== void 0 && yielded >= opts.maxItems) return;
|
|
2345
|
+
yield item;
|
|
2346
|
+
yielded++;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
if (allEmpty) return;
|
|
2350
|
+
offset += pages.length * pageSize;
|
|
2351
|
+
if (knownTotal !== void 0 && offset >= knownTotal) return;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
async function collect(iter) {
|
|
2355
|
+
const out = [];
|
|
2356
|
+
for await (const item of iter) out.push(item);
|
|
2357
|
+
return out;
|
|
2358
|
+
}
|
|
2359
|
+
function paginatePayments(client, filter = {}, opts = {}) {
|
|
2360
|
+
return paginate(
|
|
2361
|
+
(offset, limit) => client.searchPayments({ ...filter, offset, limit }),
|
|
2362
|
+
{
|
|
2363
|
+
extractItems: (p) => p.results ?? [],
|
|
2364
|
+
extractTotal: (p) => p.paging?.total,
|
|
2365
|
+
...opts
|
|
2366
|
+
}
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
function paginateSubscriptions(client, filter = {}, opts = {}) {
|
|
2370
|
+
return paginate(
|
|
2371
|
+
(offset, limit) => client.searchPreapprovals({ ...filter, offset, limit }),
|
|
2372
|
+
{
|
|
2373
|
+
extractItems: (p) => p.results,
|
|
2374
|
+
extractTotal: (p) => p.paging.total,
|
|
2375
|
+
...opts
|
|
2376
|
+
}
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
function paginateAccountMovements(client, filter = {}, opts = {}) {
|
|
2380
|
+
return paginate(
|
|
2381
|
+
(offset, limit) => client.listAccountMovements({ ...filter, offset, limit }),
|
|
2382
|
+
{
|
|
2383
|
+
extractItems: (p) => p.movements,
|
|
2384
|
+
extractTotal: (p) => p.paging.total,
|
|
2385
|
+
...opts
|
|
2386
|
+
}
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
function paginateSettlements(client, filter = {}, opts = {}) {
|
|
2390
|
+
return paginate(
|
|
2391
|
+
(offset, limit) => client.listSettlements({ ...filter, offset, limit }),
|
|
2392
|
+
{
|
|
2393
|
+
extractItems: (p) => p.settlements,
|
|
2394
|
+
extractTotal: (p) => p.paging.total,
|
|
2395
|
+
...opts
|
|
2396
|
+
}
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2399
|
+
function paginateMerchantOrders(client, filter = {}, opts = {}) {
|
|
2400
|
+
return paginate(
|
|
2401
|
+
(offset, limit) => client.searchMerchantOrders({ ...filter, offset, limit }),
|
|
2402
|
+
{
|
|
2403
|
+
extractItems: (p) => p.elements,
|
|
2404
|
+
extractTotal: (p) => p.paging.total,
|
|
2405
|
+
...opts
|
|
2406
|
+
}
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
function paginateSubscriptionPlans(client, filter = {}, opts = {}) {
|
|
2410
|
+
return paginate(
|
|
2411
|
+
(offset, limit) => client.listSubscriptionPlans({ ...filter, offset, limit }),
|
|
2412
|
+
{
|
|
2413
|
+
extractItems: (p) => p.results,
|
|
2414
|
+
extractTotal: (p) => p.paging.total,
|
|
2415
|
+
...opts
|
|
2416
|
+
}
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
function paginateSubscriptionPayments(client, preapprovalId, opts = {}) {
|
|
2420
|
+
return paginate(
|
|
2421
|
+
(offset, limit) => client.listSubscriptionPayments(preapprovalId, { offset, limit }),
|
|
2422
|
+
{
|
|
2423
|
+
extractItems: (p) => p.results,
|
|
2424
|
+
extractTotal: (p) => p.paging.total,
|
|
2425
|
+
...opts
|
|
2426
|
+
}
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2122
2430
|
// src/test-cards.ts
|
|
2123
2431
|
var TEST_CARDS_AR = {
|
|
2124
2432
|
VISA_CREDIT: {
|
|
@@ -2246,8 +2554,40 @@ function analyze3DS(payment) {
|
|
|
2246
2554
|
description: "No se pudo determinar el estado 3DS \u2014 revisar payment.three_d_secure_mode + payment.status_detail manualmente."
|
|
2247
2555
|
};
|
|
2248
2556
|
}
|
|
2557
|
+
async function confirmChallengeAndPoll(client, paymentId, options = {}) {
|
|
2558
|
+
const maxAttempts = options.maxAttempts ?? 5;
|
|
2559
|
+
const interval = options.pollIntervalMs ?? 1e3;
|
|
2560
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2561
|
+
if (options.signal?.aborted) {
|
|
2562
|
+
const payment3 = await client.getPayment(paymentId);
|
|
2563
|
+
return { payment: payment3, threeDs: analyze3DS(payment3), resolved: false, attempts: attempt };
|
|
2564
|
+
}
|
|
2565
|
+
const payment2 = await client.getPayment(paymentId);
|
|
2566
|
+
const threeDs = analyze3DS(payment2);
|
|
2567
|
+
const stillWaiting = threeDs.status === "challenge_required" || payment2.status === "pending" || payment2.status === "in_process";
|
|
2568
|
+
if (!stillWaiting) {
|
|
2569
|
+
return { payment: payment2, threeDs, resolved: true, attempts: attempt };
|
|
2570
|
+
}
|
|
2571
|
+
if (attempt < maxAttempts) {
|
|
2572
|
+
await new Promise((resolve) => {
|
|
2573
|
+
const timer = setTimeout(resolve, interval);
|
|
2574
|
+
options.signal?.addEventListener(
|
|
2575
|
+
"abort",
|
|
2576
|
+
() => {
|
|
2577
|
+
clearTimeout(timer);
|
|
2578
|
+
resolve(void 0);
|
|
2579
|
+
},
|
|
2580
|
+
{ once: true }
|
|
2581
|
+
);
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
const payment = await client.getPayment(paymentId);
|
|
2586
|
+
return { payment, threeDs: analyze3DS(payment), resolved: false, attempts: maxAttempts };
|
|
2587
|
+
}
|
|
2249
2588
|
|
|
2250
2589
|
// src/webhook.ts
|
|
2590
|
+
init_crypto();
|
|
2251
2591
|
function parseWebhookEvent(body, searchParams) {
|
|
2252
2592
|
const parseResult = WebhookBodySchema.safeParse(body ?? {});
|
|
2253
2593
|
const parsedBody = parseResult.success ? parseResult.data : {};
|
|
@@ -2399,7 +2739,14 @@ var DEFAULT_DESCRIPTIONS = {
|
|
|
2399
2739
|
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.",
|
|
2400
2740
|
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.",
|
|
2401
2741
|
// ── v0.9 — Health check + observability ──────────────────────────────────
|
|
2402
|
-
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."
|
|
2742
|
+
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.",
|
|
2743
|
+
// ── v0.10 — AR issuer cuotas promos (pure) ───────────────────────────────
|
|
2744
|
+
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.",
|
|
2745
|
+
// ── v0.10 — 3DS challenge resolution ────────────────────────────────────
|
|
2746
|
+
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.",
|
|
2747
|
+
// ── v0.10 — Auto-paginate variants ──────────────────────────────────────
|
|
2748
|
+
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.",
|
|
2749
|
+
list_settlements_all: "Collect ALL settlements matching a filter \u2014 auto-paginates. Pass `max_items` to cap. Use for monthly bank-conciliation reports."
|
|
2403
2750
|
};
|
|
2404
2751
|
function mercadoPagoTools(client, options) {
|
|
2405
2752
|
const desc = (name) => options.descriptions?.[name] ?? DEFAULT_DESCRIPTIONS[name];
|
|
@@ -4198,6 +4545,101 @@ function mercadoPagoTools(client, options) {
|
|
|
4198
4545
|
...explanation
|
|
4199
4546
|
};
|
|
4200
4547
|
}
|
|
4548
|
+
}),
|
|
4549
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4550
|
+
// v0.10 — AR issuer promos (pure)
|
|
4551
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4552
|
+
find_applicable_promos: tool({
|
|
4553
|
+
description: desc("find_applicable_promos"),
|
|
4554
|
+
inputSchema: z.object({
|
|
4555
|
+
issuer: z.string().optional().describe("Issuer name (e.g. 'Banco Galicia')"),
|
|
4556
|
+
payment_method_id: z.string().optional().describe("e.g. 'visa', 'master', 'naranja'"),
|
|
4557
|
+
amount_ars: z.number().positive().optional(),
|
|
4558
|
+
category: z.enum([
|
|
4559
|
+
"electronics",
|
|
4560
|
+
"appliances",
|
|
4561
|
+
"clothing",
|
|
4562
|
+
"supermarket",
|
|
4563
|
+
"travel",
|
|
4564
|
+
"education",
|
|
4565
|
+
"health",
|
|
4566
|
+
"general"
|
|
4567
|
+
]).optional(),
|
|
4568
|
+
date: z.string().datetime().optional(),
|
|
4569
|
+
include_ahora_program: z.boolean().optional()
|
|
4570
|
+
}),
|
|
4571
|
+
execute: async (input) => {
|
|
4572
|
+
const args = {};
|
|
4573
|
+
if (input.issuer !== void 0) args.issuer = input.issuer;
|
|
4574
|
+
if (input.payment_method_id !== void 0) args.paymentMethodId = input.payment_method_id;
|
|
4575
|
+
if (input.amount_ars !== void 0) args.amountArs = input.amount_ars;
|
|
4576
|
+
if (input.category !== void 0) args.category = input.category;
|
|
4577
|
+
if (input.date !== void 0) args.date = new Date(input.date);
|
|
4578
|
+
if (input.include_ahora_program !== void 0) args.includeAhoraProgram = input.include_ahora_program;
|
|
4579
|
+
const promos = findApplicablePromos(args);
|
|
4580
|
+
return { ok: true, count: promos.length, promos };
|
|
4581
|
+
}
|
|
4582
|
+
}),
|
|
4583
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4584
|
+
// v0.10 — 3DS challenge resolution (poll-and-confirm)
|
|
4585
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4586
|
+
confirm_3ds_challenge: tool({
|
|
4587
|
+
description: desc("confirm_3ds_challenge"),
|
|
4588
|
+
inputSchema: z.object({
|
|
4589
|
+
payment_id: z.string(),
|
|
4590
|
+
max_attempts: z.number().int().positive().max(20).optional(),
|
|
4591
|
+
poll_interval_ms: z.number().int().positive().max(1e4).optional()
|
|
4592
|
+
}),
|
|
4593
|
+
execute: async ({ payment_id, max_attempts, poll_interval_ms }) => {
|
|
4594
|
+
const args = {};
|
|
4595
|
+
if (max_attempts !== void 0) args.maxAttempts = max_attempts;
|
|
4596
|
+
if (poll_interval_ms !== void 0) args.pollIntervalMs = poll_interval_ms;
|
|
4597
|
+
return confirmChallengeAndPoll(client, payment_id, args);
|
|
4598
|
+
}
|
|
4599
|
+
}),
|
|
4600
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4601
|
+
// v0.10 — Auto-paginate variants (collect-all)
|
|
4602
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4603
|
+
search_payments_all: tool({
|
|
4604
|
+
description: desc("search_payments_all"),
|
|
4605
|
+
inputSchema: z.object({
|
|
4606
|
+
status: z.string().optional(),
|
|
4607
|
+
external_reference: z.string().optional(),
|
|
4608
|
+
from: z.string().optional(),
|
|
4609
|
+
to: z.string().optional(),
|
|
4610
|
+
max_items: z.number().int().positive().max(1e4).optional().describe("Cap on total items returned (default 10,000 hard limit).")
|
|
4611
|
+
}),
|
|
4612
|
+
execute: async ({ max_items, ...filter }) => {
|
|
4613
|
+
const filterClean = {};
|
|
4614
|
+
for (const [k, v] of Object.entries(filter)) {
|
|
4615
|
+
if (v !== void 0) filterClean[k] = v;
|
|
4616
|
+
}
|
|
4617
|
+
const opts = {};
|
|
4618
|
+
if (max_items !== void 0) opts.maxItems = max_items;
|
|
4619
|
+
else opts.maxItems = 1e4;
|
|
4620
|
+
const all = await collect(paginatePayments(client, filterClean, opts));
|
|
4621
|
+
return { ok: true, count: all.length, payments: all };
|
|
4622
|
+
}
|
|
4623
|
+
}),
|
|
4624
|
+
list_settlements_all: tool({
|
|
4625
|
+
description: desc("list_settlements_all"),
|
|
4626
|
+
inputSchema: z.object({
|
|
4627
|
+
from: z.string().optional(),
|
|
4628
|
+
to: z.string().optional(),
|
|
4629
|
+
status: z.string().optional(),
|
|
4630
|
+
max_items: z.number().int().positive().max(1e4).optional()
|
|
4631
|
+
}),
|
|
4632
|
+
execute: async ({ max_items, ...filter }) => {
|
|
4633
|
+
const filterClean = {};
|
|
4634
|
+
if (filter.from !== void 0) filterClean.from = filter.from;
|
|
4635
|
+
if (filter.to !== void 0) filterClean.to = filter.to;
|
|
4636
|
+
if (filter.status !== void 0) filterClean.status = filter.status;
|
|
4637
|
+
const opts = {};
|
|
4638
|
+
if (max_items !== void 0) opts.maxItems = max_items;
|
|
4639
|
+
else opts.maxItems = 1e4;
|
|
4640
|
+
const all = await collect(paginateSettlements(client, filterClean, opts));
|
|
4641
|
+
return { ok: true, count: all.length, settlements: all };
|
|
4642
|
+
}
|
|
4201
4643
|
})
|
|
4202
4644
|
};
|
|
4203
4645
|
}
|
|
@@ -4420,6 +4862,294 @@ var CircuitBreaker = class {
|
|
|
4420
4862
|
}
|
|
4421
4863
|
};
|
|
4422
4864
|
|
|
4423
|
-
|
|
4865
|
+
// src/audit.ts
|
|
4866
|
+
var InMemoryAuditLog = class {
|
|
4867
|
+
entries = [];
|
|
4868
|
+
async append(entry) {
|
|
4869
|
+
this.entries.push(entry);
|
|
4870
|
+
}
|
|
4871
|
+
async query(filter) {
|
|
4872
|
+
const filtered = this.entries.filter((e) => {
|
|
4873
|
+
if (filter.actor && e.actor !== filter.actor) return false;
|
|
4874
|
+
if (filter.operation && e.operation !== filter.operation) return false;
|
|
4875
|
+
if (filter.tenantId && e.tenantId !== filter.tenantId) return false;
|
|
4876
|
+
if (filter.from && e.timestamp < filter.from) return false;
|
|
4877
|
+
if (filter.to && e.timestamp > filter.to) return false;
|
|
4878
|
+
return true;
|
|
4879
|
+
});
|
|
4880
|
+
return filter.limit ? filtered.slice(0, filter.limit) : filtered;
|
|
4881
|
+
}
|
|
4882
|
+
/** All entries (test helper, not part of the adapter interface). */
|
|
4883
|
+
all() {
|
|
4884
|
+
return [...this.entries];
|
|
4885
|
+
}
|
|
4886
|
+
reset() {
|
|
4887
|
+
this.entries.length = 0;
|
|
4888
|
+
}
|
|
4889
|
+
};
|
|
4890
|
+
var AuditLogger = class {
|
|
4891
|
+
adapter;
|
|
4892
|
+
defaultActor;
|
|
4893
|
+
redact;
|
|
4894
|
+
hash;
|
|
4895
|
+
constructor(options) {
|
|
4896
|
+
this.adapter = options.adapter;
|
|
4897
|
+
this.defaultActor = options.defaultActor ?? "unknown";
|
|
4898
|
+
this.redact = options.redact ?? true;
|
|
4899
|
+
this.hash = options.hashFn ?? defaultHasher;
|
|
4900
|
+
}
|
|
4901
|
+
/**
|
|
4902
|
+
* Wrap a tool execute() function with auto-audit. The returned function:
|
|
4903
|
+
* 1. Computes inputHash before the call.
|
|
4904
|
+
* 2. Invokes the original execute().
|
|
4905
|
+
* 3. On success, appends an entry with outcome="ok" + resourceId.
|
|
4906
|
+
* 4. On failure, appends an entry with outcome="error" + errorCode/Message.
|
|
4907
|
+
* 5. Re-throws the error transparently.
|
|
4908
|
+
*/
|
|
4909
|
+
async record(args) {
|
|
4910
|
+
const t0 = Date.now();
|
|
4911
|
+
const inputHash = await this.hash(stableStringify(args.input));
|
|
4912
|
+
try {
|
|
4913
|
+
const result = await args.fn();
|
|
4914
|
+
const resourceId = (args.extractResourceId ?? defaultExtractResourceId)(result);
|
|
4915
|
+
const entry = {
|
|
4916
|
+
id: `mpaud-${(/* @__PURE__ */ new Date()).toISOString()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
4917
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4918
|
+
operation: args.operation,
|
|
4919
|
+
actor: args.actor ?? this.defaultActor,
|
|
4920
|
+
inputHash,
|
|
4921
|
+
outcome: "ok",
|
|
4922
|
+
durationMs: Date.now() - t0
|
|
4923
|
+
};
|
|
4924
|
+
if (args.tenantId !== void 0) entry.tenantId = args.tenantId;
|
|
4925
|
+
if (resourceId !== void 0) entry.resourceId = resourceId;
|
|
4926
|
+
if (args.idempotencyKey !== void 0) entry.idempotencyKey = args.idempotencyKey;
|
|
4927
|
+
if (!this.redact) entry.inputRaw = args.input;
|
|
4928
|
+
await this.adapter.append(entry);
|
|
4929
|
+
return result;
|
|
4930
|
+
} catch (err) {
|
|
4931
|
+
const entry = {
|
|
4932
|
+
id: `mpaud-${(/* @__PURE__ */ new Date()).toISOString()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
4933
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4934
|
+
operation: args.operation,
|
|
4935
|
+
actor: args.actor ?? this.defaultActor,
|
|
4936
|
+
inputHash,
|
|
4937
|
+
outcome: "error",
|
|
4938
|
+
errorCode: extractErrorCode(err),
|
|
4939
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
4940
|
+
durationMs: Date.now() - t0
|
|
4941
|
+
};
|
|
4942
|
+
if (args.tenantId !== void 0) entry.tenantId = args.tenantId;
|
|
4943
|
+
if (args.idempotencyKey !== void 0) entry.idempotencyKey = args.idempotencyKey;
|
|
4944
|
+
if (!this.redact) entry.inputRaw = args.input;
|
|
4945
|
+
await this.adapter.append(entry);
|
|
4946
|
+
throw err;
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
};
|
|
4950
|
+
async function defaultHasher(input) {
|
|
4951
|
+
const { sha256Hex: sha256Hex2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
4952
|
+
return sha256Hex2(input);
|
|
4953
|
+
}
|
|
4954
|
+
function stableStringify(obj) {
|
|
4955
|
+
return JSON.stringify(obj, (_, v) => {
|
|
4956
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
4957
|
+
const sorted = {};
|
|
4958
|
+
for (const k of Object.keys(v).sort()) {
|
|
4959
|
+
sorted[k] = v[k];
|
|
4960
|
+
}
|
|
4961
|
+
return sorted;
|
|
4962
|
+
}
|
|
4963
|
+
return v;
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4966
|
+
function defaultExtractResourceId(result) {
|
|
4967
|
+
if (!result || typeof result !== "object") return void 0;
|
|
4968
|
+
const r = result;
|
|
4969
|
+
for (const key of [
|
|
4970
|
+
"id",
|
|
4971
|
+
"payment_id",
|
|
4972
|
+
"subscription_id",
|
|
4973
|
+
"order_id",
|
|
4974
|
+
"preference_id",
|
|
4975
|
+
"customer_id",
|
|
4976
|
+
"refund_id",
|
|
4977
|
+
"store_id",
|
|
4978
|
+
"pos_id",
|
|
4979
|
+
"merchant_order_id",
|
|
4980
|
+
"intent_id",
|
|
4981
|
+
"device_id"
|
|
4982
|
+
]) {
|
|
4983
|
+
const v = r[key];
|
|
4984
|
+
if (typeof v === "string" || typeof v === "number") return String(v);
|
|
4985
|
+
}
|
|
4986
|
+
return void 0;
|
|
4987
|
+
}
|
|
4988
|
+
function extractErrorCode(err) {
|
|
4989
|
+
if (err && typeof err === "object") {
|
|
4990
|
+
const e = err;
|
|
4991
|
+
return e.code ?? e.name ?? "unknown_error";
|
|
4992
|
+
}
|
|
4993
|
+
return "unknown_error";
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
// src/webhook-dedup.ts
|
|
4997
|
+
var WebhookDedup = class {
|
|
4998
|
+
cache;
|
|
4999
|
+
ttlSeconds;
|
|
5000
|
+
onDuplicate;
|
|
5001
|
+
constructor(opts) {
|
|
5002
|
+
this.cache = opts.cache;
|
|
5003
|
+
this.ttlSeconds = opts.ttlSeconds ?? 7 * 24 * 3600;
|
|
5004
|
+
this.onDuplicate = opts.onDuplicate;
|
|
5005
|
+
}
|
|
5006
|
+
/**
|
|
5007
|
+
* Check whether a webhook delivery has been seen before. If new, mark it
|
|
5008
|
+
* as seen (so subsequent retries return shouldProcess=false). If seen,
|
|
5009
|
+
* return shouldProcess=false WITHOUT marking again.
|
|
5010
|
+
*
|
|
5011
|
+
* **Important**: this method is not atomic across concurrent calls — two
|
|
5012
|
+
* simultaneous deliveries with the same key may both pass shouldProcess=true.
|
|
5013
|
+
* For strict at-most-once processing, follow with a transaction or use a
|
|
5014
|
+
* cache that supports `setNX`-style semantics (Redis, Cloudflare KV with
|
|
5015
|
+
* conditional writes).
|
|
5016
|
+
*
|
|
5017
|
+
* For most webhook handlers this race is acceptable: even if two get
|
|
5018
|
+
* through, the downstream business logic (e.g., "charge if not already
|
|
5019
|
+
* charged") will be idempotent on its own.
|
|
5020
|
+
*/
|
|
5021
|
+
async check(args) {
|
|
5022
|
+
const deliveryKey = this.deriveKey(args);
|
|
5023
|
+
const seen = await this.cache.get(deliveryKey);
|
|
5024
|
+
if (seen) {
|
|
5025
|
+
this.onDuplicate?.(deliveryKey);
|
|
5026
|
+
return { shouldProcess: false, deliveryKey };
|
|
5027
|
+
}
|
|
5028
|
+
await this.cache.set(deliveryKey, true, this.ttlSeconds);
|
|
5029
|
+
return { shouldProcess: true, deliveryKey };
|
|
5030
|
+
}
|
|
5031
|
+
/**
|
|
5032
|
+
* Manually mark a delivery as processed. Call this AFTER your business
|
|
5033
|
+
* logic succeeds — useful when you want to control when the dedup
|
|
5034
|
+
* marker is written (e.g., only on success).
|
|
5035
|
+
*
|
|
5036
|
+
* Combined with calling `check()` BEFORE the work, this gives "at-least-once"
|
|
5037
|
+
* semantics: failed processing → no marker → retry will be processed again.
|
|
5038
|
+
*/
|
|
5039
|
+
async markProcessed(args) {
|
|
5040
|
+
const deliveryKey = this.deriveKey(args);
|
|
5041
|
+
await this.cache.set(deliveryKey, true, this.ttlSeconds);
|
|
5042
|
+
}
|
|
5043
|
+
/**
|
|
5044
|
+
* Variant of `check` that doesn't mark on first sight — caller must
|
|
5045
|
+
* explicitly `markProcessed` when their business logic succeeds.
|
|
5046
|
+
* Use this for at-least-once semantics (each delivery processed at
|
|
5047
|
+
* least once, possibly more if processing fails before mark).
|
|
5048
|
+
*/
|
|
5049
|
+
async peekIsDuplicate(args) {
|
|
5050
|
+
const deliveryKey = this.deriveKey(args);
|
|
5051
|
+
const seen = await this.cache.get(deliveryKey);
|
|
5052
|
+
if (seen) this.onDuplicate?.(deliveryKey);
|
|
5053
|
+
return { shouldProcess: !seen, deliveryKey };
|
|
5054
|
+
}
|
|
5055
|
+
deriveKey(args) {
|
|
5056
|
+
return `mp:webhook:${args.topic}:${args.dataId}:${args.requestId ?? "noreqid"}`;
|
|
5057
|
+
}
|
|
5058
|
+
};
|
|
5059
|
+
|
|
5060
|
+
// src/rate-limiter.ts
|
|
5061
|
+
var RateLimitTimeoutError = class extends Error {
|
|
5062
|
+
constructor(waitedMs) {
|
|
5063
|
+
super(`Rate limit acquire timed out after ${waitedMs}ms.`);
|
|
5064
|
+
this.waitedMs = waitedMs;
|
|
5065
|
+
this.name = "RateLimitTimeoutError";
|
|
5066
|
+
}
|
|
5067
|
+
waitedMs;
|
|
5068
|
+
};
|
|
5069
|
+
var TokenBucketRateLimiter = class {
|
|
5070
|
+
tokens;
|
|
5071
|
+
lastRefill;
|
|
5072
|
+
capacity;
|
|
5073
|
+
refillPerSecond;
|
|
5074
|
+
adaptive;
|
|
5075
|
+
acquireTimeoutMs;
|
|
5076
|
+
now;
|
|
5077
|
+
constructor(opts = {}) {
|
|
5078
|
+
this.capacity = opts.capacity ?? 50;
|
|
5079
|
+
this.refillPerSecond = opts.refillPerSecond ?? 25;
|
|
5080
|
+
this.adaptive = opts.adaptive ?? true;
|
|
5081
|
+
this.acquireTimeoutMs = opts.acquireTimeoutMs ?? 3e4;
|
|
5082
|
+
this.now = opts.now ?? Date.now;
|
|
5083
|
+
this.tokens = this.capacity;
|
|
5084
|
+
this.lastRefill = this.now();
|
|
5085
|
+
}
|
|
5086
|
+
/**
|
|
5087
|
+
* Acquire a token. Resolves immediately if tokens are available;
|
|
5088
|
+
* otherwise waits until one is. Rejects with `RateLimitTimeoutError`
|
|
5089
|
+
* if the wait exceeds `acquireTimeoutMs`.
|
|
5090
|
+
*/
|
|
5091
|
+
async acquire() {
|
|
5092
|
+
this.refill();
|
|
5093
|
+
if (this.tokens >= 1) {
|
|
5094
|
+
this.tokens -= 1;
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
const tokensNeeded = 1 - this.tokens;
|
|
5098
|
+
const waitMs = Math.ceil(tokensNeeded / this.refillPerSecond * 1e3);
|
|
5099
|
+
if (waitMs > this.acquireTimeoutMs) {
|
|
5100
|
+
throw new RateLimitTimeoutError(waitMs);
|
|
5101
|
+
}
|
|
5102
|
+
await sleep2(waitMs);
|
|
5103
|
+
this.refill();
|
|
5104
|
+
this.tokens -= 1;
|
|
5105
|
+
}
|
|
5106
|
+
/**
|
|
5107
|
+
* Best-effort acquire: returns true if a token was available, false
|
|
5108
|
+
* otherwise. Doesn't wait. Useful for "non-blocking" code paths that
|
|
5109
|
+
* want to fall back to a cached response or queue the request elsewhere.
|
|
5110
|
+
*/
|
|
5111
|
+
tryAcquire() {
|
|
5112
|
+
this.refill();
|
|
5113
|
+
if (this.tokens >= 1) {
|
|
5114
|
+
this.tokens -= 1;
|
|
5115
|
+
return true;
|
|
5116
|
+
}
|
|
5117
|
+
return false;
|
|
5118
|
+
}
|
|
5119
|
+
/**
|
|
5120
|
+
* Adaptive learning hook — call after every API response with MP's
|
|
5121
|
+
* rate-limit headers to keep the bucket in sync with reality.
|
|
5122
|
+
*/
|
|
5123
|
+
learnFromHeaders(headers) {
|
|
5124
|
+
if (!this.adaptive) return;
|
|
5125
|
+
if (headers.remaining === null) return;
|
|
5126
|
+
this.refill();
|
|
5127
|
+
if (headers.remaining < this.tokens) {
|
|
5128
|
+
this.tokens = Math.max(0, headers.remaining);
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
/** Inspect the current bucket state. */
|
|
5132
|
+
getStats() {
|
|
5133
|
+
this.refill();
|
|
5134
|
+
return {
|
|
5135
|
+
tokens: this.tokens,
|
|
5136
|
+
capacity: this.capacity,
|
|
5137
|
+
refillPerSecond: this.refillPerSecond
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
refill() {
|
|
5141
|
+
const now = this.now();
|
|
5142
|
+
const elapsedMs = now - this.lastRefill;
|
|
5143
|
+
if (elapsedMs <= 0) return;
|
|
5144
|
+
const refilled = elapsedMs / 1e3 * this.refillPerSecond;
|
|
5145
|
+
this.tokens = Math.min(this.capacity, this.tokens + refilled);
|
|
5146
|
+
this.lastRefill = now;
|
|
5147
|
+
}
|
|
5148
|
+
};
|
|
5149
|
+
function sleep2(ms) {
|
|
5150
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
export { AHORA_PROGRAM_PROMOS, AR_ISSUER_PROMOS, AuditLogger, CircuitBreaker, CircuitOpenError, InMemoryAuditLog, InMemoryIdempotencyCache, InMemoryOAuthTokenStore, InMemoryStateAdapter, MercadoPagoAccountTypeMismatchError, MercadoPagoAuthError, MercadoPagoAuthorizeForbiddenError, MercadoPagoBackUrlInvalidError, MercadoPagoClient, MercadoPagoError, MercadoPagoOverloadedError, MercadoPagoPaymentRejectedError, MercadoPagoRateLimitError, MercadoPagoSelfPaymentError, MercadoPagoTimeoutError, RateLimitTimeoutError, TEST_CARDS_AR, TEST_PAYERS_AR, TokenBucketRateLimiter, WebhookDedup, analyze3DS, buildAuthorizeUrl, buildTestCardScenario, classifyError, collect, computeMarketplaceFee, confirmChallengeAndPoll, exchangeCodeForToken, expirationTimeMs, explainPaymentStatus, findApplicablePromos, isExpiringSoon, mercadoPagoTools, paginate, paginateAccountMovements, paginateMerchantOrders, paginatePayments, paginateSettlements, paginateSubscriptionPayments, paginateSubscriptionPlans, paginateSubscriptions, parseWebhookEvent, refreshAccessToken, verifyWebhookSignature };
|
|
4424
5154
|
//# sourceMappingURL=index.js.map
|
|
4425
5155
|
//# sourceMappingURL=index.js.map
|