@ar-agents/mercadopago 0.10.0 → 0.12.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 CHANGED
@@ -1,5 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes — Idempotency-by-default for state-mutating writes
6
+
7
+ `MercadoPagoClient` now auto-generates a UUID v4 X-Idempotency-Key header on
8
+ every state-mutating POST request when the caller doesn't provide one
9
+ explicitly. Naive callers (and the LLM tools layer) often forget to pass an
10
+ idempotency key, leaving them exposed to double-charge bugs on network
11
+ partitions. This makes the safe default: safe.
12
+
13
+ - **Auto-generated keys are unique per call** (Web Crypto's `randomUUID()` —
14
+ Edge Runtime + Node 19+ + Cloudflare Workers + browsers).
15
+ - **Caller-supplied keys still win** — pass `idempotencyKey: "..."` for
16
+ deterministic retries from a job queue (e.g., same key across retry
17
+ attempts).
18
+ - **Only POST requests are auto-keyed.** GET / DELETE are HTTP-idempotent
19
+ by spec. PUT skips auto-gen because MP's PUT endpoints encode the dedup
20
+ key in the resource path (`/v1/payments/:id` → cancel; `/preapproval/:id` →
21
+ pause/resume — already deduped by id).
22
+
23
+ 6 new tests in `idempotency-default.test.ts` verify:
24
+ - UUID v4 format on auto-gen
25
+ - Different keys per call
26
+ - Caller-supplied keys honored over auto-gen
27
+ - GET requests NOT keyed
28
+ - Works for `createPayment`, `createPreference`, `createPreapproval`
29
+
30
+ ### New cookbook recipe
31
+
32
+ - `cookbook/09-otel-wired.ts` — full OpenTelemetry wiring example. Shows
33
+ how to wire `traceContext` for distributed-trace correlation, instrument
34
+ the client + tools, and what the resulting trace + metric shape looks
35
+ like in your APM. Closes the half-finished OTel story (lib + subpath
36
+ existed since v0.10 but no recipe wired it end-to-end).
37
+
38
+ ## 0.11.0
39
+
40
+ ### Minor Changes — Composability + cross-LATAM + fraud scoring
41
+
42
+ **Tool middleware pattern (NEW)**: composable wrappers around any AI SDK tool. Ships `withAuditLog`, `withRateLimit`, `withMetrics`, `withRetry` + `compose()` + `applyToAllTools()`. Add audit + rate-limit + metrics to every tool with a single config block instead of wiring each concern into the tool implementation.
43
+
44
+ **TaxID validation cross-LATAM (NEW)**: `validateTaxId(input, type)` for AR (DNI/CUIT/CUIL with modulo-11), BR (CPF/CNPJ two-step weighted), MX (RFC structure), CL (RUT with K), CO (NIT modulo-11), UY (RUT 12-digit), PE (RUC 11-digit + prefix). `detectAndValidate(input, country)` auto-detects type from length. New `validate_tax_id` agent tool. Pure, no network.
45
+
46
+ **`additional_info` on `create_payment` (fraud scoring enrichment)**: `payer` profile (registration_date, authentication_type, is_first_purchase, is_prime_user, last_purchase), `shipments` (receiver_address, express_shipment, local_pickup), `ip_address`, `referral_url`. Per MP RG 5286/2023, payments without enrichment have 3-5x higher rejection rate — this is a real conversion uplift. Documented in tool description so the agent surfaces it proactively.
47
+
48
+ **VercelKVAuditLog (NEW)**: production audit-log adapter in `@ar-agents/mercadopago/vercel-kv` subpath. Storage layout: per-entry key + ZSET indexes by day/actor/tenant for O(log N) time-range queries. Backed by Vercel KV (Upstash Redis).
49
+
50
+ **Migration guide vs `mercadopago` (official SDK)** — `MIGRATION.md` shipped in tarball with side-by-side mappings, conceptual table, "when to use both" section.
51
+
52
+ **CI fixes**: build packages BEFORE typecheck (was failing for facturacion → identity workspace dep with subpath exports). Release workflow now `workflow_dispatch` only (was spamming failure emails on every push because changesets/action couldn't create PRs without explicit repo permission).
53
+
54
+ **New tools**: `validate_tax_id`. Tool count: **87** (was 86).
55
+
56
+ **Quality**: 284 tests pass (was 245; +39 v0.11 tests covering middleware, taxId, and more). publint clean, attw 🟢 across 3 subpaths. Bundle: main 42.9 KB brotli'd (size budget bumped 40→50 KB).
57
+
3
58
  ## 0.10.0
4
59
 
5
60
  ### Minor Changes — Compliance + DX + observability deepening
package/MIGRATION.md ADDED
@@ -0,0 +1,176 @@
1
+ # Migration guide — `mercadopago` (official SDK) → `@ar-agents/mercadopago`
2
+
3
+ If you're already using MP's official Node SDK and want to switch to (or
4
+ add on top of) the agent toolkit, this guide shows the side-by-side mapping.
5
+
6
+ The agent toolkit is **not** a drop-in replacement — it's a layer ABOVE
7
+ the underlying API designed for AI agents. You can use both packages in
8
+ the same project: keep `mercadopago` for traditional server flows, add
9
+ `@ar-agents/mercadopago` for the agent layer.
10
+
11
+ ## Conceptual mapping
12
+
13
+ | `mercadopago` (official) | `@ar-agents/mercadopago` |
14
+ |---|---|
15
+ | `MercadoPagoConfig({ accessToken })` | `new MercadoPagoClient({ accessToken })` |
16
+ | `new Payment(config)` | Same `MercadoPagoClient` (single object, no per-resource client) |
17
+ | `payment.create({ body })` | `client.createPayment(params)` (camelCase params) |
18
+ | `payment.get({ id })` | `client.getPayment(id)` |
19
+ | `payment.search({ options })` | `client.searchPayments(filter)` |
20
+ | `preApproval.create({ body })` | `client.createPreapproval(params)` |
21
+ | `customer.search({ options: { criteria: 'desc', email } })` | `client.searchCustomers({ email })` |
22
+ | Manual webhook signature check | `verifyWebhookSignature({ ... })` (HMAC + replay protection) |
23
+ | Manual idempotency key generation | Auto-generated by tool layer (deterministic SHA-256) |
24
+ | Manual retry logic | Built-in retry budget + circuit breaker |
25
+ | Manual installments display | `findApplicablePromos({ issuer, ... })` |
26
+
27
+ ## Side-by-side: create a payment
28
+
29
+ **Before (`mercadopago`):**
30
+
31
+ ```ts
32
+ import { MercadoPagoConfig, Payment } from "mercadopago";
33
+
34
+ const config = new MercadoPagoConfig({ accessToken: process.env.MP_ACCESS_TOKEN! });
35
+ const payment = new Payment(config);
36
+
37
+ const created = await payment.create({
38
+ body: {
39
+ transaction_amount: 100,
40
+ payment_method_id: "visa",
41
+ payer: { email: "buyer@test.com" },
42
+ token: cardToken,
43
+ description: "Test",
44
+ external_reference: "order-123",
45
+ },
46
+ });
47
+ console.log(created.id, created.status);
48
+ ```
49
+
50
+ **After (`@ar-agents/mercadopago` — direct client):**
51
+
52
+ ```ts
53
+ import { MercadoPagoClient } from "@ar-agents/mercadopago";
54
+
55
+ const client = new MercadoPagoClient({ accessToken: process.env.MP_ACCESS_TOKEN! });
56
+
57
+ const created = await client.createPayment({
58
+ transactionAmount: 100, // camelCase
59
+ paymentMethodId: "visa",
60
+ payerEmail: "buyer@test.com", // flat (not nested under `payer`)
61
+ token: cardToken,
62
+ description: "Test",
63
+ externalReference: "order-123",
64
+ });
65
+ console.log(created.id, created.status);
66
+ ```
67
+
68
+ **After (`@ar-agents/mercadopago` — agent tool):**
69
+
70
+ ```ts
71
+ import { MercadoPagoClient, mercadoPagoTools, InMemoryStateAdapter } from "@ar-agents/mercadopago";
72
+
73
+ const client = new MercadoPagoClient({ accessToken: process.env.MP_ACCESS_TOKEN! });
74
+ const tools = mercadoPagoTools(client, {
75
+ state: new InMemoryStateAdapter(),
76
+ backUrl: "https://yourapp.com/done",
77
+ });
78
+
79
+ // Tool runs from inside an Agent.generate() call:
80
+ // "Cobrale 100 pesos a buyer@test.com"
81
+ ```
82
+
83
+ ## Side-by-side: webhook signature verification
84
+
85
+ **Before (`mercadopago`)**: not provided — you implement HMAC-SHA256 yourself
86
+ from the docs.
87
+
88
+ **After (`@ar-agents/mercadopago`):**
89
+
90
+ ```ts
91
+ import { verifyWebhookSignature, parseWebhookEvent } from "@ar-agents/mercadopago";
92
+
93
+ export async function POST(req: Request) {
94
+ const rawBody = await req.text();
95
+ const event = parseWebhookEvent(JSON.parse(rawBody));
96
+ const verified = await verifyWebhookSignature({
97
+ requestId: req.headers.get("x-request-id"),
98
+ dataId: event!.dataId,
99
+ signatureHeader: req.headers.get("x-signature"),
100
+ secret: process.env.MP_WEBHOOK_SECRET!,
101
+ // 5-min replay protection by default
102
+ });
103
+ if (!verified) return new Response("unauthorized", { status: 401 });
104
+ // ...
105
+ }
106
+ ```
107
+
108
+ ## Side-by-side: idempotency
109
+
110
+ **Before (`mercadopago`)**: pass `requestOptions: { idempotencyKey: "..." }`
111
+ manually on each call. You compute the key yourself.
112
+
113
+ **After (`@ar-agents/mercadopago`)**: tools auto-derive a deterministic
114
+ idempotency key from the meaningful inputs (SHA-256 of `external_reference`,
115
+ amount, payment_method, etc.). Same input → same key → MP dedupes safely
116
+ across retries. No manual work.
117
+
118
+ ## Side-by-side: retries + timeouts
119
+
120
+ **Before**: implement your own retry loop + AbortController.
121
+
122
+ **After**: built into `MercadoPagoClient`:
123
+
124
+ ```ts
125
+ new MercadoPagoClient({
126
+ accessToken: "...",
127
+ requestTimeoutMs: 30_000, // default 30s
128
+ maxRetries: 1, // default 1, retries 5xx + 429
129
+ circuitBreaker: new CircuitBreaker({ failureThreshold: 5 }),
130
+ // Honors Retry-After on 429 automatically
131
+ });
132
+ ```
133
+
134
+ ## What this toolkit adds that the official SDK doesn't have
135
+
136
+ - **Agent tool layer** for Vercel AI SDK 6+ (`mercadoPagoTools()`)
137
+ - **Webhook HMAC + replay protection** (`verifyWebhookSignature` async)
138
+ - **Circuit breaker** with state machine
139
+ - **Deadline propagation** via parent `AbortSignal`
140
+ - **W3C Trace Context** propagation (OpenTelemetry compat sin peer dep)
141
+ - **Audit logging** with pluggable adapter (`AuditLogger` + `InMemoryAuditLog`)
142
+ - **Webhook idempotency dedup** (`WebhookDedup` — short-circuits MP retries)
143
+ - **Pagination helpers** (`paginate()` AsyncIterable for 7 endpoints)
144
+ - **Token bucket rate limiter** with adaptive learning from MP headers
145
+ - **AR issuer cuotas catalog** (`AR_ISSUER_PROMOS`, `findApplicablePromos`)
146
+ - **OpenTelemetry instrumentation subpath** (`@ar-agents/mercadopago/otel`)
147
+ - **Tool middleware pattern** (`withAuditLog`, `withRateLimit`, `withMetrics`, `withRetry`)
148
+ - **3DS challenge resolution** (`confirmChallengeAndPoll`)
149
+ - **TaxID validation cross-LATAM** (DNI/CUIT/CPF/CNPJ/RFC/RUT/NIT/RUC)
150
+ - **Status detail explainer** (`explainPaymentStatus` — Spanish actionable guidance)
151
+ - **Marketplace fee calculator** (`computeMarketplaceFee`)
152
+ - **Vercel KV state adapters** (subscription state + OAuth tokens + idempotency cache + audit log)
153
+ - **Cookbook** with 8 production-grade recipes
154
+ - **Edge Runtime support** (Web Crypto, no `node:crypto`)
155
+ - **Property-based testing** with fast-check (~1500 random scenarios)
156
+ - **Failure injection tests** + integration tests vs MP sandbox
157
+ - **Benchmarks** (`pnpm bench`)
158
+
159
+ ## When to keep using `mercadopago` (official) instead
160
+
161
+ - Your codebase is already deeply integrated with the official SDK
162
+ - You don't need the agent layer (no Vercel AI SDK)
163
+ - You operate primarily server-side with cron jobs and don't care about
164
+ agent ergonomics, audit logs, circuit breakers, or status explainers
165
+
166
+ ## When to add `@ar-agents/mercadopago`
167
+
168
+ - You're building anything with an AI agent (Claude, GPT, Gemini)
169
+ - You're deploying to Vercel and want first-class KV adapters
170
+ - You need **production-grade webhook handling** (HMAC + dedup + replay protection)
171
+ - You operate a **marketplace** with per-seller OAuth flows
172
+ - You need **compliance-grade audit logging** for refunds/payments
173
+ - You want **AR-specific knowledge** (cuotas catalog, status_detail explainer in Spanish, AR landmines documented)
174
+ - You want **OpenTelemetry-native** observability without writing instrumentation glue
175
+
176
+ You can use BOTH packages in the same project — they don't conflict.
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Recipe 09 — OpenTelemetry wired end-to-end.
3
+ *
4
+ * # Pattern
5
+ *
6
+ * 1. Wire an OTel SDK at app boot (NodeSDK or Edge equivalent)
7
+ * 2. Build the MP client with `traceContext` so each MP request injects a
8
+ * W3C `traceparent` header (correlates with your distributed traces)
9
+ * 3. Wrap the client / tools with the OTel instrumentation from
10
+ * `@ar-agents/mercadopago/otel` to get spans + metrics for every call:
11
+ * - `mp.request` span per MP API call (with attrs for endpoint, method,
12
+ * status, duration, retry count)
13
+ * - `mp.tool` span per agent-invoked tool (with input + output attrs)
14
+ * - Metrics: latency p50/p95/p99, error rate, rate-limit-remaining
15
+ * 4. Ship traces + metrics to your OTel collector (Honeycomb, Datadog,
16
+ * New Relic, Grafana Tempo, ...)
17
+ *
18
+ * # Why this matters
19
+ *
20
+ * MP's API is the slow path of any agent that uses it (200-600ms per call).
21
+ * Without observability you can't tell:
22
+ * - Which tool calls are slow (`create_payment` vs `create_subscription` vs `get_payment`)
23
+ * - When MP is degraded (rate-limit-remaining trending down before failures)
24
+ * - Where retries kick in (so you can size your timeout budget correctly)
25
+ *
26
+ * # Setup (one-time at app boot)
27
+ *
28
+ * ```ts
29
+ * // instrumentation.ts (Vercel auto-loads this if present)
30
+ * import { NodeSDK } from "@opentelemetry/sdk-node";
31
+ * import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
32
+ * import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
33
+ * import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
34
+ *
35
+ * const sdk = new NodeSDK({
36
+ * serviceName: "my-ar-agent",
37
+ * traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT! }),
38
+ * metricReader: new PeriodicExportingMetricReader({
39
+ * exporter: new OTLPMetricExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT! }),
40
+ * exportIntervalMillis: 30_000,
41
+ * }),
42
+ * });
43
+ * sdk.start();
44
+ * ```
45
+ *
46
+ * # Edge Runtime
47
+ *
48
+ * Edge-compatible. Use `@vercel/otel` instead of `@opentelemetry/sdk-node`.
49
+ * The instrumentation in `@ar-agents/mercadopago/otel` is runtime-agnostic
50
+ * (uses the `@opentelemetry/api` interface, no Node-only deps).
51
+ */
52
+
53
+ import { trace, context as otelContext } from "@opentelemetry/api";
54
+ import { Experimental_Agent as Agent, stepCountIs } from "ai";
55
+ import {
56
+ MercadoPagoClient,
57
+ mercadoPagoTools,
58
+ InMemoryStateAdapter,
59
+ CircuitBreaker,
60
+ } from "@ar-agents/mercadopago";
61
+ import {
62
+ instrumentMercadoPagoClient,
63
+ instrumentMercadoPagoTools,
64
+ } from "@ar-agents/mercadopago/otel";
65
+
66
+ const tracer = trace.getTracer("my-ar-agent");
67
+ const meter = trace.getTracer("my-ar-agent"); // simplified — use `metrics.getMeter` in real code
68
+
69
+ // 1. Build the client with traceContext so each MP request injects traceparent.
70
+ // The function returns whatever active OTel context exists at call time.
71
+ const mp = new MercadoPagoClient({
72
+ accessToken: process.env.MP_ACCESS_TOKEN!,
73
+ // Wire OTel context propagation. MP logs will be correlated with your trace
74
+ // graph, and downstream tooling (Datadog APM, Honeycomb) can join the dots.
75
+ traceContext: () => {
76
+ const span = trace.getActiveSpan();
77
+ if (!span) return undefined;
78
+ const ctx = span.spanContext();
79
+ return {
80
+ traceId: ctx.traceId,
81
+ spanId: ctx.spanId,
82
+ traceFlags: ctx.traceFlags,
83
+ };
84
+ },
85
+ // Production hardening — circuit breaker observed by OTel as well.
86
+ circuitBreaker: new CircuitBreaker({
87
+ failureThreshold: 5,
88
+ rollingWindowMs: 60_000,
89
+ cooldownMs: 30_000,
90
+ }),
91
+ maxRetries: 2,
92
+ });
93
+
94
+ // 2. Instrument the client. Wraps every public method with a span + metric.
95
+ const instrumentedMp = instrumentMercadoPagoClient(mp, { tracer, meter });
96
+
97
+ // 3. Build tools as usual, then wrap with OTel tool instrumentation.
98
+ const baseTools = mercadoPagoTools(instrumentedMp, {
99
+ state: new InMemoryStateAdapter(),
100
+ backUrl: "https://example.com/done",
101
+ });
102
+ const tools = instrumentMercadoPagoTools(baseTools, { tracer });
103
+
104
+ // 4. Use the agent. Every tool call becomes a span; every MP request is a
105
+ // nested span with full request context. Open Honeycomb / Tempo and you
106
+ // see the full picture.
107
+ export const agent = new Agent({
108
+ model: "anthropic/claude-sonnet-4-6",
109
+ instructions: "You are a billing assistant for a SaaS in Argentina.",
110
+ tools,
111
+ stopWhen: stepCountIs(8),
112
+ });
113
+
114
+ /**
115
+ * Trace shape (in your APM):
116
+ *
117
+ * tool: agent.generate (root span)
118
+ * ├── tool: create_payment_preference (mp.tool)
119
+ * │ └── http: POST /checkout/preferences (mp.request)
120
+ * │ attrs:
121
+ * │ mp.method = POST
122
+ * │ mp.path = /checkout/preferences
123
+ * │ mp.status = 201
124
+ * │ mp.duration_ms = 287
125
+ * │ mp.retried = false
126
+ * │ mp.idempotency_key = <uuid>
127
+ * │
128
+ * └── tool: get_payment (mp.tool)
129
+ * └── http: GET /v1/payments/9999 (mp.request)
130
+ * attrs:
131
+ * mp.method = GET
132
+ * mp.duration_ms = 142
133
+ *
134
+ * Metrics emitted (with attributes [endpoint, method, status_class]):
135
+ *
136
+ * mp_request_duration_ms (histogram)
137
+ * mp_request_total (counter)
138
+ * mp_request_error_total (counter, by status_class=4xx|5xx|network)
139
+ * mp_circuit_breaker_state (gauge: closed=0, open=1, half_open=0.5)
140
+ * mp_rate_limit_remaining (gauge, from x-ratelimit-remaining response header)
141
+ */
142
+
143
+ // Example: wrap a request handler with a parent span so MP calls join the trace.
144
+ export async function handleAgentRequest(userMessage: string) {
145
+ return tracer.startActiveSpan("agent.request", async (span) => {
146
+ try {
147
+ const result = await agent.generate({ prompt: userMessage });
148
+ span.setAttribute("agent.steps", result.steps.length);
149
+ span.setAttribute("agent.finish_reason", result.finishReason);
150
+ return result;
151
+ } finally {
152
+ span.end();
153
+ }
154
+ });
155
+ }
@@ -16,6 +16,7 @@ deploy on Vercel as-is.
16
16
  | 06 | `06-3ds-challenge.ts` | Detect challenge → redirect buyer → recover via webhook |
17
17
  | 07 | `07-auth-only-order.ts` | `Order` with manual capture → capture later when service completes |
18
18
  | 08 | `08-recovery-patterns.ts` | Retry expired subscriptions, recover stuck-pending payments, etc. |
19
+ | 09 | `09-otel-wired.ts` | Full OpenTelemetry wiring — spans + metrics for every MP call + tool |
19
20
 
20
21
  ## Conventions
21
22