@ar-agents/mercadopago 0.8.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/dist/index.d.cts CHANGED
@@ -1,7 +1,172 @@
1
1
  import { z } from 'zod';
2
+ import { I as IdempotencyCache, S as SubscriptionStateAdapter } from './state-C6Wzb_XX.cjs';
3
+ export { a as InMemoryIdempotencyCache, b as InMemoryOAuthTokenStore, c as InMemoryStateAdapter, O as OAuthTokenRecord, d as OAuthTokenStore, e as SubscriptionStateRecord } from './state-C6Wzb_XX.cjs';
2
4
  import { ToolSet } from 'ai';
3
- import { S as SubscriptionStateAdapter } from './state-C6Wzb_XX.cjs';
4
- export { I as IdempotencyCache, a as InMemoryIdempotencyCache, b as InMemoryOAuthTokenStore, c as InMemoryStateAdapter, O as OAuthTokenRecord, d as OAuthTokenStore, e as SubscriptionStateRecord } from './state-C6Wzb_XX.cjs';
5
+
6
+ /**
7
+ * Circuit breaker — protects your app from cascading failures when MP
8
+ * (or any upstream) is degraded.
9
+ *
10
+ * # Why
11
+ *
12
+ * When MP's API has an outage, naive retry-with-backoff still pounds the
13
+ * dead service N times per request × every concurrent request. That makes
14
+ * MP's outage worse AND your app's error rate worse (each request burns
15
+ * `requestTimeoutMs × maxRetries` ms of CPU/event-loop time before failing).
16
+ *
17
+ * A circuit breaker observes failures over a rolling window. After enough
18
+ * failures it OPENS — subsequent calls fail fast (no network round-trip)
19
+ * with a `CircuitOpenError`. After a cooldown it HALF-OPENs — lets one
20
+ * trial through. If that succeeds, it CLOSES (back to normal). If it
21
+ * fails, it RE-OPENs for another cooldown.
22
+ *
23
+ * # State machine
24
+ *
25
+ * CLOSED ──(failures ≥ threshold)──▶ OPEN
26
+ * ▲ │
27
+ * │ │ (cooldown elapsed)
28
+ * │ ▼
29
+ * │ HALF_OPEN
30
+ * │ │
31
+ * └──(trial succeeds)────────────────┤
32
+ * │ (trial fails)
33
+ * ▼
34
+ * OPEN
35
+ *
36
+ * # When to use
37
+ *
38
+ * - **Protects YOUR app** from being slow/dead when MP is slow/dead.
39
+ * - **Protects MP** from your app pummeling it during incidents.
40
+ * - **Surfaces a clear signal to ops**: `circuit_open` event tells you
41
+ * "MP is broken, my app is intentionally short-circuiting" — different
42
+ * from "MP timed out 30s × 3 retries × 1000 concurrent users".
43
+ *
44
+ * # When NOT to use
45
+ *
46
+ * - For idempotent reads where stale-cached data is acceptable, prefer
47
+ * a cache-aside pattern instead.
48
+ * - For fire-and-forget webhooks where the backpressure should propagate
49
+ * to MP itself (return 5xx, MP retries with backoff).
50
+ *
51
+ * # Configuration
52
+ *
53
+ * Defaults are tuned for typical MP traffic patterns:
54
+ * - `failureThreshold: 5` — open after 5 consecutive failures
55
+ * - `successThreshold: 2` — close after 2 trial successes (half-open)
56
+ * - `resetTimeoutMs: 30_000` — 30s cooldown before half-open trial
57
+ * - `monitoringWindowMs: 60_000` — count failures within a 60s window
58
+ *
59
+ * # Per-host vs global
60
+ *
61
+ * The default `MercadoPagoClient` uses ONE breaker per client instance
62
+ * (which means one per upstream host: `api.mercadopago.com` for prod,
63
+ * `api.mercadopago.com` sandbox for TEST). For multi-host setups (e.g.,
64
+ * marketplace flows with per-seller clients), instantiate a SHARED breaker
65
+ * and pass it to all clients — they all benefit from the same backpressure
66
+ * signal.
67
+ */
68
+ type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
69
+ interface CircuitBreakerOptions {
70
+ /** Open the breaker after this many consecutive failures. Default 5. */
71
+ failureThreshold?: number;
72
+ /** Close the breaker after this many successive successes in HALF_OPEN. Default 2. */
73
+ successThreshold?: number;
74
+ /** Time to stay OPEN before allowing a HALF_OPEN trial. Default 30s. */
75
+ resetTimeoutMs?: number;
76
+ /** Rolling window for counting failures. Failures older than this don't count. Default 60s. */
77
+ monitoringWindowMs?: number;
78
+ /**
79
+ * Called on EVERY state transition. Useful for emitting metrics/logs.
80
+ * `cause` is the error that triggered the transition (when applicable).
81
+ */
82
+ onStateChange?: (event: {
83
+ from: CircuitState;
84
+ to: CircuitState;
85
+ cause?: unknown;
86
+ consecutiveFailures: number;
87
+ }) => void;
88
+ /**
89
+ * Predicate to decide whether an error should count as a circuit failure.
90
+ * By default, all errors count. Override to ignore expected business
91
+ * errors (e.g., 404s, validation errors) — they shouldn't open the breaker.
92
+ */
93
+ isFailure?: (error: unknown) => boolean;
94
+ /** Time provider (for tests). Defaults to `Date.now`. */
95
+ now?: () => number;
96
+ }
97
+ /**
98
+ * Thrown when a circuit breaker is OPEN and rejects a call without trying.
99
+ * Catch this separately from MercadoPagoError to differentiate "MP said no"
100
+ * from "we didn't even ask MP".
101
+ */
102
+ declare class CircuitOpenError extends Error {
103
+ readonly retryAfterMs: number;
104
+ readonly consecutiveFailures: number;
105
+ constructor(retryAfterMs: number, consecutiveFailures: number);
106
+ }
107
+ /**
108
+ * Thread-safe circuit breaker. Single-instance per upstream (typically per
109
+ * `MercadoPagoClient`). Pass to multiple clients to share state.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * import { CircuitBreaker, MercadoPagoClient } from "@ar-agents/mercadopago";
114
+ *
115
+ * const breaker = new CircuitBreaker({
116
+ * failureThreshold: 5,
117
+ * resetTimeoutMs: 30_000,
118
+ * onStateChange: (e) => metrics.increment(`circuit.${e.to}`),
119
+ * });
120
+ *
121
+ * const client = new MercadoPagoClient({
122
+ * accessToken: process.env.MP_ACCESS_TOKEN!,
123
+ * circuitBreaker: breaker,
124
+ * });
125
+ * ```
126
+ */
127
+ declare class CircuitBreaker {
128
+ private state;
129
+ private consecutiveFailures;
130
+ private halfOpenSuccesses;
131
+ private openedAt;
132
+ /** Timestamps of failures within the monitoring window. */
133
+ private failureWindow;
134
+ private readonly failureThreshold;
135
+ private readonly successThreshold;
136
+ private readonly resetTimeoutMs;
137
+ private readonly monitoringWindowMs;
138
+ private readonly onStateChange;
139
+ private readonly isFailureFn;
140
+ private readonly now;
141
+ constructor(opts?: CircuitBreakerOptions);
142
+ /** Read the current state. Useful for health checks + metrics. */
143
+ getState(): CircuitState;
144
+ /** Read diagnostic state for health checks + dashboards. */
145
+ getStats(): {
146
+ state: CircuitState;
147
+ consecutiveFailures: number;
148
+ failuresInWindow: number;
149
+ msSinceOpened: number | null;
150
+ msUntilHalfOpen: number | null;
151
+ };
152
+ /**
153
+ * Execute `fn` under the breaker's protection.
154
+ * - If the breaker is OPEN, throws `CircuitOpenError` immediately.
155
+ * - If `fn` succeeds, may transition HALF_OPEN → CLOSED.
156
+ * - If `fn` fails (and the error counts as a failure), records the
157
+ * failure; may transition CLOSED → OPEN or HALF_OPEN → OPEN.
158
+ */
159
+ execute<T>(fn: () => Promise<T>): Promise<T>;
160
+ /** Manually force the breaker open. Useful for runbook / manual ops. */
161
+ trip(reason?: unknown): void;
162
+ /** Manually reset the breaker to CLOSED. */
163
+ reset(): void;
164
+ private recordSuccess;
165
+ private recordFailure;
166
+ private transitionTo;
167
+ private pruneWindow;
168
+ private failuresInCurrentWindow;
169
+ }
5
170
 
