@ar-agents/mercadopago 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var crypto = require('crypto');
3
4
  var ai = require('ai');
4
5
  var zod = require('zod');
5
- var crypto = require('crypto');
6
6
 
7
7
  // src/errors.ts
8
8
  var MercadoPagoError = class extends Error {
@@ -100,12 +100,38 @@ var MercadoPagoRateLimitError = class extends MercadoPagoError {
100
100
  }
101
101
  retryAfterSeconds;
102
102
  };
103
+ var MercadoPagoOverloadedError = class extends MercadoPagoError {
104
+ constructor(endpoint, status) {
105
+ super(
106
+ `Mercado Pago appears overloaded \u2014 returned a non-JSON ${status} response for ${endpoint}. Wait a few seconds and retry.`,
107
+ status,
108
+ endpoint
109
+ );
110
+ this.name = "MercadoPagoOverloadedError";
111
+ }
112
+ };
113
+ var MercadoPagoTimeoutError = class extends MercadoPagoError {
114
+ constructor(endpoint, timeoutMs) {
115
+ super(
116
+ `Mercado Pago request timed out after ${timeoutMs}ms on ${endpoint}. Increase requestTimeoutMs or check connectivity.`,
117
+ 0,
118
+ endpoint
119
+ );
120
+ this.timeoutMs = timeoutMs;
121
+ this.name = "MercadoPagoTimeoutError";
122
+ }
123
+ timeoutMs;
124
+ };
103
125
  function classifyError(status, endpoint, body, context) {
104
126
  const bodyText = typeof body === "string" ? body : body && typeof body === "object" ? JSON.stringify(body) : "";
105
127
  const lower = bodyText.toLowerCase();
106
128
  if (status === 401) return new MercadoPagoAuthError(endpoint, body);
107
129
  if (status === 429) {
108
- return new MercadoPagoRateLimitError(endpoint, null, body);
130
+ let retryAfter = null;
131
+ const obj = body;
132
+ if (obj?.retry_after) retryAfter = Number(obj.retry_after);
133
+ else if (obj?.["retry-after"]) retryAfter = Number(obj["retry-after"]);
134
+ return new MercadoPagoRateLimitError(endpoint, retryAfter, body);
109
135
  }
110
136
  if (status === 400) {
111
137
  if (lower.includes("back_url") && lower.includes("not a valid url")) {
@@ -131,10 +157,16 @@ function classifyError(status, endpoint, body, context) {
131
157
 
132
158
  // src/client.ts
133
159
  var DEFAULT_BASE_URL = "https://api.mercadopago.com";
160
+ function sleep(ms) {
161
+ return new Promise((resolve) => setTimeout(resolve, ms));
162
+ }
134
163
  var MercadoPagoClient = class {
135
164
  accessToken;
136
165
  baseUrl;
137
166
  fetchImpl;
167
+ requestTimeoutMs;
168
+ maxRetries;
169
+ onCall;
138
170
  constructor(options) {
139
171
  if (!options.accessToken) {
140
172
  throw new Error(
@@ -144,32 +176,123 @@ var MercadoPagoClient = class {
144
176
  this.accessToken = options.accessToken;
145
177
  this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
146
178
  this.fetchImpl = options.fetch;
179
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
180
+ this.maxRetries = Math.max(0, options.maxRetries ?? 1);
181
+ this.onCall = options.onCall;
147
182
  }
148
- async request(method, path, body, classifyContext) {
149
- const init = {
150
- method,
151
- headers: {
152
- Authorization: `Bearer ${this.accessToken}`,
153
- "Content-Type": "application/json"
154
- }
183
+ async request(method, path, body, options) {
184
+ const headers = {
185
+ Authorization: `Bearer ${this.accessToken}`,
186
+ "Content-Type": "application/json"
155
187
  };
156
- if (body !== void 0) {
157
- init.body = JSON.stringify(body);
188
+ if (options?.idempotencyKey) {
189
+ headers["X-Idempotency-Key"] = options.idempotencyKey;
190
+ }
191
+ let url = `${this.baseUrl}${path}`;
192
+ if (options?.query) {
193
+ const search = new URLSearchParams();
194
+ for (const [k, v] of Object.entries(options.query)) {
195
+ if (v !== void 0 && v !== null && v !== "") {
196
+ search.set(k, String(v));
197
+ }
198
+ }
199
+ const qs = search.toString();
200
+ if (qs) url += `?${qs}`;
158
201
  }
159
202
  const fetchFn = this.fetchImpl ?? globalThis.fetch;
160
- const res = await fetchFn(`${this.baseUrl}${path}`, init);
161
- if (!res.ok) {
162
- let parsed;
163
- const text = await res.text();
203
+ const t0 = Date.now();
204
+ let attempt = 0;
205
+ let lastError;
206
+ let lastStatus = null;
207
+ while (attempt <= this.maxRetries) {
208
+ const controller = new AbortController();
209
+ const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
210
+ const init = { method, headers, signal: controller.signal };
211
+ if (body !== void 0) init.body = JSON.stringify(body);
164
212
  try {
165
- parsed = JSON.parse(text);
166
- } catch {
167
- parsed = text;
213
+ const res = await fetchFn(url, init);
214
+ clearTimeout(timer);
215
+ lastStatus = res.status;
216
+ if (res.ok) {
217
+ const text2 = await res.text();
218
+ this.onCall?.({
219
+ method,
220
+ path,
221
+ durationMs: Date.now() - t0,
222
+ httpStatus: res.status,
223
+ retried: attempt,
224
+ success: true
225
+ });
226
+ if (!text2) return void 0;
227
+ return JSON.parse(text2);
228
+ }
229
+ const isRetryable = res.status >= 500 || res.status === 429;
230
+ if (isRetryable && attempt < this.maxRetries) {
231
+ const retryAfter = res.headers.get("retry-after");
232
+ const waitMs = retryAfter ? Number(retryAfter) * 1e3 : 250 * Math.pow(2, attempt);
233
+ attempt++;
234
+ await sleep(waitMs);
235
+ continue;
236
+ }
237
+ const contentType = res.headers.get("content-type") ?? "";
238
+ if (res.status >= 500 && !contentType.includes("application/json")) {
239
+ this.onCall?.({
240
+ method,
241
+ path,
242
+ durationMs: Date.now() - t0,
243
+ httpStatus: res.status,
244
+ retried: attempt,
245
+ success: false
246
+ });
247
+ throw new MercadoPagoOverloadedError(path, res.status);
248
+ }
249
+ let parsed;
250
+ const text = await res.text();
251
+ try {
252
+ parsed = JSON.parse(text);
253
+ } catch {
254
+ parsed = text;
255
+ }
256
+ const err = classifyError(res.status, path, parsed, options?.classifyContext);
257
+ this.onCall?.({
258
+ method,
259
+ path,
260
+ durationMs: Date.now() - t0,
261
+ httpStatus: res.status,
262
+ retried: attempt,
263
+ success: false
264
+ });
265
+ throw err;
266
+ } catch (err) {
267
+ clearTimeout(timer);
268
+ if (err instanceof MercadoPagoError) throw err;
269
+ const isAbort = err instanceof Error && err.name === "AbortError";
270
+ const isNetwork = !lastStatus && !isAbort;
271
+ if ((isNetwork || isAbort) && attempt < this.maxRetries) {
272
+ lastError = err;
273
+ attempt++;
274
+ await sleep(250 * Math.pow(2, attempt - 1));
275
+ continue;
276
+ }
277
+ this.onCall?.({
278
+ method,
279
+ path,
280
+ durationMs: Date.now() - t0,
281
+ httpStatus: lastStatus,
282
+ retried: attempt,
283
+ success: false
284
+ });
285
+ if (isAbort) {
286
+ throw new MercadoPagoTimeoutError(path, this.requestTimeoutMs);
287
+ }
288
+ throw err;
168
289
  }
169
- throw classifyError(res.status, path, parsed, classifyContext);
170
290
  }
171
- return await res.json();
291
+ throw lastError ?? new Error(`MercadoPago request failed after ${this.maxRetries} retries`);
172
292
  }
293
+ // ───────────────────────────────────────────────────────────────────────────
294
+ // Subscriptions (Preapprovals) — v0.1 surface, kept stable
295
+ // ───────────────────────────────────────────────────────────────────────────
173
296
  /**
174
297
  * Create a recurring subscription (preapproval). The returned `init_point`
175
298
  * URL is where the buyer must complete the FIRST payment with their card +
@@ -191,70 +314,452 @@ var MercadoPagoClient = class {
191
314
  currency_id: params.currency
192
315
  }
193
316
  },
194
- { payerEmail: params.payerEmail }
317
+ { classifyContext: { payerEmail: params.payerEmail } }
195
318
  );
196
319
  }
320
+ async getPreapproval(id) {
321
+ return this.request("GET", `/preapproval/${id}`, void 0, {
322
+ classifyContext: { preapprovalId: id }
323
+ });
324
+ }
325
+ async cancelPreapproval(id) {
326
+ return this.request(
327
+ "PUT",
328
+ `/preapproval/${id}`,
329
+ { status: "cancelled" },
330
+ { classifyContext: { preapprovalId: id } }
331
+ );
332
+ }
333
+ async pausePreapproval(id) {
334
+ return this.request(
335
+ "PUT",
336
+ `/preapproval/${id}`,
337
+ { status: "paused" },
338
+ { classifyContext: { preapprovalId: id } }
339
+ );
340
+ }
341
+ async resumePreapproval(id) {
342
+ return this.request(
343
+ "PUT",
344
+ `/preapproval/${id}`,
345
+ { status: "authorized" },
346
+ { classifyContext: { preapprovalId: id } }
347
+ );
348
+ }
349
+ // ───────────────────────────────────────────────────────────────────────────
350
+ // Payments (v0.2)
351
+ // ───────────────────────────────────────────────────────────────────────────
197
352
  /**
198
- * Fetch the current state of a preapproval. Useful to confirm whether the
199
- * buyer has completed the first payment (`status: 'authorized'`) or whether
200
- * the subscription was cancelled.
353
+ * Create a payment. Two main flows:
354
+ * - **Card payment**: pass `token` (from MP frontend Cardform) + payment_method_id.
355
+ * - **Account money / cash**: omit token, pass payment_method_id like "account_money", "rapipago", "pagofacil".
356
+ *
357
+ * For credit card payments where you don't have a card token (i.e., you only
358
+ * have a payer email and want to send them a payment link), use
359
+ * `createPreference` (Checkout Pro) instead.
360
+ *
361
+ * Idempotency: pass `idempotencyKey` to safely retry. Required for production
362
+ * to dedupe network-failed requests.
201
363
  */
202
- async getPreapproval(id) {
364
+ async createPayment(params) {
365
+ const body = {
366
+ transaction_amount: params.transactionAmount,
367
+ payment_method_id: params.paymentMethodId,
368
+ payer: {
369
+ email: params.payerEmail,
370
+ ...params.identification ? { identification: params.identification } : {}
371
+ }
372
+ };
373
+ if (params.installments !== void 0) body.installments = params.installments;
374
+ if (params.token !== void 0) body.token = params.token;
375
+ if (params.description !== void 0) body.description = params.description;
376
+ if (params.externalReference !== void 0) body.external_reference = params.externalReference;
377
+ if (params.notificationUrl !== void 0) body.notification_url = params.notificationUrl;
378
+ if (params.statementDescriptor !== void 0)
379
+ body.statement_descriptor = params.statementDescriptor;
380
+ if (params.capture !== void 0) body.capture = params.capture;
381
+ if (params.additionalInfo !== void 0) body.additional_info = params.additionalInfo;
382
+ return this.request("POST", "/v1/payments", body, {
383
+ ...params.idempotencyKey ? { idempotencyKey: params.idempotencyKey } : {},
384
+ classifyContext: { payerEmail: params.payerEmail }
385
+ });
386
+ }
387
+ /** Fetch a payment by ID. */
388
+ async getPayment(id) {
389
+ return this.request("GET", `/v1/payments/${id}`, void 0, {
390
+ classifyContext: { paymentId: id }
391
+ });
392
+ }
393
+ /**
394
+ * Search payments with filters. Common: by external_reference (your-system
395
+ * id), by status, by date range. Pagination via offset + limit (max 100).
396
+ */
397
+ async searchPayments(params = {}) {
398
+ const query = {
399
+ limit: params.limit ?? 30,
400
+ offset: params.offset ?? 0
401
+ };
402
+ if (params.externalReference) query["external_reference"] = params.externalReference;
403
+ if (params.status) query["status"] = params.status;
404
+ if (params.payerEmail) query["payer.email"] = params.payerEmail;
405
+ if (params.beginDate) query["begin_date"] = params.beginDate;
406
+ if (params.endDate) query["end_date"] = params.endDate;
407
+ if (params.sort) query["sort"] = params.sort;
408
+ if (params.criteria) query["criteria"] = params.criteria;
203
409
  return this.request(
204
410
  "GET",
205
- `/preapproval/${id}`,
411
+ "/v1/payments/search",
206
412
  void 0,
207
- { preapprovalId: id }
413
+ { query }
208
414
  );
209
415
  }
210
416
  /**
211
- * Cancel an active preapproval. Irreversible: MP will not charge the buyer
212
- * again and the subscription cannot be reactivated.
417
+ * Capture a previously authorized payment. Only works for credit-card
418
+ * payments created with `capture: false`. Optional partial capture amount.
213
419
  */
214
- async cancelPreapproval(id) {
420
+ async capturePayment(id, amount) {
215
421
  return this.request(
216
422
  "PUT",
217
- `/preapproval/${id}`,
218
- { status: "cancelled" },
219
- { preapprovalId: id }
423
+ `/v1/payments/${id}`,
424
+ amount !== void 0 ? { capture: true, transaction_amount: amount } : { capture: true },
425
+ { classifyContext: { paymentId: id } }
220
426
  );
221
427
  }
222
428
  /**
223
- * Pause an authorized preapproval. The subscription stops auto-charging but
224
- * can be re-activated. Note: MP only allows pausing subs that are currently
225
- * `authorized` — pending/cancelled subs reject this.
429
+ * Cancel a pending or in_process payment. Once approved, you must use
430
+ * `createRefund` instead.
226
431
  */
227
- async pausePreapproval(id) {
432
+ async cancelPayment(id) {
228
433
  return this.request(
229
434
  "PUT",
230
- `/preapproval/${id}`,
231
- { status: "paused" },
232
- { preapprovalId: id }
435
+ `/v1/payments/${id}`,
436
+ { status: "cancelled" },
437
+ { classifyContext: { paymentId: id } }
233
438
  );
234
439
  }
440
+ // ───────────────────────────────────────────────────────────────────────────
441
+ // Refunds
442
+ // ───────────────────────────────────────────────────────────────────────────
235
443
  /**
236
- * Re-activate a paused preapproval. Charges resume on the next scheduled
237
- * date.
444
+ * Refund a payment fully (omit `amount`) or partially. Idempotency key
445
+ * recommended — refunds can fail mid-flight and you don't want double-refunds
446
+ * on retry.
238
447
  */
239
- async resumePreapproval(id) {
448
+ async createRefund(params) {
449
+ const body = params.amount !== void 0 ? { amount: params.amount } : void 0;
450
+ return this.request(
451
+ "POST",
452
+ `/v1/payments/${params.paymentId}/refunds`,
453
+ body,
454
+ {
455
+ ...params.idempotencyKey ? { idempotencyKey: params.idempotencyKey } : {},
456
+ classifyContext: { paymentId: params.paymentId }
457
+ }
458
+ );
459
+ }
460
+ async listRefunds(paymentId) {
461
+ const res = await this.request(
462
+ "GET",
463
+ `/v1/payments/${paymentId}/refunds`,
464
+ void 0,
465
+ { classifyContext: { paymentId } }
466
+ );
467
+ return Array.isArray(res) ? res : res.refunds ?? [];
468
+ }
469
+ async getRefund(paymentId, refundId) {
470
+ return this.request(
471
+ "GET",
472
+ `/v1/payments/${paymentId}/refunds/${refundId}`,
473
+ void 0,
474
+ { classifyContext: { paymentId } }
475
+ );
476
+ }
477
+ // ───────────────────────────────────────────────────────────────────────────
478
+ // Checkout Pro (Preferences)
479
+ // ───────────────────────────────────────────────────────────────────────────
480
+ /**
481
+ * Create a payment preference for Checkout Pro. Returns `init_point` URL
482
+ * where the buyer completes payment on MP-hosted form. This is the
483
+ * recommended flow when you don't have a card token (most common path for
484
+ * agents — you don't want to handle PCI data).
485
+ *
486
+ * Sandbox: use `sandbox_init_point` instead of `init_point`.
487
+ */
488
+ async createPreference(params) {
489
+ const body = {
490
+ items: params.items.map((it) => ({
491
+ title: it.title,
492
+ quantity: it.quantity,
493
+ unit_price: it.unit_price,
494
+ currency_id: it.currency_id ?? "ARS",
495
+ ...it.description ? { description: it.description } : {},
496
+ ...it.picture_url ? { picture_url: it.picture_url } : {}
497
+ }))
498
+ };
499
+ if (params.payer) body.payer = params.payer;
500
+ if (params.backUrls) body.back_urls = params.backUrls;
501
+ if (params.autoReturn) body.auto_return = params.autoReturn;
502
+ if (params.notificationUrl) body.notification_url = params.notificationUrl;
503
+ if (params.externalReference) body.external_reference = params.externalReference;
504
+ if (params.paymentMethods) body.payment_methods = params.paymentMethods;
505
+ if (params.statementDescriptor)
506
+ body.statement_descriptor = params.statementDescriptor;
507
+ if (params.expires !== void 0) body.expires = params.expires;
508
+ if (params.expirationDateFrom) body.expiration_date_from = params.expirationDateFrom;
509
+ if (params.expirationDateTo) body.expiration_date_to = params.expirationDateTo;
510
+ return this.request("POST", "/checkout/preferences", body);
511
+ }
512
+ async getPreference(id) {
513
+ return this.request("GET", `/checkout/preferences/${id}`);
514
+ }
515
+ async updatePreference(id, patch) {
516
+ return this.request("PUT", `/checkout/preferences/${id}`, patch);
517
+ }
518
+ // ───────────────────────────────────────────────────────────────────────────
519
+ // Customers + Saved Cards
520
+ // ───────────────────────────────────────────────────────────────────────────
521
+ async createCustomer(params) {
522
+ const body = { email: params.email };
523
+ if (params.firstName) body.first_name = params.firstName;
524
+ if (params.lastName) body.last_name = params.lastName;
525
+ if (params.phone) body.phone = { area_code: params.phone.areaCode, number: params.phone.number };
526
+ if (params.identification) body.identification = params.identification;
527
+ if (params.description) body.description = params.description;
528
+ return this.request("POST", "/v1/customers", body, {
529
+ classifyContext: { payerEmail: params.email }
530
+ });
531
+ }
532
+ async getCustomer(id) {
533
+ return this.request("GET", `/v1/customers/${id}`, void 0, {
534
+ classifyContext: { customerId: id }
535
+ });
536
+ }
537
+ /**
538
+ * Search customers. Most common: by email (returns 0 or 1 result).
539
+ * Note: MP's `/v1/customers/search` returns a paginated wrapper, not a flat array.
540
+ */
541
+ async searchCustomers(params = {}) {
542
+ const query = {
543
+ limit: params.limit ?? 10,
544
+ offset: params.offset ?? 0
545
+ };
546
+ if (params.email) query["email"] = params.email;
547
+ return this.request("GET", "/v1/customers/search", void 0, { query });
548
+ }
549
+ async listCustomerCards(customerId) {
550
+ return this.request(
551
+ "GET",
552
+ `/v1/customers/${customerId}/cards`,
553
+ void 0,
554
+ { classifyContext: { customerId } }
555
+ );
556
+ }
557
+ async getCustomerCard(customerId, cardId) {
558
+ return this.request(
559
+ "GET",
560
+ `/v1/customers/${customerId}/cards/${cardId}`,
561
+ void 0,
562
+ { classifyContext: { customerId } }
563
+ );
564
+ }
565
+ async deleteCustomerCard(customerId, cardId) {
566
+ await this.request(
567
+ "DELETE",
568
+ `/v1/customers/${customerId}/cards/${cardId}`,
569
+ void 0,
570
+ { classifyContext: { customerId } }
571
+ );
572
+ }
573
+ // ───────────────────────────────────────────────────────────────────────────
574
+ // Payment Methods + Installments
575
+ // ───────────────────────────────────────────────────────────────────────────
576
+ /** List all payment methods enabled for the account's site (MLA = Argentina). */
577
+ async listPaymentMethods() {
578
+ return this.request("GET", "/v1/payment_methods");
579
+ }
580
+ /**
581
+ * Get installment options for an amount. THE killer AR feature — returns
582
+ * `payer_costs` with `recommended_message` strings like "12 cuotas sin
583
+ * interés de $X" that you should surface verbatim to the user.
584
+ *
585
+ * Pass `bin` (first 6 digits of card) for issuer-specific offers (e.g.,
586
+ * Naranja's interest-free promotions). Without bin, returns generic offers.
587
+ */
588
+ async getInstallments(params) {
589
+ const query = {
590
+ amount: params.amount
591
+ };
592
+ if (params.paymentMethodId) query["payment_method_id"] = params.paymentMethodId;
593
+ if (params.bin) query["bin"] = params.bin;
594
+ if (params.issuerId) query["issuer.id"] = params.issuerId;
595
+ return this.request(
596
+ "GET",
597
+ "/v1/payment_methods/installments",
598
+ void 0,
599
+ { query }
600
+ );
601
+ }
602
+ // ───────────────────────────────────────────────────────────────────────────
603
+ // Account
604
+ // ───────────────────────────────────────────────────────────────────────────
605
+ /** Get info about the account that owns this access token. */
606
+ async getMe() {
607
+ return this.request("GET", "/users/me");
608
+ }
609
+ // ───────────────────────────────────────────────────────────────────────────
610
+ // Card tokens (server-side, for saved-card retokenization)
611
+ // ───────────────────────────────────────────────────────────────────────────
612
+ /**
613
+ * Create a single-use card token from a saved card. This is the server-side
614
+ * retokenization path (PCI-safe because the card data lives in MP's vault,
615
+ * we only pass the saved card_id + customer_id + the user-supplied CVV).
616
+ *
617
+ * Tokens expire in 7 days but typically burn on first use. AR currently
618
+ * REQUIRES CVV on every charge (MP doesn't store it); skipping CVV requires
619
+ * a private MP product enablement, not a public API.
620
+ */
621
+ async createCardToken(params) {
622
+ return this.request("POST", "/v1/card_tokens", {
623
+ card_id: params.cardId,
624
+ customer_id: params.customerId,
625
+ security_code: params.securityCode
626
+ });
627
+ }
628
+ /**
629
+ * High-level helper: charge a saved card in 3 steps.
630
+ * 1. Mint a card token from {customer_id, card_id, security_code}
631
+ * 2. Lookup card to fill payment_method_id (avoids agent guessing)
632
+ * 3. Create the payment with the token + idempotency key
633
+ *
634
+ * Returns the resulting Payment. Uses deterministic idempotency from
635
+ * (card_id, amount, externalReference) so retries dedupe on MP's side.
636
+ */
637
+ async chargeSavedCard(params) {
638
+ const token = await this.createCardToken({
639
+ cardId: params.cardId,
640
+ customerId: params.customerId,
641
+ securityCode: params.securityCode
642
+ });
643
+ const card = await this.getCustomerCard(params.customerId, params.cardId);
644
+ const paymentMethodId = card.payment_method?.id;
645
+ if (!paymentMethodId) {
646
+ throw new MercadoPagoError(
647
+ `Saved card ${params.cardId} has no payment_method.id. Cannot charge.`,
648
+ 0,
649
+ `/v1/customers/${params.customerId}/cards/${params.cardId}`
650
+ );
651
+ }
652
+ const body = {
653
+ transaction_amount: params.amount,
654
+ token: token.id,
655
+ payment_method_id: paymentMethodId,
656
+ installments: params.installments ?? 1,
657
+ description: params.description,
658
+ payer: { type: "customer", id: params.customerId }
659
+ };
660
+ if (params.externalReference) body.external_reference = params.externalReference;
661
+ if (params.statementDescriptor) body.statement_descriptor = params.statementDescriptor;
662
+ return this.request("POST", "/v1/payments", body, {
663
+ ...params.idempotencyKey !== void 0 ? { idempotencyKey: params.idempotencyKey } : {},
664
+ classifyContext: { customerId: params.customerId }
665
+ });
666
+ }
667
+ // ───────────────────────────────────────────────────────────────────────────
668
+ // QR (in-store dynamic) — Section 2 of v0.3 spec
669
+ // ───────────────────────────────────────────────────────────────────────────
670
+ /**
671
+ * Create a dynamic in-store QR order. Returns `qr_data` (EMVCo TLV string)
672
+ * + `in_store_order_id`. The buyer scans the QR with any AR wallet (Modo,
673
+ * BNA+, Cuenta DNI, Naranja X, etc. — interop is mandated by Transferencias
674
+ * 3.0). On payment, MP fires `point_integration_wh` then `payment` topics.
675
+ *
676
+ * Requires a pre-configured POS (`external_pos_id` from MP dashboard or
677
+ * `POST /pos`). The seller's `user_id` is auto-fetched from `/users/me`.
678
+ *
679
+ * The lib does NOT render the QR image — pass `qr_data` to a QR renderer
680
+ * (e.g., `qrcode` package) to get a data URL. The agent tool layer wraps
681
+ * this and returns both raw + data URL.
682
+ */
683
+ async createQrPayment(userId, params) {
684
+ const body = {
685
+ total_amount: params.totalAmount,
686
+ title: params.title
687
+ };
688
+ if (params.description) body.description = params.description;
689
+ if (params.notificationUrl) body.notification_url = params.notificationUrl;
690
+ if (params.externalReference) body.external_reference = params.externalReference;
691
+ if (params.expirationDate) body.expiration_date = params.expirationDate;
692
+ body.items = params.items ?? [
693
+ {
694
+ title: params.title,
695
+ quantity: 1,
696
+ unit_price: params.totalAmount,
697
+ unit_measure: "unit",
698
+ total_amount: params.totalAmount
699
+ }
700
+ ];
240
701
  return this.request(
241
702
  "PUT",
242
- `/preapproval/${id}`,
243
- { status: "authorized" },
244
- { preapprovalId: id }
703
+ `/instore/orders/qr/seller/collectors/${encodeURIComponent(userId)}/pos/${encodeURIComponent(params.externalPosId)}/qrs`,
704
+ body
705
+ );
706
+ }
707
+ /**
708
+ * Cancel a pending QR order on a POS. Necessary if the buyer never scans
709
+ * — otherwise the next `createQrPayment` on the same POS returns 409.
710
+ */
711
+ async cancelQrPayment(userId, externalPosId) {
712
+ await this.request(
713
+ "DELETE",
714
+ `/instore/orders/qr/seller/collectors/${encodeURIComponent(userId)}/pos/${encodeURIComponent(externalPosId)}/qrs`
245
715
  );
246
716
  }
247
717
  };
718
+ function deterministicIdempotencyKey(...parts) {
719
+ const payload = parts.filter((p) => p !== void 0 && p !== null).map(String).join("|");
720
+ return crypto.createHash("sha256").update(payload).digest("hex").slice(0, 32);
721
+ }
248
722
  var DEFAULT_DESCRIPTIONS = {
723
+ // ── Subscriptions ────────────────────────────────────────────────────────
249
724
  create_subscription: "Create a Mercado Pago recurring subscription. Returns an init_point URL where the customer must complete the FIRST payment with their card and CVV (this is a hard MP requirement; agents cannot bypass it). After they pay, MP will auto-charge at the configured frequency without further intervention.",
250
725
  get_subscription_status: "Check the current status of a Mercado Pago subscription. Use this to confirm the customer completed the first payment (status becomes 'authorized') or to inspect the next charge date.",
251
726
  cancel_subscription: "Cancel an active Mercado Pago subscription. After cancellation, MP will not charge the customer again. This action is irreversible \u2014 confirm with the user before calling.",
252
727
  pause_subscription: "Pause an authorized Mercado Pago subscription. Charges stop until resumed. Only works on subscriptions in 'authorized' status.",
253
- resume_subscription: "Resume a paused Mercado Pago subscription. Charges resume on the next scheduled date. Only works on subscriptions in 'paused' status."
728
+ resume_subscription: "Resume a paused Mercado Pago subscription. Charges resume on the next scheduled date. Only works on subscriptions in 'paused' status.",
729
+ // ── Payments ─────────────────────────────────────────────────────────────
730
+ create_payment: "Create a one-time payment. Two flows: (a) with a card token from MP frontend Cardform \u2014 for transparent checkout; (b) without token, for non-card methods like 'account_money', 'rapipago', 'pagofacil'. For most agent flows where you only have a payer email and want to send them a payment link, use create_payment_preference instead (Checkout Pro hosted form). Returns the Payment object with status \u2014 typically 'approved' for account_money and 'pending' for tickets.",
731
+ get_payment: "Fetch a single payment by ID. Use to confirm status after webhook arrives, or to inspect details (status_detail explains rejections).",
732
+ search_payments: "Search payments with filters. Most common: by external_reference (your-system identifier) to find all payments for an order, or by status='approved' to list successful charges in a date range. Returns paginated results.",
733
+ cancel_payment: "Cancel a pending or in_process payment (only works before approval). Once approved, use refund_payment instead. Common use: cancel an unpaid ticket payment that's still pending.",
734
+ capture_payment: "Capture an authorized credit-card payment that was created with capture=false. Use for hold-then-capture flows (e.g., authorize on order, capture on shipment). Optional partial amount.",
735
+ // ── Refunds ──────────────────────────────────────────────────────────────
736
+ refund_payment: "Refund an approved payment. Pass amount for partial refund; omit for full refund. Idempotency key is auto-generated based on paymentId+amount to prevent double-refunds on retries.",
737
+ list_refunds: "List all refunds for a given payment. Returns array of Refund objects. Useful to confirm a refund was processed or to inspect partial-refund history.",
738
+ // ── Checkout Pro ─────────────────────────────────────────────────────────
739
+ create_payment_preference: "Create a Mercado Pago Checkout Pro preference and get back a payment URL (init_point) to send to the customer. THIS is the recommended way for an agent to take a payment when you only have a payer email \u2014 the buyer enters card data on MP's hosted form (no PCI scope needed). Supports cuotas configuration, payment method exclusions, back URLs after success/failure/pending. In sandbox, use sandbox_init_point from the response.",
740
+ get_payment_preference: "Fetch a Checkout Pro preference by ID. Returns the preference config and current init_point URLs. Use to inspect a previously-created link.",
741
+ // ── Customers + Cards ────────────────────────────────────────────────────
742
+ create_customer: "Create a Mercado Pago customer record so the buyer can save cards for future charges. Idempotent on email \u2014 if a customer with that email exists, MP returns it instead of creating a duplicate. Use find_customer_by_email first if you're unsure.",
743
+ find_customer_by_email: "Find an existing customer by email address. Returns the customer object if found, or null. Use before create_customer to avoid duplicate records.",
744
+ list_customer_cards: "List the saved cards for a customer. Returns array with last 4 digits, expiration, payment method (visa, master, naranja, etc.). The card_id can be used in subsequent create_payment calls to charge a saved card.",
745
+ delete_customer_card: "Delete a saved card from a customer. Common use: customer requests removal, or expired card cleanup. Irreversible.",
746
+ // ── Payment Methods + Installments ───────────────────────────────────────
747
+ list_payment_methods: "List all payment methods enabled for the seller's MP account (visa, master, naranja, naranja_x, cabal, account_money, rapipago, pagofacil, etc.). Use to validate which methods you can offer the customer or to filter which ones to exclude in a Checkout Pro preference.",
748
+ calculate_installments: "Calculate cuotas (installments) options for a given amount. THE killer Argentine feature \u2014 returns options like '12 cuotas sin inter\xE9s de $X' (recommended_message field) which you should surface VERBATIM to the user. Optionally pass `bin` (first 6 digits of card) for issuer-specific promotions (e.g., Naranja's interest-free deals). Use before create_payment to let the user pick installments knowingly.",
749
+ // ── Account ──────────────────────────────────────────────────────────────
750
+ get_account_info: "Get info about the Mercado Pago account that owns the access token: site_id (MLA=Argentina), country_id, user_type (registered, partial, etc.). Useful to verify the agent is connected to the right account before taking actions.",
751
+ // ── Saved-card charging (v0.3) ───────────────────────────────────────────
752
+ charge_saved_card: "Charge a previously-saved card for a returning customer. Requires customer_id + card_id (from list_customer_cards) AND a fresh CVV the user provides this session. AR Mercado Pago does NOT support CVV-less charges via the public API \u2014 every charge needs CVV. Idempotent on (card_id, amount, external_reference): retries dedupe automatically. Returns the resulting Payment.",
753
+ // ── QR in-store (v0.3) ───────────────────────────────────────────────────
754
+ create_qr_payment: "Generate a dynamic in-store QR for a buyer to scan with any AR wallet (Modo, BNA+, Cuenta DNI, Naranja X, Mercado Pago, etc. \u2014 interop is mandated by Transferencias 3.0). Requires a pre-configured POS external_id (one-time setup in MP dashboard). Returns the qr_data string + a base64 PNG data URL ready to display. The QR expires in `expires_in_seconds` (default 600). MP fires `point_integration_wh` then `payment` webhooks when scanned.",
755
+ cancel_qr_payment: "Cancel a pending QR order on a POS. Necessary if the buyer never scans \u2014 otherwise the next create_qr_payment on the same POS returns 409."
254
756
  };
255
757
  function mercadoPagoTools(client, options) {
256
758
  const desc = (name) => options.descriptions?.[name] ?? DEFAULT_DESCRIPTIONS[name];
257
759
  return {
760
+ // ─────────────────────────────────────────────────────────────────────────
761
+ // Subscriptions (v0.1 — kept identical for backward compatibility)
762
+ // ─────────────────────────────────────────────────────────────────────────
258
763
  create_subscription: ai.tool({
259
764
  description: desc("create_subscription"),
260
765
  inputSchema: zod.z.object({
@@ -264,13 +769,7 @@ function mercadoPagoTools(client, options) {
264
769
  reason: zod.z.string().min(3).max(120).describe("Short description shown to the customer at checkout"),
265
770
  external_reference: zod.z.string().optional().describe("Optional id from your system to track this subscription")
266
771
  }),
267
- execute: async ({
268
- customer_email,
269
- amount_ars,
270
- frequency_months,
271
- reason,
272
- external_reference
273
- }) => {
772
+ execute: async ({ customer_email, amount_ars, frequency_months, reason, external_reference }) => {
274
773
  const created = await client.createPreapproval({
275
774
  reason,
276
775
  payerEmail: customer_email,
@@ -340,9 +839,7 @@ function mercadoPagoTools(client, options) {
340
839
  }),
341
840
  pause_subscription: ai.tool({
342
841
  description: desc("pause_subscription"),
343
- inputSchema: zod.z.object({
344
- subscription_id: zod.z.string()
345
- }),
842
+ inputSchema: zod.z.object({ subscription_id: zod.z.string() }),
346
843
  execute: async ({ subscription_id }) => {
347
844
  const paused = await client.pausePreapproval(subscription_id);
348
845
  await options.state.set(subscription_id, { status: paused.status });
@@ -355,9 +852,7 @@ function mercadoPagoTools(client, options) {
355
852
  }),
356
853
  resume_subscription: ai.tool({
357
854
  description: desc("resume_subscription"),
358
- inputSchema: zod.z.object({
359
- subscription_id: zod.z.string()
360
- }),
855
+ inputSchema: zod.z.object({ subscription_id: zod.z.string() }),
361
856
  execute: async ({ subscription_id }) => {
362
857
  const resumed = await client.resumePreapproval(subscription_id);
363
858
  await options.state.set(subscription_id, { status: resumed.status });
@@ -367,6 +862,506 @@ function mercadoPagoTools(client, options) {
367
862
  message: "Subscription resumed. Charges will continue on next scheduled date."
368
863
  };
369
864
  }
865
+ }),
866
+ // ─────────────────────────────────────────────────────────────────────────
867
+ // Payments (v0.2)
868
+ // ─────────────────────────────────────────────────────────────────────────
869
+ create_payment: ai.tool({
870
+ description: desc("create_payment"),
871
+ inputSchema: zod.z.object({
872
+ amount_ars: zod.z.number().positive().describe("Amount in ARS"),
873
+ payment_method_id: zod.z.string().describe("MP payment method id (e.g. 'account_money', 'rapipago', 'visa', 'master', 'naranja')"),
874
+ payer_email: zod.z.string().email().describe("Email of the payer. Cannot equal seller email."),
875
+ token: zod.z.string().optional().describe("Card token from MP frontend Cardform. Required for credit/debit; omit for cash/account_money."),
876
+ installments: zod.z.number().int().min(1).max(24).optional().describe("Number of installments (cuotas). Default 1. Use calculate_installments first to see options."),
877
+ description: zod.z.string().max(255).optional().describe("Short description"),
878
+ external_reference: zod.z.string().optional().describe("Your-system identifier"),
879
+ identification: zod.z.object({
880
+ type: zod.z.enum(["DNI", "CUIT", "CUIL"]),
881
+ number: zod.z.string()
882
+ }).optional().describe("Payer identification \u2014 required for some payment types in AR"),
883
+ statement_descriptor: zod.z.string().max(13).optional().describe("Shows on buyer's card statement (max 13 chars)")
884
+ }),
885
+ execute: async (input) => {
886
+ const payment = await client.createPayment({
887
+ transactionAmount: input.amount_ars,
888
+ paymentMethodId: input.payment_method_id,
889
+ payerEmail: input.payer_email,
890
+ ...input.token !== void 0 ? { token: input.token } : {},
891
+ ...input.installments !== void 0 ? { installments: input.installments } : {},
892
+ ...input.description !== void 0 ? { description: input.description } : {},
893
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
894
+ ...input.identification !== void 0 ? { identification: input.identification } : {},
895
+ ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
896
+ ...options.notificationUrl !== void 0 ? { notificationUrl: options.notificationUrl } : {},
897
+ // Deterministic idempotency key — safe to retry, same inputs always
898
+ // produce the same key (MP dedupes on its side).
899
+ idempotencyKey: deterministicIdempotencyKey(
900
+ "create_payment",
901
+ input.external_reference ?? input.payer_email,
902
+ input.amount_ars,
903
+ input.payment_method_id,
904
+ input.token
905
+ )
906
+ });
907
+ return {
908
+ payment_id: payment.id,
909
+ status: payment.status,
910
+ status_detail: payment.status_detail,
911
+ amount: payment.transaction_amount,
912
+ currency: payment.currency_id,
913
+ installments: payment.installments,
914
+ payment_method: payment.payment_method_id,
915
+ payer_email: payment.payer?.email ?? null,
916
+ external_reference: payment.external_reference,
917
+ date_created: payment.date_created,
918
+ date_approved: payment.date_approved
919
+ };
920
+ }
921
+ }),
922
+ get_payment: ai.tool({
923
+ description: desc("get_payment"),
924
+ inputSchema: zod.z.object({
925
+ payment_id: zod.z.string().describe("The MP payment ID")
926
+ }),
927
+ execute: async ({ payment_id }) => {
928
+ const p = await client.getPayment(payment_id);
929
+ return {
930
+ payment_id: p.id,
931
+ status: p.status,
932
+ status_detail: p.status_detail,
933
+ amount: p.transaction_amount,
934
+ currency: p.currency_id,
935
+ payment_method: p.payment_method_id,
936
+ installments: p.installments,
937
+ payer_email: p.payer?.email ?? null,
938
+ external_reference: p.external_reference,
939
+ date_created: p.date_created,
940
+ date_approved: p.date_approved,
941
+ net_received: p.transaction_details?.net_received_amount ?? null
942
+ };
943
+ }
944
+ }),
945
+ search_payments: ai.tool({
946
+ description: desc("search_payments"),
947
+ inputSchema: zod.z.object({
948
+ external_reference: zod.z.string().optional(),
949
+ status: zod.z.string().optional().describe("'approved' | 'pending' | 'rejected' | 'cancelled' | 'refunded' etc."),
950
+ payer_email: zod.z.string().optional(),
951
+ begin_date: zod.z.string().optional().describe("ISO 8601, e.g. 2026-01-01T00:00:00Z"),
952
+ end_date: zod.z.string().optional().describe("ISO 8601"),
953
+ limit: zod.z.number().int().min(1).max(100).optional().describe("Default 30, max 100"),
954
+ offset: zod.z.number().int().min(0).optional().describe("Pagination offset (default 0)")
955
+ }),
956
+ execute: async (input) => {
957
+ const result = await client.searchPayments({
958
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
959
+ ...input.status !== void 0 ? { status: input.status } : {},
960
+ ...input.payer_email !== void 0 ? { payerEmail: input.payer_email } : {},
961
+ ...input.begin_date !== void 0 ? { beginDate: input.begin_date } : {},
962
+ ...input.end_date !== void 0 ? { endDate: input.end_date } : {},
963
+ ...input.limit !== void 0 ? { limit: input.limit } : {},
964
+ ...input.offset !== void 0 ? { offset: input.offset } : {}
965
+ });
966
+ return {
967
+ total: result.paging.total,
968
+ returned: result.results.length,
969
+ offset: result.paging.offset,
970
+ payments: result.results.map((p) => ({
971
+ payment_id: p.id,
972
+ status: p.status,
973
+ amount: p.transaction_amount,
974
+ currency: p.currency_id,
975
+ payer_email: p.payer?.email ?? null,
976
+ external_reference: p.external_reference,
977
+ date_created: p.date_created
978
+ }))
979
+ };
980
+ }
981
+ }),
982
+ cancel_payment: ai.tool({
983
+ description: desc("cancel_payment"),
984
+ inputSchema: zod.z.object({ payment_id: zod.z.string() }),
985
+ execute: async ({ payment_id }) => {
986
+ const cancelled = await client.cancelPayment(payment_id);
987
+ return {
988
+ payment_id: cancelled.id,
989
+ status: cancelled.status,
990
+ message: "Payment cancelled. If it was already approved, use refund_payment instead."
991
+ };
992
+ }
993
+ }),
994
+ capture_payment: ai.tool({
995
+ description: desc("capture_payment"),
996
+ inputSchema: zod.z.object({
997
+ payment_id: zod.z.string(),
998
+ amount_ars: zod.z.number().positive().optional().describe("Optional partial-capture amount. Omit to capture full authorized amount.")
999
+ }),
1000
+ execute: async ({ payment_id, amount_ars }) => {
1001
+ const captured = await client.capturePayment(payment_id, amount_ars);
1002
+ return {
1003
+ payment_id: captured.id,
1004
+ status: captured.status,
1005
+ amount: captured.transaction_amount
1006
+ };
1007
+ }
1008
+ }),
1009
+ // ─────────────────────────────────────────────────────────────────────────
1010
+ // Refunds
1011
+ // ─────────────────────────────────────────────────────────────────────────
1012
+ refund_payment: ai.tool({
1013
+ description: desc("refund_payment"),
1014
+ inputSchema: zod.z.object({
1015
+ payment_id: zod.z.string(),
1016
+ amount_ars: zod.z.number().positive().optional().describe("Partial-refund amount in ARS. Omit for full refund.")
1017
+ }),
1018
+ execute: async ({ payment_id, amount_ars }) => {
1019
+ const refund = await client.createRefund({
1020
+ paymentId: payment_id,
1021
+ ...amount_ars !== void 0 ? { amount: amount_ars } : {},
1022
+ idempotencyKey: deterministicIdempotencyKey("refund", payment_id, amount_ars ?? "full")
1023
+ });
1024
+ return {
1025
+ refund_id: refund.id,
1026
+ payment_id: refund.payment_id,
1027
+ amount: refund.amount,
1028
+ status: refund.status,
1029
+ message: amount_ars === void 0 ? "Full refund issued. Funds return to the buyer in 3-10 business days." : `Partial refund of ${amount_ars} ARS issued.`
1030
+ };
1031
+ }
1032
+ }),
1033
+ list_refunds: ai.tool({
1034
+ description: desc("list_refunds"),
1035
+ inputSchema: zod.z.object({ payment_id: zod.z.string() }),
1036
+ execute: async ({ payment_id }) => {
1037
+ const refunds = await client.listRefunds(payment_id);
1038
+ return {
1039
+ payment_id,
1040
+ count: refunds.length,
1041
+ refunds: refunds.map((r) => ({
1042
+ refund_id: r.id,
1043
+ amount: r.amount,
1044
+ status: r.status,
1045
+ date_created: r.date_created
1046
+ }))
1047
+ };
1048
+ }
1049
+ }),
1050
+ // ─────────────────────────────────────────────────────────────────────────
1051
+ // Checkout Pro
1052
+ // ─────────────────────────────────────────────────────────────────────────
1053
+ create_payment_preference: ai.tool({
1054
+ description: desc("create_payment_preference"),
1055
+ inputSchema: zod.z.object({
1056
+ items: zod.z.array(zod.z.object({
1057
+ title: zod.z.string().min(1).max(256),
1058
+ quantity: zod.z.number().int().positive(),
1059
+ unit_price: zod.z.number().positive(),
1060
+ description: zod.z.string().optional(),
1061
+ picture_url: zod.z.string().url().optional()
1062
+ })).min(1).describe("Items being charged. At least one required."),
1063
+ payer_email: zod.z.string().email().optional().describe("Pre-fill the payer email on Checkout Pro form"),
1064
+ external_reference: zod.z.string().optional(),
1065
+ max_installments: zod.z.number().int().min(1).max(24).optional().describe("Limit max cuotas offered. Defaults to MP account config."),
1066
+ statement_descriptor: zod.z.string().max(13).optional(),
1067
+ excluded_payment_types: zod.z.array(zod.z.enum(["credit_card", "debit_card", "ticket", "atm", "bank_transfer"])).optional().describe("Block payment types \u2014 e.g., ['ticket'] to disable Rapipago/Pago F\xE1cil")
1068
+ }),
1069
+ execute: async (input) => {
1070
+ const pref = await client.createPreference({
1071
+ items: input.items.map((it) => ({
1072
+ title: it.title,
1073
+ quantity: it.quantity,
1074
+ unit_price: it.unit_price,
1075
+ currency_id: "ARS",
1076
+ ...it.description !== void 0 ? { description: it.description } : {},
1077
+ ...it.picture_url !== void 0 ? { picture_url: it.picture_url } : {}
1078
+ })),
1079
+ ...input.payer_email !== void 0 ? { payer: { email: input.payer_email } } : {},
1080
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
1081
+ ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
1082
+ backUrls: { success: options.backUrl, failure: options.backUrl, pending: options.backUrl },
1083
+ autoReturn: "approved",
1084
+ ...options.notificationUrl !== void 0 ? { notificationUrl: options.notificationUrl } : {},
1085
+ ...input.max_installments !== void 0 || input.excluded_payment_types !== void 0 ? {
1086
+ paymentMethods: {
1087
+ ...input.max_installments !== void 0 ? { installments: input.max_installments } : {},
1088
+ ...input.excluded_payment_types !== void 0 ? { excluded_payment_types: input.excluded_payment_types.map((id) => ({ id })) } : {}
1089
+ }
1090
+ } : {}
1091
+ });
1092
+ return {
1093
+ preference_id: pref.id,
1094
+ init_point_url: pref.init_point ?? null,
1095
+ sandbox_init_point_url: pref.sandbox_init_point ?? null,
1096
+ external_reference: pref.external_reference,
1097
+ date_created: pref.date_created,
1098
+ next_step: "Send init_point_url (or sandbox_init_point_url in sandbox) to the customer. After they pay, MP fires a webhook with the payment_id; use get_payment to confirm status."
1099
+ };
1100
+ }
1101
+ }),
1102
+ get_payment_preference: ai.tool({
1103
+ description: desc("get_payment_preference"),
1104
+ inputSchema: zod.z.object({ preference_id: zod.z.string() }),
1105
+ execute: async ({ preference_id }) => {
1106
+ const pref = await client.getPreference(preference_id);
1107
+ return {
1108
+ preference_id: pref.id,
1109
+ init_point_url: pref.init_point ?? null,
1110
+ sandbox_init_point_url: pref.sandbox_init_point ?? null,
1111
+ external_reference: pref.external_reference,
1112
+ items: pref.items,
1113
+ date_created: pref.date_created
1114
+ };
1115
+ }
1116
+ }),
1117
+ // ─────────────────────────────────────────────────────────────────────────
1118
+ // Customers + Saved Cards
1119
+ // ─────────────────────────────────────────────────────────────────────────
1120
+ create_customer: ai.tool({
1121
+ description: desc("create_customer"),
1122
+ inputSchema: zod.z.object({
1123
+ email: zod.z.string().email(),
1124
+ first_name: zod.z.string().optional(),
1125
+ last_name: zod.z.string().optional(),
1126
+ identification: zod.z.object({
1127
+ type: zod.z.enum(["DNI", "CUIT", "CUIL"]),
1128
+ number: zod.z.string()
1129
+ }).optional(),
1130
+ description: zod.z.string().optional()
1131
+ }),
1132
+ execute: async (input) => {
1133
+ const customer = await client.createCustomer({
1134
+ email: input.email,
1135
+ ...input.first_name !== void 0 ? { firstName: input.first_name } : {},
1136
+ ...input.last_name !== void 0 ? { lastName: input.last_name } : {},
1137
+ ...input.identification !== void 0 ? { identification: input.identification } : {},
1138
+ ...input.description !== void 0 ? { description: input.description } : {}
1139
+ });
1140
+ return {
1141
+ customer_id: customer.id,
1142
+ email: customer.email,
1143
+ first_name: customer.first_name,
1144
+ last_name: customer.last_name,
1145
+ date_created: customer.date_created
1146
+ };
1147
+ }
1148
+ }),
1149
+ find_customer_by_email: ai.tool({
1150
+ description: desc("find_customer_by_email"),
1151
+ inputSchema: zod.z.object({ email: zod.z.string().email() }),
1152
+ execute: async ({ email }) => {
1153
+ const result = await client.searchCustomers({ email, limit: 1 });
1154
+ const customer = result.results[0] ?? null;
1155
+ return customer ? {
1156
+ found: true,
1157
+ customer_id: customer.id,
1158
+ email: customer.email,
1159
+ first_name: customer.first_name,
1160
+ last_name: customer.last_name
1161
+ } : { found: false, customer_id: null };
1162
+ }
1163
+ }),
1164
+ list_customer_cards: ai.tool({
1165
+ description: desc("list_customer_cards"),
1166
+ inputSchema: zod.z.object({ customer_id: zod.z.string() }),
1167
+ execute: async ({ customer_id }) => {
1168
+ const cards = await client.listCustomerCards(customer_id);
1169
+ return {
1170
+ customer_id,
1171
+ count: cards.length,
1172
+ cards: cards.map((c) => ({
1173
+ card_id: c.id,
1174
+ last_four_digits: c.last_four_digits,
1175
+ expiration_month: c.expiration_month,
1176
+ expiration_year: c.expiration_year,
1177
+ payment_method: c.payment_method?.id ?? null,
1178
+ payment_method_name: c.payment_method?.name ?? null
1179
+ }))
1180
+ };
1181
+ }
1182
+ }),
1183
+ delete_customer_card: ai.tool({
1184
+ description: desc("delete_customer_card"),
1185
+ inputSchema: zod.z.object({
1186
+ customer_id: zod.z.string(),
1187
+ card_id: zod.z.string()
1188
+ }),
1189
+ execute: async ({ customer_id, card_id }) => {
1190
+ await client.deleteCustomerCard(customer_id, card_id);
1191
+ return { customer_id, card_id, deleted: true };
1192
+ }
1193
+ }),
1194
+ // ─────────────────────────────────────────────────────────────────────────
1195
+ // Payment Methods + Installments
1196
+ // ─────────────────────────────────────────────────────────────────────────
1197
+ list_payment_methods: ai.tool({
1198
+ description: desc("list_payment_methods"),
1199
+ inputSchema: zod.z.object({}),
1200
+ execute: async () => {
1201
+ const methods = await client.listPaymentMethods();
1202
+ return {
1203
+ count: methods.length,
1204
+ methods: methods.map((m) => ({
1205
+ id: m.id,
1206
+ name: m.name,
1207
+ payment_type: m.payment_type_id,
1208
+ status: m.status,
1209
+ min_amount: m.min_allowed_amount,
1210
+ max_amount: m.max_allowed_amount
1211
+ }))
1212
+ };
1213
+ }
1214
+ }),
1215
+ calculate_installments: ai.tool({
1216
+ description: desc("calculate_installments"),
1217
+ inputSchema: zod.z.object({
1218
+ amount_ars: zod.z.number().positive(),
1219
+ payment_method_id: zod.z.string().optional().describe("E.g. 'visa', 'master', 'naranja'. Omit for all available methods."),
1220
+ bin: zod.z.string().min(6).max(8).optional().describe("First 6-8 digits of card for issuer-specific offers (e.g., Naranja interest-free promotions)")
1221
+ }),
1222
+ execute: async (input) => {
1223
+ const offers = await client.getInstallments({
1224
+ amount: input.amount_ars,
1225
+ ...input.payment_method_id !== void 0 ? { paymentMethodId: input.payment_method_id } : {},
1226
+ ...input.bin !== void 0 ? { bin: input.bin } : {}
1227
+ });
1228
+ return {
1229
+ amount: input.amount_ars,
1230
+ offers: offers.map((o) => ({
1231
+ payment_method_id: o.payment_method_id,
1232
+ payment_type_id: o.payment_type_id,
1233
+ issuer_name: o.issuer?.name ?? null,
1234
+ options: o.payer_costs.map((pc) => ({
1235
+ installments: pc.installments,
1236
+ installment_amount: pc.installment_amount,
1237
+ total_amount: pc.total_amount,
1238
+ installment_rate: pc.installment_rate,
1239
+ recommended_message: pc.recommended_message
1240
+ }))
1241
+ }))
1242
+ };
1243
+ }
1244
+ }),
1245
+ // ─────────────────────────────────────────────────────────────────────────
1246
+ // Account
1247
+ // ─────────────────────────────────────────────────────────────────────────
1248
+ get_account_info: ai.tool({
1249
+ description: desc("get_account_info"),
1250
+ inputSchema: zod.z.object({}),
1251
+ execute: async () => {
1252
+ const me = await client.getMe();
1253
+ return {
1254
+ account_id: me.id,
1255
+ email: me.email,
1256
+ nickname: me.nickname,
1257
+ country_id: me.country_id,
1258
+ site_id: me.site_id,
1259
+ user_type: me.user_type
1260
+ };
1261
+ }
1262
+ }),
1263
+ // ─────────────────────────────────────────────────────────────────────────
1264
+ // Saved-card charging (v0.3)
1265
+ // ─────────────────────────────────────────────────────────────────────────
1266
+ charge_saved_card: ai.tool({
1267
+ description: desc("charge_saved_card"),
1268
+ inputSchema: zod.z.object({
1269
+ customer_id: zod.z.string().describe("MP customer id (from create_customer / find_customer_by_email)"),
1270
+ card_id: zod.z.string().describe("Saved card id (from list_customer_cards)"),
1271
+ security_code: zod.z.string().regex(/^\d{3,4}$/).describe("CVV \u2014 3 digits (Visa/Master) or 4 (Amex). User must provide this each charge in AR."),
1272
+ amount_ars: zod.z.number().positive(),
1273
+ description: zod.z.string().min(1).max(255),
1274
+ installments: zod.z.number().int().min(1).max(24).optional().describe("Default 1. Use calculate_installments first to pick a valid count."),
1275
+ external_reference: zod.z.string().optional(),
1276
+ statement_descriptor: zod.z.string().max(13).optional()
1277
+ }),
1278
+ execute: async (input) => {
1279
+ const payment = await client.chargeSavedCard({
1280
+ customerId: input.customer_id,
1281
+ cardId: input.card_id,
1282
+ securityCode: input.security_code,
1283
+ amount: input.amount_ars,
1284
+ description: input.description,
1285
+ ...input.installments !== void 0 ? { installments: input.installments } : {},
1286
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
1287
+ ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
1288
+ idempotencyKey: deterministicIdempotencyKey(
1289
+ "charge_saved_card",
1290
+ input.card_id,
1291
+ input.amount_ars,
1292
+ input.external_reference
1293
+ )
1294
+ });
1295
+ return {
1296
+ payment_id: payment.id,
1297
+ status: payment.status,
1298
+ status_detail: payment.status_detail,
1299
+ amount: payment.transaction_amount,
1300
+ installments: payment.installments,
1301
+ payment_method: payment.payment_method_id,
1302
+ customer_id: input.customer_id,
1303
+ card_id: input.card_id,
1304
+ external_reference: payment.external_reference,
1305
+ date_approved: payment.date_approved
1306
+ };
1307
+ }
1308
+ }),
1309
+ // ─────────────────────────────────────────────────────────────────────────
1310
+ // QR in-store (v0.3)
1311
+ // ─────────────────────────────────────────────────────────────────────────
1312
+ create_qr_payment: ai.tool({
1313
+ description: desc("create_qr_payment"),
1314
+ inputSchema: zod.z.object({
1315
+ external_pos_id: zod.z.string().describe("Pre-configured POS external_id from MP dashboard. Required."),
1316
+ amount_ars: zod.z.number().positive(),
1317
+ title: zod.z.string().min(1).max(80).describe("Display title shown when scanning"),
1318
+ description: zod.z.string().max(255).optional(),
1319
+ external_reference: zod.z.string().optional(),
1320
+ notification_url: zod.z.string().url().optional().describe("Webhook URL \u2014 falls back to dashboard config if omitted"),
1321
+ expires_in_seconds: zod.z.number().int().min(60).max(3600).optional().describe("Default 600 (10 min)")
1322
+ }),
1323
+ execute: async (input) => {
1324
+ const QRCode = (await import('qrcode')).default;
1325
+ const me = await client.getMe();
1326
+ const userId = String(me.id);
1327
+ const expiresAt = new Date(
1328
+ Date.now() + (input.expires_in_seconds ?? 600) * 1e3
1329
+ ).toISOString();
1330
+ const qr = await client.createQrPayment(userId, {
1331
+ externalPosId: input.external_pos_id,
1332
+ totalAmount: input.amount_ars,
1333
+ title: input.title,
1334
+ ...input.description !== void 0 ? { description: input.description } : {},
1335
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
1336
+ ...input.notification_url !== void 0 ? { notificationUrl: input.notification_url } : {},
1337
+ expirationDate: expiresAt
1338
+ });
1339
+ const qrDataUrl = await QRCode.toDataURL(qr.qr_data, {
1340
+ errorCorrectionLevel: "M",
1341
+ margin: 1,
1342
+ width: 512
1343
+ });
1344
+ return {
1345
+ in_store_order_id: qr.in_store_order_id,
1346
+ qr_data: qr.qr_data,
1347
+ qr_data_url: qrDataUrl,
1348
+ expires_at: expiresAt,
1349
+ external_pos_id: input.external_pos_id,
1350
+ amount: input.amount_ars,
1351
+ next_step: "Display the qr_data_url image to the buyer. Wait for the payment webhook (point_integration_wh fires first, then payment topic). If buyer doesn't scan in time, call cancel_qr_payment to free the POS."
1352
+ };
1353
+ }
1354
+ }),
1355
+ cancel_qr_payment: ai.tool({
1356
+ description: desc("cancel_qr_payment"),
1357
+ inputSchema: zod.z.object({
1358
+ external_pos_id: zod.z.string()
1359
+ }),
1360
+ execute: async ({ external_pos_id }) => {
1361
+ const me = await client.getMe();
1362
+ await client.cancelQrPayment(String(me.id), external_pos_id);
1363
+ return { external_pos_id, cancelled: true };
1364
+ }
370
1365
  })
371
1366
  };
372
1367
  }
@@ -431,6 +1426,162 @@ var WebhookBodySchema = zod.z.object({
431
1426
  id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
432
1427
  live_mode: zod.z.boolean().optional()
433
1428
  }).passthrough();
1429
+ var PaymentStatusSchema = zod.z.union([
1430
+ zod.z.literal("pending"),
1431
+ zod.z.literal("approved"),
1432
+ zod.z.literal("authorized"),
1433
+ zod.z.literal("in_process"),
1434
+ zod.z.literal("in_mediation"),
1435
+ zod.z.literal("rejected"),
1436
+ zod.z.literal("cancelled"),
1437
+ zod.z.literal("refunded"),
1438
+ zod.z.literal("charged_back"),
1439
+ zod.z.string()
1440
+ ]);
1441
+ var PaymentSchema = zod.z.object({
1442
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1443
+ status: PaymentStatusSchema,
1444
+ status_detail: zod.z.string().nullable().optional(),
1445
+ date_created: zod.z.string().nullable().optional(),
1446
+ date_approved: zod.z.string().nullable().optional(),
1447
+ date_last_updated: zod.z.string().nullable().optional(),
1448
+ transaction_amount: zod.z.number(),
1449
+ currency_id: zod.z.string(),
1450
+ installments: zod.z.number().int().nullable().optional(),
1451
+ payment_method_id: zod.z.string().nullable().optional(),
1452
+ payment_type_id: zod.z.string().nullable().optional(),
1453
+ external_reference: zod.z.string().nullable().optional(),
1454
+ description: zod.z.string().nullable().optional(),
1455
+ payer: zod.z.object({
1456
+ id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
1457
+ email: zod.z.string().nullable().optional(),
1458
+ identification: zod.z.object({
1459
+ type: zod.z.string().nullable().optional(),
1460
+ number: zod.z.string().nullable().optional()
1461
+ }).nullable().optional()
1462
+ }).passthrough().optional(),
1463
+ transaction_details: zod.z.object({
1464
+ net_received_amount: zod.z.number().nullable().optional(),
1465
+ total_paid_amount: zod.z.number().nullable().optional(),
1466
+ installment_amount: zod.z.number().nullable().optional()
1467
+ }).passthrough().optional()
1468
+ }).passthrough();
1469
+ zod.z.object({
1470
+ paging: zod.z.object({
1471
+ total: zod.z.number(),
1472
+ limit: zod.z.number(),
1473
+ offset: zod.z.number()
1474
+ }),
1475
+ results: zod.z.array(PaymentSchema)
1476
+ });
1477
+ zod.z.object({
1478
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1479
+ payment_id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1480
+ amount: zod.z.number(),
1481
+ source: zod.z.object({
1482
+ id: zod.z.string().nullable().optional(),
1483
+ name: zod.z.string().nullable().optional(),
1484
+ type: zod.z.string().nullable().optional()
1485
+ }).nullable().optional(),
1486
+ date_created: zod.z.string().nullable().optional(),
1487
+ status: zod.z.string().nullable().optional()
1488
+ }).passthrough();
1489
+ var PreferenceItemSchema = zod.z.object({
1490
+ id: zod.z.string().optional(),
1491
+ title: zod.z.string(),
1492
+ description: zod.z.string().optional(),
1493
+ picture_url: zod.z.string().url().optional(),
1494
+ category_id: zod.z.string().optional(),
1495
+ quantity: zod.z.number().int().positive(),
1496
+ unit_price: zod.z.number().positive(),
1497
+ currency_id: CurrencyIdSchema.optional()
1498
+ });
1499
+ zod.z.object({
1500
+ id: zod.z.string(),
1501
+ init_point: zod.z.string().url().optional(),
1502
+ sandbox_init_point: zod.z.string().url().optional(),
1503
+ client_id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
1504
+ collector_id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
1505
+ items: zod.z.array(PreferenceItemSchema).optional(),
1506
+ external_reference: zod.z.string().nullable().optional(),
1507
+ date_created: zod.z.string().nullable().optional(),
1508
+ expires: zod.z.boolean().optional(),
1509
+ expiration_date_from: zod.z.string().nullable().optional(),
1510
+ expiration_date_to: zod.z.string().nullable().optional()
1511
+ }).passthrough();
1512
+ zod.z.object({
1513
+ id: zod.z.string(),
1514
+ email: zod.z.string(),
1515
+ first_name: zod.z.string().nullable().optional(),
1516
+ last_name: zod.z.string().nullable().optional(),
1517
+ phone: zod.z.object({ area_code: zod.z.string().nullable().optional(), number: zod.z.string().nullable().optional() }).nullable().optional(),
1518
+ identification: zod.z.object({ type: zod.z.string().nullable().optional(), number: zod.z.string().nullable().optional() }).nullable().optional(),
1519
+ date_created: zod.z.string().nullable().optional(),
1520
+ date_last_updated: zod.z.string().nullable().optional(),
1521
+ description: zod.z.string().nullable().optional()
1522
+ }).passthrough();
1523
+ zod.z.object({
1524
+ id: zod.z.string(),
1525
+ customer_id: zod.z.string(),
1526
+ expiration_month: zod.z.number().int().nullable().optional(),
1527
+ expiration_year: zod.z.number().int().nullable().optional(),
1528
+ first_six_digits: zod.z.string().nullable().optional(),
1529
+ last_four_digits: zod.z.string().nullable().optional(),
1530
+ payment_method: zod.z.object({
1531
+ id: zod.z.string().nullable().optional(),
1532
+ name: zod.z.string().nullable().optional(),
1533
+ payment_type_id: zod.z.string().nullable().optional()
1534
+ }).nullable().optional(),
1535
+ date_created: zod.z.string().nullable().optional()
1536
+ }).passthrough();
1537
+ zod.z.object({
1538
+ id: zod.z.string(),
1539
+ name: zod.z.string(),
1540
+ payment_type_id: zod.z.string(),
1541
+ status: zod.z.string(),
1542
+ thumbnail: zod.z.string().nullable().optional(),
1543
+ secure_thumbnail: zod.z.string().nullable().optional(),
1544
+ min_allowed_amount: zod.z.number().nullable().optional(),
1545
+ max_allowed_amount: zod.z.number().nullable().optional()
1546
+ }).passthrough();
1547
+ zod.z.object({
1548
+ payment_method_id: zod.z.string(),
1549
+ payment_type_id: zod.z.string(),
1550
+ issuer: zod.z.object({
1551
+ id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
1552
+ name: zod.z.string().nullable().optional()
1553
+ }).nullable().optional(),
1554
+ payer_costs: zod.z.array(
1555
+ zod.z.object({
1556
+ installments: zod.z.number().int(),
1557
+ installment_rate: zod.z.number(),
1558
+ discount_rate: zod.z.number().nullable().optional(),
1559
+ installment_amount: zod.z.number(),
1560
+ total_amount: zod.z.number(),
1561
+ recommended_message: zod.z.string().nullable().optional()
1562
+ }).passthrough()
1563
+ )
1564
+ }).passthrough();
1565
+ zod.z.object({
1566
+ in_store_order_id: zod.z.string(),
1567
+ qr_data: zod.z.string()
1568
+ }).passthrough();
1569
+ zod.z.object({
1570
+ id: zod.z.string(),
1571
+ status: zod.z.string().optional(),
1572
+ date_due: zod.z.string().optional(),
1573
+ card_id: zod.z.string().optional(),
1574
+ cardholder: zod.z.unknown().optional()
1575
+ }).passthrough();
1576
+ zod.z.object({
1577
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1578
+ email: zod.z.string().nullable().optional(),
1579
+ nickname: zod.z.string().nullable().optional(),
1580
+ country_id: zod.z.string().nullable().optional(),
1581
+ site_id: zod.z.string().nullable().optional(),
1582
+ user_type: zod.z.string().nullable().optional(),
1583
+ status: zod.z.object({ user_type: zod.z.string().nullable().optional() }).passthrough().nullable().optional()
1584
+ }).passthrough();
434
1585
 
435
1586
  // src/webhook.ts
436
1587
  function parseWebhookEvent(body, searchParams) {
@@ -469,9 +1620,11 @@ exports.MercadoPagoAuthorizeForbiddenError = MercadoPagoAuthorizeForbiddenError;
469
1620
  exports.MercadoPagoBackUrlInvalidError = MercadoPagoBackUrlInvalidError;
470
1621
  exports.MercadoPagoClient = MercadoPagoClient;
471
1622
  exports.MercadoPagoError = MercadoPagoError;
1623
+ exports.MercadoPagoOverloadedError = MercadoPagoOverloadedError;
472
1624
  exports.MercadoPagoPaymentRejectedError = MercadoPagoPaymentRejectedError;
473
1625
  exports.MercadoPagoRateLimitError = MercadoPagoRateLimitError;
474
1626
  exports.MercadoPagoSelfPaymentError = MercadoPagoSelfPaymentError;
1627
+ exports.MercadoPagoTimeoutError = MercadoPagoTimeoutError;
475
1628
  exports.classifyError = classifyError;
476
1629
  exports.mercadoPagoTools = mercadoPagoTools;
477
1630
  exports.parseWebhookEvent = parseWebhookEvent;