6
171
  /**
7
172
  * Base class for any error originating from the Mercado Pago integration. All
@@ -1120,6 +1285,10 @@ interface MercadoPagoClientOptions {
1120
1285
  /**
1121
1286
  * Observability hook fired AFTER every request (success or failure).
1122
1287
  * Useful for logging, metrics, tracing. Synchronous, fire-and-forget.
1288
+ *
1289
+ * The `traceContext` field follows the W3C Trace Context spec — pass
1290
+ * an OpenTelemetry-compatible context propagator and you get full
1291
+ * distributed tracing for free. See `traceContext` option below.
1123
1292
  */
1124
1293
  onCall?: (event: {
1125
1294
  method: string;
@@ -1128,7 +1297,52 @@ interface MercadoPagoClientOptions {
1128
1297
  httpStatus: number | null;
1129
1298
  retried: number;
1130
1299
  success: boolean;
1300
+ /** v0.9: MP's `x-request-id` echo. Useful for support tickets. */
1301
+ requestId?: string | null;
1302
+ /** v0.9: MP's rate-limit headers when present. */
1303
+ rateLimit?: {
1304
+ remaining: number | null;
1305
+ resetSeconds: number | null;
1306
+ };
1307
+ /** v0.9: Circuit breaker state at the time of the call. */
1308
+ circuitState?: "CLOSED" | "OPEN" | "HALF_OPEN";
1309
+ /** v0.9: Trace context for OpenTelemetry-style propagation. */
1310
+ traceContext?: {
1311
+ traceId?: string;
1312
+ spanId?: string;
1313
+ };
1131
1314
  }) => void;
1315
+ /**
1316
+ * v0.9 — Opt-in circuit breaker. When MP is failing, fail fast instead of
1317
+ * piling up retries against a dead service. Pass a configured instance
1318
+ * (or share one across multiple clients to give them shared backpressure
1319
+ * signal).
1320
+ *
1321
+ * @example
1322
+ * ```ts
1323
+ * const breaker = new CircuitBreaker({
1324
+ * failureThreshold: 5,
1325
+ * resetTimeoutMs: 30_000,
1326
+ * onStateChange: (e) => metrics.gauge("circuit.state", e.to),
1327
+ * });
1328
+ * const client = new MercadoPagoClient({ accessToken: "...", circuitBreaker: breaker });
1329
+ * ```
1330
+ */
1331
+ circuitBreaker?: CircuitBreaker;
1332
+ /**
1333
+ * v0.9 — Optional W3C Trace Context propagator. If provided, the client
1334
+ * extracts traceId/spanId on each request, injects `traceparent` /
1335
+ * `tracestate` headers (MP echoes them back via x-request-id), and surfaces
1336
+ * them in `onCall` events. Compatible with OpenTelemetry without adding
1337
+ * `@opentelemetry/api` as a peer dep.
1338
+ *
1339
+ * If you have OTEL set up, just pass `() => trace.getActiveSpan()?.spanContext()`.
1340
+ */
1341
+ traceContext?: () => {
1342
+ traceId?: string;
1343
+ spanId?: string;
1344
+ traceFlags?: number;
1345
+ } | undefined;
1132
1346
  }
1133
1347
  interface RequestOptions {
1134
1348
  /** Idempotency key. Required for POST/PUT to dedupe retries safely. */
@@ -1143,6 +1357,13 @@ interface RequestOptions {
1143
1357
  payerEmail?: string;
1144
1358
  sellerEmail?: string;
1145
1359
  };
1360
+ /**
1361
+ * v0.9 — Parent AbortSignal for deadline propagation. When the agent
1362
+ * has a fixed budget (e.g., 5s for the whole tool call), pass it here.
1363
+ * The client merges it with its own per-request timeout — whichever
1364
+ * fires first wins.
1365
+ */
1366
+ signal?: AbortSignal;
1146
1367
  }
1147
1368
  /**
1148
1369
  * Thin, typed wrapper around Mercado Pago's REST API. Exposes the surface
@@ -1158,8 +1379,16 @@ declare class MercadoPagoClient {
1158
1379
  private readonly requestTimeoutMs;
1159
1380
  private readonly maxRetries;
1160
1381
  private readonly onCall;
1382
+ private readonly circuitBreaker;
1383
+ private readonly traceContext;
1161
1384
  constructor(options: MercadoPagoClientOptions);
1385
+ /**
1386
+ * v0.9 — Inspect the circuit breaker state (when configured). Returns
1387
+ * `null` when no circuit breaker is wired. Useful for health checks.
1388
+ */
1389
+ getCircuitState(): ReturnType<CircuitBreaker["getStats"]> | null;
1162
1390
  private request;
1391
+ private requestUnprotected;
1163
1392
  /**
1164
1393
  * Create a recurring subscription (preapproval). The returned `init_point`
1165
1394
  * URL is where the buyer must complete the FIRST payment with their card +
@@ -1625,6 +1854,358 @@ declare class MercadoPagoClient {
1625
1854
  id: string;
1626
1855
  canceled: true;
1627
1856
  }>;
1857
+ /**
1858
+ * Liveness probe against MP. Returns latency + circuit-breaker state.
1859
+ * Use as a /health endpoint for k8s, Vercel cron, or status-page checks.
1860
+ *
1861
+ * Returns `{ ok: false, ... }` instead of throwing — designed for
1862
+ * monitoring loops that want to keep running.
1863
+ *
1864
+ * @param signal Optional AbortSignal to cap wait time (e.g., 2s for
1865
+ * status-page polling).
1866
+ */
1867
+ healthCheck(signal?: AbortSignal): Promise<{
1868
+ ok: boolean;
1869
+ latencyMs: number;
1870
+ /** MP user_id when reachable. */
1871
+ userId: string | null;
1872
+ /** Last error message when not OK. */
1873
+ error: string | null;
1874
+ /** Circuit breaker state when configured. */
1875
+ circuit: ReturnType<CircuitBreaker["getStats"]> | null;
1876
+ }>;
1877
+ }
1878
+
1879
+ /**
1880
+ * Webhook idempotency / dedup — short-circuits duplicate webhook deliveries
1881
+ * from MP to prevent double-processing.
1882
+ *
1883
+ * # The problem
1884
+ *
1885
+ * MP retries webhook deliveries on 5xx responses. The retry policy is
1886
+ * exponential backoff: 5min, 15min, 30min, 1h, 6h, 24h, 48h, 96h, 192h
1887
+ * (~12 attempts over 8 days). If your handler temporarily 5xx'd (DB
1888
+ * down, deploy in progress, etc.) and then recovered, you'll receive
1889
+ * the SAME webhook 5+ times. Without dedup:
1890
+ *
1891
+ * - You double-charge (if the webhook triggers a charge)
1892
+ * - You double-send notifications (5 emails to the buyer instead of 1)
1893
+ * - You double-create downstream resources
1894
+ *
1895
+ * # The fix
1896
+ *
1897
+ * Cache the unique "delivery key" of every webhook you've successfully
1898
+ * processed. On retry, recognize the key, return 200 immediately, skip
1899
+ * processing.
1900
+ *
1901
+ * # The "delivery key"
1902
+ *
1903
+ * MP doesn't ship a single canonical id per delivery, but the tuple
1904
+ * `${topic}:${dataId}:${requestId}` is stable for retries (same delivery
1905
+ * attempt → same x-request-id) and unique enough to dedupe.
1906
+ *
1907
+ * # Storage
1908
+ *
1909
+ * Reuse `IdempotencyCache` from `state.ts`. Default TTL: 7 days (matches
1910
+ * MP's webhook retry window). Override per-deployment.
1911
+ */
1912
+
1913
+ interface WebhookDedupOptions {
1914
+ /**
1915
+ * Storage for processed webhook ids. Plug in `VercelKVIdempotencyCache`
1916
+ * for production or `InMemoryIdempotencyCache` for tests.
1917
+ */
1918
+ cache: IdempotencyCache;
1919
+ /**
1920
+ * Time-to-live for dedup entries (seconds). Default 7 days — covers MP's
1921
+ * full retry window (~8 days) with a safety margin.
1922
+ */
1923
+ ttlSeconds?: number;
1924
+ /**
1925
+ * Optional callback fired when a duplicate is detected. Useful for
1926
+ * metrics ("webhooks deduped" counter).
1927
+ */
1928
+ onDuplicate?: (deliveryKey: string) => void;
1929
+ }
1930
+ interface DedupResult {
1931
+ /**
1932
+ * `true` if this is the first time we've seen this delivery — caller
1933
+ * should process it.
1934
+ * `false` if it's a retry of a previously-seen delivery — caller should
1935
+ * acknowledge with 200 and skip processing.
1936
+ */
1937
+ shouldProcess: boolean;
1938
+ /** The deduplication key derived from the webhook. */
1939
+ deliveryKey: string;
1940
+ }
1941
+ /**
1942
+ * Dedup helper. Use this BEFORE processing a webhook to short-circuit retries.
1943
+ *
1944
+ * @example
1945
+ * ```ts
1946
+ * import { WebhookDedup, VercelKVIdempotencyCache } from "@ar-agents/mercadopago";
1947
+ *
1948
+ * const dedup = new WebhookDedup({
1949
+ * cache: new VercelKVIdempotencyCache(),
1950
+ * onDuplicate: (key) => metrics.increment("mp.webhook.duplicate"),
1951
+ * });
1952
+ *
1953
+ * export async function POST(req: Request) {
1954
+ * const event = parseWebhookEvent(...);
1955
+ * if (!event) return new Response("bad request", { status: 400 });
1956
+ *
1957
+ * const requestId = req.headers.get("x-request-id");
1958
+ * const { shouldProcess } = await dedup.check({
1959
+ * topic: event.topic,
1960
+ * dataId: event.dataId,
1961
+ * requestId,
1962
+ * });
1963
+ * if (!shouldProcess) return new Response("ok (duplicate)", { status: 200 });
1964
+ *
1965
+ * // ... process the webhook ...
1966
+ *
1967
+ * return new Response("ok", { status: 200 });
1968
+ * }
1969
+ * ```
1970
+ */
1971
+ declare class WebhookDedup {
1972
+ private readonly cache;
1973
+ private readonly ttlSeconds;
1974
+ private readonly onDuplicate;
1975
+ constructor(opts: WebhookDedupOptions);
1976
+ /**
1977
+ * Check whether a webhook delivery has been seen before. If new, mark it
1978
+ * as seen (so subsequent retries return shouldProcess=false). If seen,
1979
+ * return shouldProcess=false WITHOUT marking again.
1980
+ *
1981
+ * **Important**: this method is not atomic across concurrent calls — two
1982
+ * simultaneous deliveries with the same key may both pass shouldProcess=true.
1983
+ * For strict at-most-once processing, follow with a transaction or use a
1984
+ * cache that supports `setNX`-style semantics (Redis, Cloudflare KV with
1985
+ * conditional writes).
1986
+ *
1987
+ * For most webhook handlers this race is acceptable: even if two get
1988
+ * through, the downstream business logic (e.g., "charge if not already
1989
+ * charged") will be idempotent on its own.
1990
+ */
1991
+ check(args: {
1992
+ topic: string;
1993
+ dataId: string;
1994
+ requestId: string | null;
1995
+ }): Promise<DedupResult>;
1996
+ /**
1997
+ * Manually mark a delivery as processed. Call this AFTER your business
1998
+ * logic succeeds — useful when you want to control when the dedup
1999
+ * marker is written (e.g., only on success).
2000
+ *
2001
+ * Combined with calling `check()` BEFORE the work, this gives "at-least-once"
2002
+ * semantics: failed processing → no marker → retry will be processed again.
2003
+ */
2004
+ markProcessed(args: {
2005
+ topic: string;
2006
+ dataId: string;
2007
+ requestId: string | null;
2008
+ }): Promise<void>;
2009
+ /**
2010
+ * Variant of `check` that doesn't mark on first sight — caller must
2011
+ * explicitly `markProcessed` when their business logic succeeds.
2012
+ * Use this for at-least-once semantics (each delivery processed at
2013
+ * least once, possibly more if processing fails before mark).
2014
+ */
2015
+ peekIsDuplicate(args: {
2016
+ topic: string;
2017
+ dataId: string;
2018
+ requestId: string | null;
2019
+ }): Promise<DedupResult>;
2020
+ private deriveKey;
2021
+ }
2022
+
2023
+ /**
2024
+ * Compute SHA-256 hash of `input`. Returns the full 64-char hex digest.
2025
+ *
2026
+ * Used for deterministic idempotency keys derived from caller-meaningful
2027
+ * fields. Truncate the output to 32 chars for storage if needed.
2028
+ */
2029
+ declare function sha256Hex(input: string): Promise<string>;
2030
+
2031
+ /**
2032
+ * Audit logging — financial-grade compliance trail for every state-mutating
2033
+ * operation. Captures who/what/when/idempotency_key/before/after/result.
2034
+ *
2035
+ * # Why this is a tier-1 feature
2036
+ *
2037
+ * Every mature payment integration has an audit log. The compliance officer
2038
+ * asks "show me every refund issued in March 2026 by user X" and you need
2039
+ * to answer in <60 seconds. Without an audit log, you're trawling through
2040
+ * application logs hoping nothing was filtered out.
2041
+ *
2042
+ * # What gets logged
2043
+ *
2044
+ * Every **state-mutating** tool call automatically:
2045
+ * - `create_payment`, `charge_saved_card`, `cancel_payment`, `capture_payment`
2046
+ * - `refund_payment`
2047
+ * - `create_subscription`, `cancel/pause/resume_subscription`, `update_subscription`
2048
+ * - `create_order`, `capture_order`, `cancel_order`
2049
+ * - `create_payment_preference`, `update_payment_preference`
2050
+ * - `create_customer`, `update_customer`, `create_customer_card`, `delete_customer_card`
2051
+ * - `create_subscription_plan`, `update_subscription_plan`
2052
+ * - `create_store/pos`, `update_store/pos`, `delete_store/pos`
2053
+ * - `create_qr_payment`, `cancel_qr_payment`
2054
+ * - `create_point_payment_intent`, `cancel_point_payment_intent`, `update_point_device_mode`
2055
+ * - OAuth: `oauth_exchange_code`, `oauth_refresh_token`
2056
+ * - `register_bank_account`
2057
+ * - `create_webhook`, `update_webhook`, `delete_webhook`
2058
+ *
2059
+ * **Read-only** tools do NOT emit audit entries (would flood the log without
2060
+ * value): get_*, search_*, list_*, calculate_*, validate_*, lookup_*, analyze_*.
2061
+ *
2062
+ * # PII handling
2063
+ *
2064
+ * The audit log captures `inputSummary` (a deterministic hash of the input
2065
+ * fields, NOT the raw input) by default. Configure `redact: false` to log
2066
+ * raw inputs (payer email, CUIT, etc.) — only when your data-residency
2067
+ * policy permits.
2068
+ *
2069
+ * # Storage
2070
+ *
2071
+ * Pluggable adapter pattern. Ships:
2072
+ * - `InMemoryAuditLog` — for tests + single-process demos.
2073
+ * - `VercelKVAuditLog` (in `/vercel-kv` subpath) — production-ready, KV-backed
2074
+ * with daily-bucket indexing for efficient time-range queries.
2075
+ *
2076
+ * Implement your own for Postgres / S3 / SIEM integration.
2077
+ */
2078
+
2079
+ type AuditOperation = "create_payment" | "charge_saved_card" | "cancel_payment" | "capture_payment" | "refund_payment" | "create_subscription" | "cancel_subscription" | "pause_subscription" | "resume_subscription" | "update_subscription" | "subscribe_to_plan" | "create_subscription_plan" | "update_subscription_plan" | "create_order" | "capture_order" | "cancel_order" | "update_order" | "create_payment_preference" | "update_payment_preference" | "create_customer" | "update_customer" | "create_customer_card" | "delete_customer_card" | "create_store" | "update_store" | "delete_store" | "create_pos" | "update_pos" | "delete_pos" | "create_qr_payment" | "cancel_qr_payment" | "create_point_payment_intent" | "cancel_point_payment_intent" | "update_point_device_mode" | "oauth_exchange_code" | "oauth_refresh_token" | "register_bank_account" | "create_webhook" | "update_webhook" | "delete_webhook" | (string & {});
2080
+ interface AuditEntry {
2081
+ /**
2082
+ * Unique entry id. Format: `mpaud-{ISO date}-{random}`. Use as primary
2083
+ * key in your storage layer.
2084
+ */
2085
+ id: string;
2086
+ /** ISO 8601 timestamp. */
2087
+ timestamp: string;
2088
+ /** The MP operation performed. */
2089
+ operation: AuditOperation;
2090
+ /**
2091
+ * Logical actor that initiated the call. Caller-provided. Examples:
2092
+ * `"agent:billing-bot"`, `"user:42"`, `"cron:daily-charge"`.
2093
+ *
2094
+ * Defaults to `"unknown"` when not provided. **Always pass this in
2095
+ * production** — without it, your compliance trail is meaningless.
2096
+ */
2097
+ actor: string;
2098
+ /** Optional tenant/seller id for multi-tenant marketplace setups. */
2099
+ tenantId?: string;
2100
+ /**
2101
+ * SHA-256 hex of the meaningful input fields (deterministic). Useful as
2102
+ * a join key with the IdempotencyCache. Does NOT contain raw PII.
2103
+ */
2104
+ inputHash: string;
2105
+ /**
2106
+ * Optional raw input — only populated when `redact: false` was configured.
2107
+ * Defaults to undefined to comply with data-minimization principles.
2108
+ */
2109
+ inputRaw?: Record<string, unknown>;
2110
+ /** Outcome: success or error code. */
2111
+ outcome: "ok" | "error";
2112
+ /** Error code when `outcome === "error"`. */
2113
+ errorCode?: string;
2114
+ /** Error message when `outcome === "error"`. */
2115
+ errorMessage?: string;
2116
+ /**
2117
+ * MP resource id created/updated by the operation (e.g., payment id).
2118
+ * Allows joining audit entries to the actual MP resource.
2119
+ */
2120
+ resourceId?: string;
2121
+ /** Idempotency key passed to MP (for join with MP-side dedup logs). */
2122
+ idempotencyKey?: string;
2123
+ /** Duration in ms. */
2124
+ durationMs?: number;
2125
+ /** Free-form metadata bag. */
2126
+ metadata?: Record<string, unknown>;
2127
+ }
2128
+ interface AuditLogAdapter {
2129
+ append(entry: AuditEntry): Promise<void>;
2130
+ /** Query a time range. Optional — implementations that don't support it can omit. */
2131
+ query?(filter: {
2132
+ actor?: string;
2133
+ operation?: AuditOperation;
2134
+ tenantId?: string;
2135
+ from?: string;
2136
+ to?: string;
2137
+ limit?: number;
2138
+ }): Promise<AuditEntry[]>;
2139
+ }
2140
+ /**
2141
+ * Volatile, single-process audit log. Tests + dev only. Production deployments
2142
+ * must use a durable adapter (`VercelKVAuditLog`, your Postgres/S3 impl, etc.)
2143
+ */
2144
+ declare class InMemoryAuditLog implements AuditLogAdapter {
2145
+ private readonly entries;
2146
+ append(entry: AuditEntry): Promise<void>;
2147
+ query(filter: {
2148
+ actor?: string;
2149
+ operation?: AuditOperation;
2150
+ tenantId?: string;
2151
+ from?: string;
2152
+ to?: string;
2153
+ limit?: number;
2154
+ }): Promise<AuditEntry[]>;
2155
+ /** All entries (test helper, not part of the adapter interface). */
2156
+ all(): AuditEntry[];
2157
+ reset(): void;
2158
+ }
2159
+ /**
2160
+ * Audit logger — the user-facing facade that builds + ships entries.
2161
+ *
2162
+ * @example
2163
+ * ```ts
2164
+ * const audit = new AuditLogger({
2165
+ * adapter: new InMemoryAuditLog(),
2166
+ * defaultActor: "agent:billing-bot",
2167
+ * redact: true, // default — hashes input, no raw PII
2168
+ * });
2169
+ *
2170
+ * const tools = mercadoPagoTools(client, {
2171
+ * state, backUrl, audit,
2172
+ * });
2173
+ * ```
2174
+ */
2175
+ declare class AuditLogger {
2176
+ private readonly adapter;
2177
+ private readonly defaultActor;
2178
+ private readonly redact;
2179
+ private readonly hash;
2180
+ constructor(options: {
2181
+ adapter: AuditLogAdapter;
2182
+ defaultActor?: string;
2183
+ redact?: boolean;
2184
+ /** Override hash (testing). */
2185
+ hashFn?: typeof sha256Hex;
2186
+ });
2187
+ /**
2188
+ * Wrap a tool execute() function with auto-audit. The returned function:
2189
+ * 1. Computes inputHash before the call.
2190
+ * 2. Invokes the original execute().
2191
+ * 3. On success, appends an entry with outcome="ok" + resourceId.
2192
+ * 4. On failure, appends an entry with outcome="error" + errorCode/Message.
2193
+ * 5. Re-throws the error transparently.
2194
+ */
2195
+ record<I, O>(args: {
2196
+ operation: AuditOperation;
2197
+ input: I;
2198
+ actor?: string;
2199
+ tenantId?: string;
2200
+ idempotencyKey?: string;
2201
+ /**
2202
+ * Function that extracts the resourceId from the result. Default: tries
2203
+ * `result.id`, `result.payment_id`, `result.subscription_id`, `result.order_id`.
2204
+ */
2205
+ extractResourceId?: (result: O) => string | undefined;
2206
+ /** The actual operation to execute. */
2207
+ fn: () => Promise<O>;
2208
+ }): Promise<O>;
1628
2209
  }
1629
2210
 
1630
2211
  interface MercadoPagoToolsOptions {
@@ -1664,8 +2245,26 @@ interface MercadoPagoToolsOptions {
1664
2245
  clientId: string;
1665
2246
  clientSecret: string;
1666
2247
  };
2248
+ /**
2249
+ * v0.10 — Audit logger. When passed, every state-mutating tool call
2250
+ * automatically emits an audit entry with operation/actor/inputHash/
2251
+ * resourceId/outcome/duration. Read-only tools (get/search/list) skip
2252
+ * audit logging.
2253
+ */
2254
+ audit?: AuditLogger;
2255
+ /**
2256
+ * v0.10 — Logical actor for audit entries (e.g., "agent:billing-bot",
2257
+ * "user:42"). Defaults to the AuditLogger's defaultActor.
2258
+ */
2259
+ auditActor?: string;
2260
+ /**
2261
+ * v0.10 — Webhook deduplication for handle_webhook tool. Caches
2262
+ * processed (topic, dataId, requestId) tuples to short-circuit MP's
2263
+ * retries (which fire on 5xx and can deliver the same event 5+ times).
2264
+ */
2265
+ webhookDedup?: WebhookDedup;
1667
2266
  }
1668
- type ToolName = "create_subscription" | "get_subscription_status" | "cancel_subscription" | "pause_subscription" | "resume_subscription" | "create_payment" | "get_payment" | "search_payments" | "cancel_payment" | "capture_payment" | "refund_payment" | "list_refunds" | "create_payment_preference" | "get_payment_preference" | "create_customer" | "find_customer_by_email" | "list_customer_cards" | "delete_customer_card" | "list_payment_methods" | "calculate_installments" | "get_account_info" | "charge_saved_card" | "create_qr_payment" | "cancel_qr_payment" | "create_subscription_plan" | "list_subscription_plans" | "update_subscription_plan" | "subscribe_to_plan" | "list_subscription_payments" | "create_store" | "list_stores" | "create_pos" | "list_pos" | "list_payment_disputes" | "get_dispute" | "list_identification_types" | "list_issuers" | "list_webhooks" | "create_webhook" | "update_webhook" | "delete_webhook" | "handle_webhook" | "oauth_authorize_url" | "oauth_exchange_code" | "oauth_refresh_token" | "create_order" | "get_order" | "update_order" | "capture_order" | "cancel_order" | "get_account_balance" | "list_account_movements" | "list_settlements" | "get_settlement" | "analyze_payment_3ds" | "get_test_cards" | "get_customer" | "update_customer" | "create_customer_card" | "get_customer_card" | "get_subscription_plan" | "update_subscription" | "search_subscriptions" | "get_refund" | "update_payment_preference" | "get_merchant_order" | "search_merchant_orders" | "update_merchant_order" | "get_store" | "update_store" | "delete_store" | "get_pos" | "update_pos" | "delete_pos" | "list_bank_accounts" | "register_bank_account" | "list_point_devices" | "update_point_device_mode" | "create_point_payment_intent" | "get_point_payment_intent" | "cancel_point_payment_intent" | "compute_marketplace_fee" | "explain_payment_status";
2267
+ type ToolName = "create_subscription" | "get_subscription_status" | "cancel_subscription" | "pause_subscription" | "resume_subscription" | "create_payment" | "get_payment" | "search_payments" | "cancel_payment" | "capture_payment" | "refund_payment" | "list_refunds" | "create_payment_preference" | "get_payment_preference" | "create_customer" | "find_customer_by_email" | "list_customer_cards" | "delete_customer_card" | "list_payment_methods" | "calculate_installments" | "get_account_info" | "charge_saved_card" | "create_qr_payment" | "cancel_qr_payment" | "create_subscription_plan" | "list_subscription_plans" | "update_subscription_plan" | "subscribe_to_plan" | "list_subscription_payments" | "create_store" | "list_stores" | "create_pos" | "list_pos" | "list_payment_disputes" | "get_dispute" | "list_identification_types" | "list_issuers" | "list_webhooks" | "create_webhook" | "update_webhook" | "delete_webhook" | "handle_webhook" | "oauth_authorize_url" | "oauth_exchange_code" | "oauth_refresh_token" | "create_order" | "get_order" | "update_order" | "capture_order" | "cancel_order" | "get_account_balance" | "list_account_movements" | "list_settlements" | "get_settlement" | "analyze_payment_3ds" | "get_test_cards" | "get_customer" | "update_customer" | "create_customer_card" | "get_customer_card" | "get_subscription_plan" | "update_subscription" | "search_subscriptions" | "get_refund" | "update_payment_preference" | "get_merchant_order" | "search_merchant_orders" | "update_merchant_order" | "get_store" | "update_store" | "delete_store" | "get_pos" | "update_pos" | "delete_pos" | "list_bank_accounts" | "register_bank_account" | "list_point_devices" | "update_point_device_mode" | "create_point_payment_intent" | "get_point_payment_intent" | "cancel_point_payment_intent" | "compute_marketplace_fee" | "explain_payment_status" | "mp_health_check" | "find_applicable_promos" | "confirm_3ds_challenge" | "search_payments_all" | "list_settlements_all";
1669
2268
  /**
1670
2269
  * Build a tool set for the Vercel AI SDK that exposes Mercado Pago to an
1671
2270
  * agent. Pass directly to `Experimental_Agent`'s `tools` option, or merge with
@@ -1992,6 +2591,366 @@ declare function buildTestCardScenario(cardKey: keyof typeof TEST_CARDS_AR, scen
1992
2591
  * Analyze a Payment's 3DS state. Pure function, no I/O.
1993
2592
  */
1994
2593
  declare function analyze3DS(payment: Payment): ThreeDSInfo;
2594
+ /**
2595
+ * Submit the 3DS challenge result back to MP after the buyer completes the
2596
+ * issuer challenge. Used as the FINAL step in the 3DS challenge flow:
2597
+ *
2598
+ * 1. `createPayment` returns `pending` + `pending_challenge` status_detail
2599
+ * 2. `analyze3DS(payment)` extracts the `challengeUrl`
2600
+ * 3. Buyer is redirected to `challengeUrl` and completes the challenge
2601
+ * 4. The issuer redirects to your `back_url` with a `challenge_complete=true`
2602
+ * (or similar query — depends on issuer / browser flow)
2603
+ * 5. **You call this method** to confirm the challenge and finalize the payment
2604
+ *
2605
+ * # Why this is separate
2606
+ *
2607
+ * Step 5 isn't documented as a SINGLE endpoint in MP's public docs — different
2608
+ * 3DS providers (Mastercard, Visa, Cabal) handle the challenge resolution
2609
+ * differently. This method tries the documented path: re-fetching the payment
2610
+ * via `getPayment` after the challenge — MP updates the status server-side
2611
+ * once the issuer reports the challenge result via their backchannel.
2612
+ *
2613
+ * # When to call
2614
+ *
2615
+ * - **Before** showing the user a final "approved/rejected" screen
2616
+ * - **After** the buyer is redirected back from the challenge URL
2617
+ * - **With backoff**: MP sometimes lags by a few seconds — recommended to
2618
+ * poll `getPayment` 3-5 times with 1s spacing if the first call still
2619
+ * returns `pending_challenge`.
2620
+ */
2621
+ declare function confirmChallengeAndPoll(client: MercadoPagoClient, paymentId: string, options?: {
2622
+ /** Maximum number of polls. Default 5. */
2623
+ maxAttempts?: number;
2624
+ /** Sleep between polls in ms. Default 1000ms. */
2625
+ pollIntervalMs?: number;
2626
+ /** Optional AbortSignal to cap the total wait. */
2627
+ signal?: AbortSignal;
2628
+ }): Promise<{
2629
+ payment: Payment;
2630
+ threeDs: ThreeDSInfo;
2631
+ resolved: boolean;
2632
+ attempts: number;
2633
+ }>;
2634
+
2635
+ /**
2636
+ * Pagination helpers — automatic pagination over MP's paginated endpoints
2637
+ * via AsyncIterable. Replaces the manual offset/limit loop.
2638
+ *
2639
+ * # Why
2640
+ *
2641
+ * MP's paginated endpoints (search_payments, search_subscriptions,
2642
+ * list_account_movements, list_settlements, search_merchant_orders, etc.)
2643
+ * cap responses at 100 items per page. Iterating "all matching X" without
2644
+ * helpers means writing the offset+limit loop in every caller — annoying
2645
+ * + error-prone (off-by-one bugs are common).
2646
+ *
2647
+ * # Usage
2648
+ *
2649
+ * ```ts
2650
+ * import { paginate } from "@ar-agents/mercadopago";
2651
+ *
2652
+ * for await (const payment of paginate(
2653
+ * (offset) => client.searchPayments({ offset, limit: 100, status: "approved" }),
2654
+ * { extractItems: (page) => page.results ?? [], extractTotal: (page) => page.paging?.total ?? 0 },
2655
+ * )) {
2656
+ * console.log(payment.id);
2657
+ * }
2658
+ * ```
2659
+ *
2660
+ * Or use the convenience wrappers below:
2661
+ *
2662
+ * ```ts
2663
+ * for await (const payment of paginatePayments(client, { status: "approved" })) {
2664
+ * console.log(payment.id);
2665
+ * }
2666
+ *
2667
+ * // Materialize all (only when sure it fits in memory):
2668
+ * const allPayments = await collect(paginatePayments(client, { status: "approved" }));
2669
+ * ```
2670
+ *
2671
+ * # Performance
2672
+ *
2673
+ * - **Streaming**: items are yielded as each page arrives — your downstream
2674
+ * work can start before all pages are fetched.
2675
+ * - **Bounded concurrency**: by default fetches one page at a time. Pass
2676
+ * `concurrency: 4` to prefetch up to 4 pages ahead (faster, more bandwidth).
2677
+ * - **Total cap**: pass `maxItems: 1000` to bail out early.
2678
+ *
2679
+ * # Edge cases handled
2680
+ *
2681
+ * - Empty pages (returns no items, terminates).
2682
+ * - Pages where total < expected (terminates correctly).
2683
+ * - Mid-iteration cancellation (caller breaks the for-await — no further fetches).
2684
+ * - Paging.total === 0 (terminates immediately).
2685
+ */
2686
+
2687
+ interface PaginateOptions<TPage, TItem> {
2688
+ /** Extract the items array from a page. */
2689
+ extractItems: (page: TPage) => TItem[];
2690
+ /**
2691
+ * Extract the total count from a page. Used to know when to stop.
2692
+ * If not available, the iterator terminates when an empty page arrives.
2693
+ */
2694
+ extractTotal?: (page: TPage) => number | undefined;
2695
+ /** Page size. Default 100 (MP's max for most endpoints). */
2696
+ pageSize?: number;
2697
+ /**
2698
+ * Stop after yielding `maxItems` total. Useful for "first N matching"
2699
+ * queries that would otherwise iterate the full result set.
2700
+ */
2701
+ maxItems?: number;
2702
+ /**
2703
+ * Number of pages to prefetch concurrently. Default 1 (no prefetch).
2704
+ * Higher = lower wall-clock time but more concurrent MP requests.
2705
+ */
2706
+ concurrency?: number;
2707
+ }
2708
+ /**
2709
+ * Generic paginator. Most callers use the typed convenience wrappers below.
2710
+ *
2711
+ * @param fetchPage Function that fetches page N given the offset.
2712
+ */
2713
+ declare function paginate<TPage, TItem>(fetchPage: (offset: number, limit: number) => Promise<TPage>, opts: PaginateOptions<TPage, TItem>): AsyncGenerator<TItem, void, undefined>;
2714
+ /** Materialize an AsyncIterable into an array. Caller's responsibility to ensure it fits. */
2715
+ declare function collect<T>(iter: AsyncIterable<T>): Promise<T[]>;
2716
+ declare function paginatePayments(client: MercadoPagoClient, filter?: Parameters<MercadoPagoClient["searchPayments"]>[0], opts?: {
2717
+ pageSize?: number;
2718
+ maxItems?: number;
2719
+ concurrency?: number;
2720
+ }): AsyncGenerator<Payment, void, undefined>;
2721
+ declare function paginateSubscriptions(client: MercadoPagoClient, filter?: Parameters<MercadoPagoClient["searchPreapprovals"]>[0], opts?: {
2722
+ pageSize?: number;
2723
+ maxItems?: number;
2724
+ concurrency?: number;
2725
+ }): AsyncGenerator<Preapproval, void, undefined>;
2726
+ declare function paginateAccountMovements(client: MercadoPagoClient, filter?: {
2727
+ from?: string;
2728
+ to?: string;
2729
+ }, opts?: {
2730
+ pageSize?: number;
2731
+ maxItems?: number;
2732
+ concurrency?: number;
2733
+ }): AsyncGenerator<AccountMovement, void, undefined>;
2734
+ declare function paginateSettlements(client: MercadoPagoClient, filter?: {
2735
+ from?: string;
2736
+ to?: string;
2737
+ status?: string;
2738
+ }, opts?: {
2739
+ pageSize?: number;
2740
+ maxItems?: number;
2741
+ concurrency?: number;
2742
+ }): AsyncGenerator<Settlement, void, undefined>;
2743
+ declare function paginateMerchantOrders(client: MercadoPagoClient, filter?: Parameters<MercadoPagoClient["searchMerchantOrders"]>[0], opts?: {
2744
+ pageSize?: number;
2745
+ maxItems?: number;
2746
+ concurrency?: number;
2747
+ }): AsyncGenerator<MerchantOrder, void, undefined>;
2748
+ declare function paginateSubscriptionPlans(client: MercadoPagoClient, filter?: {
2749
+ status?: string;
2750
+ }, opts?: {
2751
+ pageSize?: number;
2752
+ maxItems?: number;
2753
+ concurrency?: number;
2754
+ }): AsyncGenerator<SubscriptionPlan, void, undefined>;
2755
+ declare function paginateSubscriptionPayments(client: MercadoPagoClient, preapprovalId: string, opts?: {
2756
+ pageSize?: number;
2757
+ maxItems?: number;
2758
+ concurrency?: number;
2759
+ }): AsyncGenerator<SubscriptionPayment, void, undefined>;
2760
+
2761
+ /**
2762
+ * Token bucket rate limiter — proactive client-side rate limiting.
2763
+ *
2764
+ * # Why proactive
2765
+ *
2766
+ * The current client honors `Retry-After` after a 429 (reactive). That's
2767
+ * good but suboptimal: every 429 still costs you a network round-trip, an
2768
+ * error log, and adds latency to the retry. Proactive rate limiting reads
2769
+ * MP's `x-rate-limit-remaining` header and slows down BEFORE the next 429.
2770
+ *
2771
+ * # The token bucket model
2772
+ *
2773
+ * - The bucket holds N tokens (the burst capacity).
2774
+ * - Tokens refill at R tokens/second (the steady-state rate).
2775
+ * - Each request consumes 1 token.
2776
+ * - When the bucket is empty, requests wait until a token is available.
2777
+ *
2778
+ * Example: capacity=20, refill=10/s means "burst 20 requests, then 10/s
2779
+ * sustained". Matches MP's typical limits (precise numbers vary by endpoint
2780
+ * and aren't publicly documented).
2781
+ *
2782
+ * # Per-host vs global
2783
+ *
2784
+ * Default: one bucket per `MercadoPagoClient`. Pass a SHARED bucket to
2785
+ * multiple clients in marketplace setups so they share the rate limit
2786
+ * (otherwise each per-seller client would think it has its own quota).
2787
+ *
2788
+ * # Adaptive learning
2789
+ *
2790
+ * The bucket auto-tunes from response headers: if MP says
2791
+ * `x-rate-limit-remaining: 5` and the bucket has 50 tokens, the bucket
2792
+ * is over-spending. The `learnFromHeaders` method updates the available
2793
+ * count to the lower of (current, MP's stated remaining).
2794
+ */
2795
+ interface RateLimiterOptions {
2796
+ /** Bucket capacity (max burst). Default 50. */
2797
+ capacity?: number;
2798
+ /** Refill rate in tokens per second. Default 25. */
2799
+ refillPerSecond?: number;
2800
+ /**
2801
+ * If true, the limiter calls `learnFromHeaders` after every successful
2802
+ * request to keep its bucket in sync with MP's actual quota. Default true.
2803
+ */
2804
+ adaptive?: boolean;
2805
+ /**
2806
+ * Hard cap on how long `acquire()` will wait. If the bucket can't refill
2807
+ * in this time, `acquire()` rejects with `RateLimitTimeoutError`.
2808
+ * Default 30s — anything longer is probably better handled as an error.
2809
+ */
2810
+ acquireTimeoutMs?: number;
2811
+ /** Time provider (testing). Defaults to Date.now. */
2812
+ now?: () => number;
2813
+ }
2814
+ declare class RateLimitTimeoutError extends Error {
2815
+ readonly waitedMs: number;
2816
+ constructor(waitedMs: number);
2817
+ }
2818
+ declare class TokenBucketRateLimiter {
2819
+ private tokens;
2820
+ private lastRefill;
2821
+ private readonly capacity;
2822
+ private readonly refillPerSecond;
2823
+ private readonly adaptive;
2824
+ private readonly acquireTimeoutMs;
2825
+ private readonly now;
2826
+ constructor(opts?: RateLimiterOptions);
2827
+ /**
2828
+ * Acquire a token. Resolves immediately if tokens are available;
2829
+ * otherwise waits until one is. Rejects with `RateLimitTimeoutError`
2830
+ * if the wait exceeds `acquireTimeoutMs`.
2831
+ */
2832
+ acquire(): Promise<void>;
2833
+ /**
2834
+ * Best-effort acquire: returns true if a token was available, false
2835
+ * otherwise. Doesn't wait. Useful for "non-blocking" code paths that
2836
+ * want to fall back to a cached response or queue the request elsewhere.
2837
+ */
2838
+ tryAcquire(): boolean;
2839
+ /**
2840
+ * Adaptive learning hook — call after every API response with MP's
2841
+ * rate-limit headers to keep the bucket in sync with reality.
2842
+ */
2843
+ learnFromHeaders(headers: {
2844
+ remaining: number | null;
2845
+ resetSeconds: number | null;
2846
+ }): void;
2847
+ /** Inspect the current bucket state. */
2848
+ getStats(): {
2849
+ tokens: number;
2850
+ capacity: number;
2851
+ refillPerSecond: number;
2852
+ };
2853
+ private refill;
2854
+ }
2855
+
2856
+ /**
2857
+ * Argentine issuer cuotas promotional catalog — embedded knowledge of which
2858
+ * banks/cards have "cuotas sin interés" (interest-free installment) deals
2859
+ * with which sellers, on which days.
2860
+ *
2861
+ * # Why embed this
2862
+ *
2863
+ * MP's `calculate_installments` API returns the CURRENT cuotas options for
2864
+ * a given (payment_method, amount, bin) tuple — but it doesn't tell the
2865
+ * agent which deals are GENERALLY available (e.g., "Naranja con Galicia, 6
2866
+ * cuotas sin interés todos los martes"). Devs surface that information to
2867
+ * buyers BEFORE checkout to drive conversion.
2868
+ *
2869
+ * This catalog is the AR-specific knowledge that turns the toolkit from
2870
+ * "MP API wrapper" into "MP integration with retail context".
2871
+ *
2872
+ * # Sources
2873
+ *
2874
+ * - Each issuer's published "Cuotas Simples" / "Ahora 12" page
2875
+ * - BCRA Comunicación A 7825 (financiamiento al consumo)
2876
+ * - Manually verified against Naranja, Galicia, Santander, Macro, BBVA, ICBC
2877
+ * Patagonia, Banco Nación, Banco Provincia, Banco Ciudad, Comafi, HSBC
2878
+ * public landing pages
2879
+ *
2880
+ * # Maintenance
2881
+ *
2882
+ * The promos schedule changes seasonally. Update this file quarterly + when
2883
+ * BCRA publishes a new "Ahora N" program. PRs welcomed.
2884
+ *
2885
+ * Last sync: 2026-Q2.
2886
+ */
2887
+ interface CuotasPromo {
2888
+ /** Issuer name (matches `list_issuers` response). */
2889
+ issuer: string;
2890
+ /** Card brand (visa, master, amex, naranja, cabal, etc.). */
2891
+ paymentMethodId: string;
2892
+ /** Number of interest-free installments. */
2893
+ installments: number;
2894
+ /** Days of the week the promo applies. Empty = always. */
2895
+ daysOfWeek?: Array<"mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun">;
2896
+ /** ISO date when the promo starts (inclusive). */
2897
+ startDate?: string;
2898
+ /** ISO date when the promo expires (inclusive). */
2899
+ endDate?: string;
2900
+ /** Minimum purchase amount in ARS for the promo to apply. */
2901
+ minAmountArs?: number;
2902
+ /** Maximum monthly cap on the promo per cardholder. Optional. */
2903
+ maxAmountArs?: number;
2904
+ /** Free-form description shown to the buyer. ALWAYS surface verbatim. */
2905
+ description: string;
2906
+ /** Categories where the promo applies (per BCRA codes). Empty = any. */
2907
+ categories?: Array<"electronics" | "appliances" | "clothing" | "supermarket" | "travel" | "education" | "health" | "general">;
2908
+ }
2909
+ /**
2910
+ * The "Ahora 12 / 18 / 24 / 30" national program — recurring federal scheme
2911
+ * that subsidizes interest-free installments on essential categories.
2912
+ *
2913
+ * As of 2026-Q2: 3, 6, 12, 18, 24, 30 installment options on appliances,
2914
+ * electronics, clothing, books, school supplies, tires, eyewear, motorcycles,
2915
+ * national-tourism services. Not all categories qualify for all tiers.
2916
+ */
2917
+ declare const AHORA_PROGRAM_PROMOS: CuotasPromo[];
2918
+ /**
2919
+ * Issuer-specific promos (running in addition to the Ahora program).
2920
+ *
2921
+ * Note: these change frequently. Check `lastVerified` before relying.
2922
+ */
2923
+ declare const AR_ISSUER_PROMOS: CuotasPromo[];
2924
+ /**
2925
+ * Find applicable promos for a given context.
2926
+ *
2927
+ * Pure function — no I/O. Use to surface "cuotas sin interés" hints to the
2928
+ * buyer BEFORE they call `calculate_installments` (the API only returns
2929
+ * what's offered for the EXACT card, which the buyer hasn't entered yet).
2930
+ *
2931
+ * @example
2932
+ * ```ts
2933
+ * import { findApplicablePromos } from "@ar-agents/mercadopago";
2934
+ *
2935
+ * const promos = findApplicablePromos({
2936
+ * issuer: "Banco Galicia",
2937
+ * paymentMethodId: "visa",
2938
+ * amountArs: 50_000,
2939
+ * category: "supermarket",
2940
+ * date: new Date(), // optional, defaults to now
2941
+ * });
2942
+ * // → [{ installments: 12, description: "Galicia ... 12 cuotas sin interés ...", ... }]
2943
+ * ```
2944
+ */
2945
+ declare function findApplicablePromos(args: {
2946
+ issuer?: string;
2947
+ paymentMethodId?: string;
2948
+ amountArs?: number;
2949
+ category?: NonNullable<CuotasPromo["categories"]>[number];
2950
+ date?: Date;
2951
+ /** Include the Ahora program in addition to issuer-specific. Default true. */
2952
+ includeAhoraProgram?: boolean;
2953
+ }): CuotasPromo[];
1995
2954
 
1996
2955
  /**
1997
2956
  * Pure helpers — no I/O, deterministic, fast. Importable directly from the
@@ -2058,4 +3017,4 @@ interface PaymentStatusExplanation {
2058
3017
  */
2059
3018
  declare function explainPaymentStatus(payment: Payment): PaymentStatusExplanation;
2060
3019
 
2061
- export { type AccountBalance, type AccountInfo, type AccountMovement, type AutoRecurring, type BankAccount, type CardToken, type CreateCardTokenParams, type CreateCustomerParams, type CreateOrderParams, type CreatePaymentParams, type CreatePointPaymentIntentParams, type CreatePosParams, type CreatePreapprovalParams, type CreatePreferenceParams, type CreateQrPaymentParams, type CreateRefundParams, type CreateStoreParams, type CreateSubscriptionPlanParams, type CreateWebhookParams, type CurrencyId, type Customer, type CustomerCard, type Dispute, type FrequencyType, type IdentificationType, type InstallmentOffer, type Issuer, type MarketplaceFeeRule, type MarketplaceParams, MercadoPagoAccountTypeMismatchError, MercadoPagoAuthError, MercadoPagoAuthorizeForbiddenError, MercadoPagoBackUrlInvalidError, MercadoPagoClient, type MercadoPagoClientOptions, MercadoPagoError, MercadoPagoOverloadedError, MercadoPagoPaymentRejectedError, MercadoPagoRateLimitError, MercadoPagoSelfPaymentError, MercadoPagoTimeoutError, type MercadoPagoToolsOptions, type MerchantOrder, type OAuthToken, type Order, type OrderItem, type OrderStatus, type ParsedWebhookEvent, type Payment, type PaymentMethod, type PaymentStatus, type PaymentStatusExplanation, type PaymentsSearchResult, type PointDevice, type PointPaymentIntent, type PointPaymentIntentState, type Pos, type Preapproval, type PreapprovalStatus, type Preference, type PreferenceItem, type QrOrder, type Refund, type SearchPaymentsParams, type Settlement, type SiteId, type Store, type SubscriptionPayment, type SubscriptionPlan, SubscriptionStateAdapter, TEST_CARDS_AR, TEST_PAYERS_AR, type TestCard, type ThreeDSInfo, type ThreeDSStatus, type WebhookBody, type WebhookConfig, type WebhookTopic, analyze3DS, buildAuthorizeUrl, buildTestCardScenario, classifyError, computeMarketplaceFee, exchangeCodeForToken, expirationTimeMs, explainPaymentStatus, isExpiringSoon, mercadoPagoTools, parseWebhookEvent, refreshAccessToken, verifyWebhookSignature };
3020
+ export { AHORA_PROGRAM_PROMOS, AR_ISSUER_PROMOS, type AccountBalance, type AccountInfo, type AccountMovement, type AuditEntry, type AuditLogAdapter, AuditLogger, type AuditOperation, type AutoRecurring, type BankAccount, type CardToken, CircuitBreaker, type CircuitBreakerOptions, CircuitOpenError, type CircuitState, type CreateCardTokenParams, type CreateCustomerParams, type CreateOrderParams, type CreatePaymentParams, type CreatePointPaymentIntentParams, type CreatePosParams, type CreatePreapprovalParams, type CreatePreferenceParams, type CreateQrPaymentParams, type CreateRefundParams, type CreateStoreParams, type CreateSubscriptionPlanParams, type CreateWebhookParams, type CuotasPromo, type CurrencyId, type Customer, type CustomerCard, type DedupResult, type Dispute, type FrequencyType, IdempotencyCache, type IdentificationType, InMemoryAuditLog, type InstallmentOffer, type Issuer, type MarketplaceFeeRule, type MarketplaceParams, MercadoPagoAccountTypeMismatchError, MercadoPagoAuthError, MercadoPagoAuthorizeForbiddenError, MercadoPagoBackUrlInvalidError, MercadoPagoClient, type MercadoPagoClientOptions, MercadoPagoError, MercadoPagoOverloadedError, MercadoPagoPaymentRejectedError, MercadoPagoRateLimitError, MercadoPagoSelfPaymentError, MercadoPagoTimeoutError, type MercadoPagoToolsOptions, type MerchantOrder, type OAuthToken, type Order, type OrderItem, type OrderStatus, type PaginateOptions, type ParsedWebhookEvent, type Payment, type PaymentMethod, type PaymentStatus, type PaymentStatusExplanation, type PaymentsSearchResult, type PointDevice, type PointPaymentIntent, type PointPaymentIntentState, type Pos, type Preapproval, type PreapprovalStatus, type Preference, type PreferenceItem, type QrOrder, RateLimitTimeoutError, type RateLimiterOptions, type Refund, type SearchPaymentsParams, type Settlement, type SiteId, type Store, type SubscriptionPayment, type SubscriptionPlan, SubscriptionStateAdapter, TEST_CARDS_AR, TEST_PAYERS_AR, type TestCard, type ThreeDSInfo, type ThreeDSStatus, TokenBucketRateLimiter, type WebhookBody, type WebhookConfig, WebhookDedup, type WebhookDedupOptions, type WebhookTopic, analyze3DS, buildAuthorizeUrl, buildTestCardScenario, classifyError, collect, computeMarketplaceFee, confirmChallengeAndPoll, exchangeCodeForToken, expirationTimeMs, explainPaymentStatus, findApplicablePromos, isExpiringSoon, mercadoPagoTools, paginate, paginateAccountMovements, paginateMerchantOrders, paginatePayments, paginateSettlements, paginateSubscriptionPayments, paginateSubscriptionPlans, paginateSubscriptions, parseWebhookEvent, refreshAccessToken, verifyWebhookSignature };