@ar-agents/mercadopago 0.2.0 → 0.4.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,6 +176,9 @@ 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
183
  async request(method, path, body, options) {
149
184
  const headers = {
@@ -153,10 +188,6 @@ var MercadoPagoClient = class {
153
188
  if (options?.idempotencyKey) {
154
189
  headers["X-Idempotency-Key"] = options.idempotencyKey;
155
190
  }
156
- const init = { method, headers };
157
- if (body !== void 0) {
158
- init.body = JSON.stringify(body);
159
- }
160
191
  let url = `${this.baseUrl}${path}`;
161
192
  if (options?.query) {
162
193
  const search = new URLSearchParams();
@@ -169,20 +200,95 @@ var MercadoPagoClient = class {
169
200
  if (qs) url += `?${qs}`;
170
201
  }
171
202
  const fetchFn = this.fetchImpl ?? globalThis.fetch;
172
- const res = await fetchFn(url, init);
173
- if (!res.ok) {
174
- let parsed;
175
- const text2 = 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);
176
212
  try {
177
- parsed = JSON.parse(text2);
178
- } catch {
179
- parsed = text2;
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;
180
289
  }
181
- throw classifyError(res.status, path, parsed, options?.classifyContext);
182
290
  }
183
- const text = await res.text();
184
- if (!text) return void 0;
185
- return JSON.parse(text);
291
+ throw lastError ?? new Error(`MercadoPago request failed after ${this.maxRetries} retries`);
186
292
  }
187
293
  // ───────────────────────────────────────────────────────────────────────────
188
294
  // Subscriptions (Preapprovals) — v0.1 surface, kept stable
@@ -500,7 +606,301 @@ var MercadoPagoClient = class {
500
606
  async getMe() {
501
607
  return this.request("GET", "/users/me");
502
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
+ ];
701
+ return this.request(
702
+ "PUT",
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`
715
+ );
716
+ }
717
+ // ───────────────────────────────────────────────────────────────────────────
718
+ // Subscription Plans (preapproval_plan — reusable plans, v0.4)
719
+ // ───────────────────────────────────────────────────────────────────────────
720
+ /**
721
+ * Create a reusable subscription plan. Customers later subscribe to it via
722
+ * `subscribeToPlan` (which creates a preapproval pointing at the plan).
723
+ *
724
+ * Use this when you have fixed tiers (Básico/Pro/Enterprise). For custom
725
+ * per-customer amounts, skip plans and use `createPreapproval` directly.
726
+ */
727
+ async createSubscriptionPlan(params) {
728
+ const body = {
729
+ reason: params.reason,
730
+ back_url: params.backUrl,
731
+ auto_recurring: {
732
+ frequency: params.frequency,
733
+ frequency_type: params.frequencyType,
734
+ transaction_amount: params.amount,
735
+ currency_id: params.currency,
736
+ ...params.freeTrialFrequency !== void 0 && params.freeTrialFrequencyType !== void 0 ? {
737
+ free_trial: {
738
+ frequency: params.freeTrialFrequency,
739
+ frequency_type: params.freeTrialFrequencyType
740
+ }
741
+ } : {}
742
+ }
743
+ };
744
+ if (params.externalReference) body.external_reference = params.externalReference;
745
+ return this.request("POST", "/preapproval_plan", body);
746
+ }
747
+ async getSubscriptionPlan(id) {
748
+ return this.request("GET", `/preapproval_plan/${id}`);
749
+ }
750
+ async listSubscriptionPlans(params = {}) {
751
+ const query = {
752
+ limit: params.limit ?? 30,
753
+ offset: params.offset ?? 0
754
+ };
755
+ if (params.status) query["status"] = params.status;
756
+ return this.request("GET", "/preapproval_plan/search", void 0, { query });
757
+ }
758
+ async updateSubscriptionPlan(id, patch) {
759
+ const body = {};
760
+ if (patch.reason !== void 0) body.reason = patch.reason;
761
+ if (patch.status !== void 0) body.status = patch.status;
762
+ if (patch.backUrl !== void 0) body.back_url = patch.backUrl;
763
+ if (patch.amount !== void 0) {
764
+ body.auto_recurring = { transaction_amount: patch.amount };
765
+ }
766
+ return this.request("PUT", `/preapproval_plan/${id}`, body);
767
+ }
768
+ /**
769
+ * Subscribe a customer to an existing plan. Returns a Preapproval with
770
+ * `init_point` URL where the buyer completes the first payment.
771
+ */
772
+ async subscribeToPlan(params) {
773
+ const body = {
774
+ preapproval_plan_id: params.planId,
775
+ payer_email: params.payerEmail
776
+ };
777
+ if (params.cardTokenId) body.card_token_id = params.cardTokenId;
778
+ if (params.externalReference) body.external_reference = params.externalReference;
779
+ return this.request("POST", "/preapproval", body, {
780
+ classifyContext: { payerEmail: params.payerEmail }
781
+ });
782
+ }
783
+ /**
784
+ * List the auto-charge attempts (authorized_payments) under a preapproval.
785
+ * Useful for "show me the cobros of the last 6 months for this client".
786
+ */
787
+ async listSubscriptionPayments(preapprovalId, params = {}) {
788
+ const query = {
789
+ preapproval_id: preapprovalId,
790
+ limit: params.limit ?? 30,
791
+ offset: params.offset ?? 0
792
+ };
793
+ return this.request(
794
+ "GET",
795
+ `/authorized_payments/search`,
796
+ void 0,
797
+ { query, classifyContext: { preapprovalId } }
798
+ );
799
+ }
800
+ // ───────────────────────────────────────────────────────────────────────────
801
+ // Stores + POS (for QR payments self-serve setup, v0.4)
802
+ // ───────────────────────────────────────────────────────────────────────────
803
+ /** Create a store for the seller. POSes (for QR) live under stores. */
804
+ async createStore(userId, params) {
805
+ const body = {
806
+ name: params.name,
807
+ external_id: params.externalId
808
+ };
809
+ if (params.location) {
810
+ body.location = {
811
+ ...params.location.addressLine ? { address_line: params.location.addressLine } : {},
812
+ ...params.location.cityName ? { city_name: params.location.cityName } : {},
813
+ ...params.location.stateName ? { state_name: params.location.stateName } : {},
814
+ ...params.location.countryId ? { country_id: params.location.countryId } : {},
815
+ ...params.location.latitude !== void 0 ? { latitude: params.location.latitude } : {},
816
+ ...params.location.longitude !== void 0 ? { longitude: params.location.longitude } : {}
817
+ };
818
+ }
819
+ return this.request("POST", `/users/${encodeURIComponent(userId)}/stores`, body);
820
+ }
821
+ async listStores(userId, params = {}) {
822
+ const query = {
823
+ limit: params.limit ?? 50,
824
+ offset: params.offset ?? 0
825
+ };
826
+ return this.request("GET", `/users/${encodeURIComponent(userId)}/stores/search`, void 0, { query });
827
+ }
828
+ /** Create a POS under a store. The POS's `external_id` is what `createQrPayment` uses. */
829
+ async createPos(params) {
830
+ const body = {
831
+ name: params.name,
832
+ external_id: params.externalId,
833
+ store_id: params.storeId,
834
+ category: params.category ?? 621102
835
+ // "Other Food and Beverage Services" — generic default
836
+ };
837
+ if (params.fixedAmount !== void 0) body.fixed_amount = params.fixedAmount;
838
+ return this.request("POST", "/pos", body);
839
+ }
840
+ async listPos(params = {}) {
841
+ const query = {
842
+ limit: params.limit ?? 50,
843
+ offset: params.offset ?? 0
844
+ };
845
+ if (params.storeId !== void 0) query["store_id"] = String(params.storeId);
846
+ return this.request("GET", "/pos", void 0, { query });
847
+ }
848
+ // ───────────────────────────────────────────────────────────────────────────
849
+ // Disputes (read-only, v0.4)
850
+ // ───────────────────────────────────────────────────────────────────────────
851
+ async listPaymentDisputes(paymentId) {
852
+ return this.request("GET", `/v1/payments/${paymentId}/disputes`, void 0, {
853
+ classifyContext: { paymentId }
854
+ });
855
+ }
856
+ async getDispute(paymentId, disputeId) {
857
+ return this.request(
858
+ "GET",
859
+ `/v1/payments/${paymentId}/disputes/${disputeId}`,
860
+ void 0,
861
+ { classifyContext: { paymentId } }
862
+ );
863
+ }
864
+ // ───────────────────────────────────────────────────────────────────────────
865
+ // Identification Types + Issuers (lookup helpers, v0.4)
866
+ // ───────────────────────────────────────────────────────────────────────────
867
+ /** List valid identification types for the seller's site. AR returns DNI/CI/LE/LC/Otro/Pasaporte/CUIT/CUIL. */
868
+ async listIdentificationTypes() {
869
+ return this.request("GET", "/v1/identification_types");
870
+ }
871
+ /** List card issuers for a payment method. Useful with `bin` for installments. */
872
+ async listIssuers(params) {
873
+ const query = {
874
+ payment_method_id: params.paymentMethodId
875
+ };
876
+ if (params.bin) query["bin"] = params.bin;
877
+ return this.request("GET", "/v1/payment_methods/card_issuers", void 0, { query });
878
+ }
879
+ // ───────────────────────────────────────────────────────────────────────────
880
+ // Webhooks management (v0.4)
881
+ // ───────────────────────────────────────────────────────────────────────────
882
+ /** List configured webhook subscriptions. */
883
+ async listWebhooks() {
884
+ return this.request("GET", "/v1/webhooks");
885
+ }
886
+ /** Create a webhook subscription for a topic. */
887
+ async createWebhook(params) {
888
+ return this.request("POST", "/v1/webhooks", {
889
+ url: params.url,
890
+ topic: params.topic
891
+ });
892
+ }
893
+ async updateWebhook(id, patch) {
894
+ return this.request("PUT", `/v1/webhooks/${id}`, patch);
895
+ }
896
+ async deleteWebhook(id) {
897
+ await this.request("DELETE", `/v1/webhooks/${id}`);
898
+ }
503
899
  };
900
+ function deterministicIdempotencyKey(...parts) {
901
+ const payload = parts.filter((p) => p !== void 0 && p !== null).map(String).join("|");
902
+ return crypto.createHash("sha256").update(payload).digest("hex").slice(0, 32);
903
+ }
504
904
  var DEFAULT_DESCRIPTIONS = {
505
905
  // ── Subscriptions ────────────────────────────────────────────────────────
506
906
  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.",
@@ -529,7 +929,34 @@ var DEFAULT_DESCRIPTIONS = {
529
929
  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.",
530
930
  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.",
531
931
  // ── Account ──────────────────────────────────────────────────────────────
532
- 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."
932
+ 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.",
933
+ // ── Saved-card charging (v0.3) ───────────────────────────────────────────
934
+ 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.",
935
+ // ── QR in-store (v0.3) ───────────────────────────────────────────────────
936
+ 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 (use create_pos to set one up first if needed). 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.",
937
+ 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.",
938
+ // ── Subscription Plans (v0.4) ────────────────────────────────────────────
939
+ create_subscription_plan: "Create a REUSABLE subscription plan (preapproval_plan). Different from create_subscription: a plan defines price + frequency once, then customers subscribe to it via subscribe_to_plan. Use plans for SaaS-style billing (B\xE1sico/Pro/Enterprise tiers). For per-customer custom amounts, use create_subscription directly.",
940
+ list_subscription_plans: "List all subscription plans defined for this MP account. Useful before create_subscription_plan to check if one already exists, or for surfacing options to a customer.",
941
+ update_subscription_plan: "Update a subscription plan's reason / amount / status / back_url. Existing customer subscriptions to the plan are NOT automatically updated \u2014 only NEW subscribers get the new pricing.",
942
+ subscribe_to_plan: "Subscribe a customer to an existing reusable plan. Returns a Preapproval with init_point URL where the customer completes first payment. Cleaner than create_subscription when you have fixed tiers.",
943
+ list_subscription_payments: "List the auto-charge attempts (authorized_payments) under a subscription. Useful for 'show me the cobros del \xFAltimo mes for this client' or to debug a failing recurring charge.",
944
+ // ── Stores + POS (v0.4) ──────────────────────────────────────────────────
945
+ create_store: "Create a store under the seller's MP account. Stores are the parent entity for POSes (which generate QR payments). Required ONE-TIME setup before create_pos. Pass a unique external_id and a display name.",
946
+ list_stores: "List all stores configured for this MP account. Use this to find an existing store_id before create_pos, or to surface store options to the agent.",
947
+ create_pos: "Create a POS (Point of Sale) under a store. The POS's external_id is what create_qr_payment uses. Each physical checkout / counter / agent typically has its own POS. Categories are MP-defined (default 621102 = Other Food and Beverage Services).",
948
+ list_pos: "List all POSes for the seller (or filtered by store_id). Use to find an existing POS before create_qr_payment, or to surface options.",
949
+ // ── Disputes (v0.4 — read-only) ──────────────────────────────────────────
950
+ list_payment_disputes: "List all disputes / chargebacks raised against a payment. Read-only \u2014 resolution is dashboard-only. Surface the dashboard URL `https://www.mercadopago.com.ar/disputes/{dispute_id}` to the user when they need to respond.",
951
+ get_dispute: "Get details of a specific dispute including reason, amount, resolution status. Read-only.",
952
+ // ── Lookup helpers (v0.4) ────────────────────────────────────────────────
953
+ list_identification_types: "List valid identification types for the seller's site. AR returns: DNI, CI, LE, LC, Otro, Pasaporte, CUIT, CUIL with their min/max length. Useful to validate an identification before passing to create_payment.",
954
+ list_issuers: "List card issuers (banks) that support a payment_method_id. Optionally filter by `bin` (first 6 digits of the card) for accurate issuer detection. Useful with calculate_installments \u2014 issuer-specific promos (e.g., Naranja Galicia 6 cuotas sin inter\xE9s) only appear when the issuer is identified.",
955
+ // ── Webhooks management (v0.4) ───────────────────────────────────────────
956
+ list_webhooks: "List all webhook subscriptions configured for this MP application. Use to see what topics + URLs are wired before adding new ones.",
957
+ create_webhook: "Subscribe a webhook URL to a MP topic (payment, subscription_authorized_payment, subscription_preapproval, merchant_order, point_integration_wh). MP will POST to this URL when events of that topic fire.",
958
+ update_webhook: "Update a webhook's URL or topic. Useful when you change deployment URLs without resubscribing from scratch.",
959
+ delete_webhook: "Delete a webhook subscription. MP stops POSTing to it immediately."
533
960
  };
534
961
  function mercadoPagoTools(client, options) {
535
962
  const desc = (name) => options.descriptions?.[name] ?? DEFAULT_DESCRIPTIONS[name];
@@ -671,8 +1098,15 @@ function mercadoPagoTools(client, options) {
671
1098
  ...input.identification !== void 0 ? { identification: input.identification } : {},
672
1099
  ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
673
1100
  ...options.notificationUrl !== void 0 ? { notificationUrl: options.notificationUrl } : {},
674
- // Auto-generate idempotency key from caller-meaningful fields
675
- idempotencyKey: `${input.external_reference ?? input.payer_email}-${input.amount_ars}-${Date.now()}`
1101
+ // Deterministic idempotency key safe to retry, same inputs always
1102
+ // produce the same key (MP dedupes on its side).
1103
+ idempotencyKey: deterministicIdempotencyKey(
1104
+ "create_payment",
1105
+ input.external_reference ?? input.payer_email,
1106
+ input.amount_ars,
1107
+ input.payment_method_id,
1108
+ input.token
1109
+ )
676
1110
  });
677
1111
  return {
678
1112
  payment_id: payment.id,
@@ -789,7 +1223,7 @@ function mercadoPagoTools(client, options) {
789
1223
  const refund = await client.createRefund({
790
1224
  paymentId: payment_id,
791
1225
  ...amount_ars !== void 0 ? { amount: amount_ars } : {},
792
- idempotencyKey: `refund-${payment_id}-${amount_ars ?? "full"}`
1226
+ idempotencyKey: deterministicIdempotencyKey("refund", payment_id, amount_ars ?? "full")
793
1227
  });
794
1228
  return {
795
1229
  refund_id: refund.id,
@@ -1029,6 +1463,493 @@ function mercadoPagoTools(client, options) {
1029
1463
  user_type: me.user_type
1030
1464
  };
1031
1465
  }
1466
+ }),
1467
+ // ─────────────────────────────────────────────────────────────────────────
1468
+ // Saved-card charging (v0.3)
1469
+ // ─────────────────────────────────────────────────────────────────────────
1470
+ charge_saved_card: ai.tool({
1471
+ description: desc("charge_saved_card"),
1472
+ inputSchema: zod.z.object({
1473
+ customer_id: zod.z.string().describe("MP customer id (from create_customer / find_customer_by_email)"),
1474
+ card_id: zod.z.string().describe("Saved card id (from list_customer_cards)"),
1475
+ 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."),
1476
+ amount_ars: zod.z.number().positive(),
1477
+ description: zod.z.string().min(1).max(255),
1478
+ installments: zod.z.number().int().min(1).max(24).optional().describe("Default 1. Use calculate_installments first to pick a valid count."),
1479
+ external_reference: zod.z.string().optional(),
1480
+ statement_descriptor: zod.z.string().max(13).optional()
1481
+ }),
1482
+ execute: async (input) => {
1483
+ const payment = await client.chargeSavedCard({
1484
+ customerId: input.customer_id,
1485
+ cardId: input.card_id,
1486
+ securityCode: input.security_code,
1487
+ amount: input.amount_ars,
1488
+ description: input.description,
1489
+ ...input.installments !== void 0 ? { installments: input.installments } : {},
1490
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
1491
+ ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
1492
+ idempotencyKey: deterministicIdempotencyKey(
1493
+ "charge_saved_card",
1494
+ input.card_id,
1495
+ input.amount_ars,
1496
+ input.external_reference
1497
+ )
1498
+ });
1499
+ return {
1500
+ payment_id: payment.id,
1501
+ status: payment.status,
1502
+ status_detail: payment.status_detail,
1503
+ amount: payment.transaction_amount,
1504
+ installments: payment.installments,
1505
+ payment_method: payment.payment_method_id,
1506
+ customer_id: input.customer_id,
1507
+ card_id: input.card_id,
1508
+ external_reference: payment.external_reference,
1509
+ date_approved: payment.date_approved
1510
+ };
1511
+ }
1512
+ }),
1513
+ // ─────────────────────────────────────────────────────────────────────────
1514
+ // QR in-store (v0.3)
1515
+ // ─────────────────────────────────────────────────────────────────────────
1516
+ create_qr_payment: ai.tool({
1517
+ description: desc("create_qr_payment"),
1518
+ inputSchema: zod.z.object({
1519
+ external_pos_id: zod.z.string().describe("Pre-configured POS external_id from MP dashboard. Required."),
1520
+ amount_ars: zod.z.number().positive(),
1521
+ title: zod.z.string().min(1).max(80).describe("Display title shown when scanning"),
1522
+ description: zod.z.string().max(255).optional(),
1523
+ external_reference: zod.z.string().optional(),
1524
+ notification_url: zod.z.string().url().optional().describe("Webhook URL \u2014 falls back to dashboard config if omitted"),
1525
+ expires_in_seconds: zod.z.number().int().min(60).max(3600).optional().describe("Default 600 (10 min)")
1526
+ }),
1527
+ execute: async (input) => {
1528
+ const QRCode = (await import('qrcode')).default;
1529
+ const me = await client.getMe();
1530
+ const userId = String(me.id);
1531
+ const expiresAt = new Date(
1532
+ Date.now() + (input.expires_in_seconds ?? 600) * 1e3
1533
+ ).toISOString();
1534
+ const qr = await client.createQrPayment(userId, {
1535
+ externalPosId: input.external_pos_id,
1536
+ totalAmount: input.amount_ars,
1537
+ title: input.title,
1538
+ ...input.description !== void 0 ? { description: input.description } : {},
1539
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
1540
+ ...input.notification_url !== void 0 ? { notificationUrl: input.notification_url } : {},
1541
+ expirationDate: expiresAt
1542
+ });
1543
+ const qrDataUrl = await QRCode.toDataURL(qr.qr_data, {
1544
+ errorCorrectionLevel: "M",
1545
+ margin: 1,
1546
+ width: 512
1547
+ });
1548
+ return {
1549
+ in_store_order_id: qr.in_store_order_id,
1550
+ qr_data: qr.qr_data,
1551
+ qr_data_url: qrDataUrl,
1552
+ expires_at: expiresAt,
1553
+ external_pos_id: input.external_pos_id,
1554
+ amount: input.amount_ars,
1555
+ 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."
1556
+ };
1557
+ }
1558
+ }),
1559
+ cancel_qr_payment: ai.tool({
1560
+ description: desc("cancel_qr_payment"),
1561
+ inputSchema: zod.z.object({
1562
+ external_pos_id: zod.z.string()
1563
+ }),
1564
+ execute: async ({ external_pos_id }) => {
1565
+ const me = await client.getMe();
1566
+ await client.cancelQrPayment(String(me.id), external_pos_id);
1567
+ return { external_pos_id, cancelled: true };
1568
+ }
1569
+ }),
1570
+ // ─────────────────────────────────────────────────────────────────────────
1571
+ // Subscription Plans (v0.4)
1572
+ // ─────────────────────────────────────────────────────────────────────────
1573
+ create_subscription_plan: ai.tool({
1574
+ description: desc("create_subscription_plan"),
1575
+ inputSchema: zod.z.object({
1576
+ reason: zod.z.string().min(3).max(120).describe("Plan name shown at checkout"),
1577
+ amount_ars: zod.z.number().positive(),
1578
+ frequency_months: zod.z.number().int().min(1).max(12),
1579
+ back_url: zod.z.string().url().describe("HTTPS URL where MP redirects after first payment"),
1580
+ external_reference: zod.z.string().optional(),
1581
+ free_trial_days: zod.z.number().int().min(1).max(60).optional().describe("Free trial period in days before first charge")
1582
+ }),
1583
+ execute: async (input) => {
1584
+ const plan = await client.createSubscriptionPlan({
1585
+ reason: input.reason,
1586
+ amount: input.amount_ars,
1587
+ currency: "ARS",
1588
+ frequency: input.frequency_months,
1589
+ frequencyType: "months",
1590
+ backUrl: input.back_url,
1591
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
1592
+ ...input.free_trial_days !== void 0 ? { freeTrialFrequency: input.free_trial_days, freeTrialFrequencyType: "days" } : {}
1593
+ });
1594
+ return {
1595
+ plan_id: plan.id,
1596
+ status: plan.status,
1597
+ reason: plan.reason,
1598
+ amount: plan.auto_recurring.transaction_amount,
1599
+ currency: plan.auto_recurring.currency_id,
1600
+ frequency: `${plan.auto_recurring.frequency} ${plan.auto_recurring.frequency_type}`,
1601
+ external_reference: plan.external_reference,
1602
+ next_step: "Use subscribe_to_plan to enroll customers in this plan, or share its ID for them to subscribe via your frontend."
1603
+ };
1604
+ }
1605
+ }),
1606
+ list_subscription_plans: ai.tool({
1607
+ description: desc("list_subscription_plans"),
1608
+ inputSchema: zod.z.object({
1609
+ status: zod.z.string().optional(),
1610
+ limit: zod.z.number().int().min(1).max(100).optional()
1611
+ }),
1612
+ execute: async (input) => {
1613
+ const result = await client.listSubscriptionPlans({
1614
+ ...input.status !== void 0 ? { status: input.status } : {},
1615
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
1616
+ });
1617
+ return {
1618
+ total: result.paging.total,
1619
+ plans: result.results.map((p) => ({
1620
+ plan_id: p.id,
1621
+ reason: p.reason,
1622
+ status: p.status,
1623
+ amount: p.auto_recurring.transaction_amount,
1624
+ currency: p.auto_recurring.currency_id,
1625
+ frequency: `${p.auto_recurring.frequency} ${p.auto_recurring.frequency_type}`
1626
+ }))
1627
+ };
1628
+ }
1629
+ }),
1630
+ update_subscription_plan: ai.tool({
1631
+ description: desc("update_subscription_plan"),
1632
+ inputSchema: zod.z.object({
1633
+ plan_id: zod.z.string(),
1634
+ reason: zod.z.string().optional(),
1635
+ amount_ars: zod.z.number().positive().optional(),
1636
+ status: zod.z.enum(["active", "cancelled"]).optional(),
1637
+ back_url: zod.z.string().url().optional()
1638
+ }),
1639
+ execute: async (input) => {
1640
+ const updated = await client.updateSubscriptionPlan(input.plan_id, {
1641
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
1642
+ ...input.amount_ars !== void 0 ? { amount: input.amount_ars } : {},
1643
+ ...input.status !== void 0 ? { status: input.status } : {},
1644
+ ...input.back_url !== void 0 ? { backUrl: input.back_url } : {}
1645
+ });
1646
+ return {
1647
+ plan_id: updated.id,
1648
+ status: updated.status,
1649
+ reason: updated.reason,
1650
+ amount: updated.auto_recurring.transaction_amount,
1651
+ message: input.amount_ars !== void 0 ? "Updated. Existing subscribers keep their old amount; only NEW subscribers get the new pricing." : "Plan updated."
1652
+ };
1653
+ }
1654
+ }),
1655
+ subscribe_to_plan: ai.tool({
1656
+ description: desc("subscribe_to_plan"),
1657
+ inputSchema: zod.z.object({
1658
+ plan_id: zod.z.string(),
1659
+ customer_email: zod.z.string().email(),
1660
+ external_reference: zod.z.string().optional()
1661
+ }),
1662
+ execute: async (input) => {
1663
+ const sub = await client.subscribeToPlan({
1664
+ planId: input.plan_id,
1665
+ payerEmail: input.customer_email,
1666
+ ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {}
1667
+ });
1668
+ return {
1669
+ subscription_id: sub.id,
1670
+ status: sub.status,
1671
+ payer_email: sub.payer_email,
1672
+ init_point_url: sub.init_point,
1673
+ next_step: "Send init_point_url to the customer for first payment with card+CVV."
1674
+ };
1675
+ }
1676
+ }),
1677
+ list_subscription_payments: ai.tool({
1678
+ description: desc("list_subscription_payments"),
1679
+ inputSchema: zod.z.object({
1680
+ subscription_id: zod.z.string(),
1681
+ limit: zod.z.number().int().min(1).max(100).optional()
1682
+ }),
1683
+ execute: async (input) => {
1684
+ const result = await client.listSubscriptionPayments(input.subscription_id, {
1685
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
1686
+ });
1687
+ return {
1688
+ subscription_id: input.subscription_id,
1689
+ total: result.paging.total,
1690
+ payments: result.results.map((p) => ({
1691
+ authorized_payment_id: p.id,
1692
+ payment_id: p.payment_id ?? null,
1693
+ status: p.status,
1694
+ amount: p.transaction_amount ?? null,
1695
+ currency: p.currency_id ?? null,
1696
+ debit_date: p.debit_date ?? null,
1697
+ next_retry_date: p.next_retry_date ?? null,
1698
+ retry_attempt: p.retry_attempt ?? 0,
1699
+ reason: p.reason ?? null
1700
+ }))
1701
+ };
1702
+ }
1703
+ }),
1704
+ // ─────────────────────────────────────────────────────────────────────────
1705
+ // Stores + POS (v0.4)
1706
+ // ─────────────────────────────────────────────────────────────────────────
1707
+ create_store: ai.tool({
1708
+ description: desc("create_store"),
1709
+ inputSchema: zod.z.object({
1710
+ name: zod.z.string().min(1).max(80),
1711
+ external_id: zod.z.string().min(1).max(64).describe("Unique within the seller's stores"),
1712
+ address_line: zod.z.string().optional(),
1713
+ city_name: zod.z.string().optional(),
1714
+ state_name: zod.z.string().optional()
1715
+ }),
1716
+ execute: async (input) => {
1717
+ const me = await client.getMe();
1718
+ const store = await client.createStore(String(me.id), {
1719
+ name: input.name,
1720
+ externalId: input.external_id,
1721
+ ...input.address_line || input.city_name || input.state_name ? {
1722
+ location: {
1723
+ ...input.address_line ? { addressLine: input.address_line } : {},
1724
+ ...input.city_name ? { cityName: input.city_name } : {},
1725
+ ...input.state_name ? { stateName: input.state_name } : {},
1726
+ countryId: "AR"
1727
+ }
1728
+ } : {}
1729
+ });
1730
+ return {
1731
+ store_id: store.id,
1732
+ name: store.name,
1733
+ external_id: store.external_id,
1734
+ next_step: "Use create_pos with this store_id to add a Point of Sale where create_qr_payment can issue QRs."
1735
+ };
1736
+ }
1737
+ }),
1738
+ list_stores: ai.tool({
1739
+ description: desc("list_stores"),
1740
+ inputSchema: zod.z.object({
1741
+ limit: zod.z.number().int().min(1).max(100).optional()
1742
+ }),
1743
+ execute: async (input) => {
1744
+ const me = await client.getMe();
1745
+ const result = await client.listStores(String(me.id), {
1746
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
1747
+ });
1748
+ return {
1749
+ total: result.paging.total,
1750
+ stores: result.results.map((s) => ({
1751
+ store_id: s.id,
1752
+ name: s.name ?? null,
1753
+ external_id: s.external_id ?? null
1754
+ }))
1755
+ };
1756
+ }
1757
+ }),
1758
+ create_pos: ai.tool({
1759
+ description: desc("create_pos"),
1760
+ inputSchema: zod.z.object({
1761
+ name: zod.z.string().min(1).max(80),
1762
+ external_id: zod.z.string().min(1).max(64).describe("Unique within the store. This is what create_qr_payment uses."),
1763
+ store_id: zod.z.string().describe("From create_store / list_stores"),
1764
+ category: zod.z.number().int().optional().describe("MP category code, default 621102 (other food/beverage)"),
1765
+ fixed_amount: zod.z.boolean().optional().describe("True for static QR with fixed amount; false (default) for dynamic per-order QR")
1766
+ }),
1767
+ execute: async (input) => {
1768
+ const pos = await client.createPos({
1769
+ name: input.name,
1770
+ externalId: input.external_id,
1771
+ storeId: input.store_id,
1772
+ ...input.category !== void 0 ? { category: input.category } : {},
1773
+ ...input.fixed_amount !== void 0 ? { fixedAmount: input.fixed_amount } : {}
1774
+ });
1775
+ return {
1776
+ pos_id: pos.id,
1777
+ external_id: pos.external_id,
1778
+ store_id: pos.store_id,
1779
+ name: pos.name,
1780
+ next_step: "Use create_qr_payment with this external_id to start issuing dynamic QRs from this POS."
1781
+ };
1782
+ }
1783
+ }),
1784
+ list_pos: ai.tool({
1785
+ description: desc("list_pos"),
1786
+ inputSchema: zod.z.object({
1787
+ store_id: zod.z.string().optional(),
1788
+ limit: zod.z.number().int().min(1).max(100).optional()
1789
+ }),
1790
+ execute: async (input) => {
1791
+ const result = await client.listPos({
1792
+ ...input.store_id !== void 0 ? { storeId: input.store_id } : {},
1793
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
1794
+ });
1795
+ return {
1796
+ total: result.paging.total,
1797
+ pos: result.results.map((p) => ({
1798
+ pos_id: p.id,
1799
+ external_id: p.external_id ?? null,
1800
+ store_id: p.store_id ?? null,
1801
+ name: p.name ?? null
1802
+ }))
1803
+ };
1804
+ }
1805
+ }),
1806
+ // ─────────────────────────────────────────────────────────────────────────
1807
+ // Disputes (v0.4 — read-only)
1808
+ // ─────────────────────────────────────────────────────────────────────────
1809
+ list_payment_disputes: ai.tool({
1810
+ description: desc("list_payment_disputes"),
1811
+ inputSchema: zod.z.object({ payment_id: zod.z.string() }),
1812
+ execute: async ({ payment_id }) => {
1813
+ const disputes = await client.listPaymentDisputes(payment_id);
1814
+ return {
1815
+ payment_id,
1816
+ count: disputes.length,
1817
+ disputes: disputes.map((d) => ({
1818
+ dispute_id: d.id,
1819
+ status: d.status,
1820
+ amount: d.amount ?? null,
1821
+ reason: d.reason ?? null,
1822
+ date_created: d.date_created ?? null,
1823
+ dashboard_url: `https://www.mercadopago.com.ar/disputes/${d.id}`
1824
+ }))
1825
+ };
1826
+ }
1827
+ }),
1828
+ get_dispute: ai.tool({
1829
+ description: desc("get_dispute"),
1830
+ inputSchema: zod.z.object({
1831
+ payment_id: zod.z.string(),
1832
+ dispute_id: zod.z.string()
1833
+ }),
1834
+ execute: async ({ payment_id, dispute_id }) => {
1835
+ const d = await client.getDispute(payment_id, dispute_id);
1836
+ return {
1837
+ dispute_id: d.id,
1838
+ status: d.status,
1839
+ amount: d.amount ?? null,
1840
+ reason: d.reason ?? null,
1841
+ reason_description: d.reason_description ?? null,
1842
+ resolution: d.resolution ?? null,
1843
+ date_created: d.date_created ?? null,
1844
+ dashboard_url: `https://www.mercadopago.com.ar/disputes/${d.id}`
1845
+ };
1846
+ }
1847
+ }),
1848
+ // ─────────────────────────────────────────────────────────────────────────
1849
+ // Lookup helpers (v0.4)
1850
+ // ─────────────────────────────────────────────────────────────────────────
1851
+ list_identification_types: ai.tool({
1852
+ description: desc("list_identification_types"),
1853
+ inputSchema: zod.z.object({}),
1854
+ execute: async () => {
1855
+ const types = await client.listIdentificationTypes();
1856
+ return {
1857
+ count: types.length,
1858
+ types: types.map((t) => ({
1859
+ id: t.id,
1860
+ name: t.name,
1861
+ type: t.type,
1862
+ min_length: t.min_length ?? null,
1863
+ max_length: t.max_length ?? null
1864
+ }))
1865
+ };
1866
+ }
1867
+ }),
1868
+ list_issuers: ai.tool({
1869
+ description: desc("list_issuers"),
1870
+ inputSchema: zod.z.object({
1871
+ payment_method_id: zod.z.string().describe("E.g. 'visa', 'master', 'naranja'"),
1872
+ bin: zod.z.string().min(6).max(8).optional().describe("First 6-8 digits of card for precise issuer detection")
1873
+ }),
1874
+ execute: async (input) => {
1875
+ const issuers = await client.listIssuers({
1876
+ paymentMethodId: input.payment_method_id,
1877
+ ...input.bin !== void 0 ? { bin: input.bin } : {}
1878
+ });
1879
+ return {
1880
+ payment_method_id: input.payment_method_id,
1881
+ count: issuers.length,
1882
+ issuers: issuers.map((i) => ({
1883
+ issuer_id: i.id,
1884
+ name: i.name,
1885
+ status: i.status ?? null
1886
+ }))
1887
+ };
1888
+ }
1889
+ }),
1890
+ // ─────────────────────────────────────────────────────────────────────────
1891
+ // Webhooks management (v0.4)
1892
+ // ─────────────────────────────────────────────────────────────────────────
1893
+ list_webhooks: ai.tool({
1894
+ description: desc("list_webhooks"),
1895
+ inputSchema: zod.z.object({}),
1896
+ execute: async () => {
1897
+ const hooks = await client.listWebhooks();
1898
+ return {
1899
+ count: hooks.length,
1900
+ webhooks: hooks.map((h) => ({
1901
+ webhook_id: h.id,
1902
+ url: h.url ?? null,
1903
+ topic: h.topic ?? null,
1904
+ status: h.status ?? null,
1905
+ date_created: h.date_created ?? null
1906
+ }))
1907
+ };
1908
+ }
1909
+ }),
1910
+ create_webhook: ai.tool({
1911
+ description: desc("create_webhook"),
1912
+ inputSchema: zod.z.object({
1913
+ url: zod.z.string().url(),
1914
+ topic: zod.z.string().describe("E.g. 'payment', 'subscription_authorized_payment', 'subscription_preapproval', 'merchant_order', 'point_integration_wh'")
1915
+ }),
1916
+ execute: async ({ url, topic }) => {
1917
+ const hook = await client.createWebhook({ url, topic });
1918
+ return {
1919
+ webhook_id: hook.id,
1920
+ url: hook.url ?? url,
1921
+ topic: hook.topic ?? topic,
1922
+ status: hook.status ?? null
1923
+ };
1924
+ }
1925
+ }),
1926
+ update_webhook: ai.tool({
1927
+ description: desc("update_webhook"),
1928
+ inputSchema: zod.z.object({
1929
+ webhook_id: zod.z.string(),
1930
+ url: zod.z.string().url().optional(),
1931
+ topic: zod.z.string().optional()
1932
+ }),
1933
+ execute: async (input) => {
1934
+ const hook = await client.updateWebhook(input.webhook_id, {
1935
+ ...input.url !== void 0 ? { url: input.url } : {},
1936
+ ...input.topic !== void 0 ? { topic: input.topic } : {}
1937
+ });
1938
+ return {
1939
+ webhook_id: hook.id,
1940
+ url: hook.url ?? null,
1941
+ topic: hook.topic ?? null,
1942
+ status: hook.status ?? null
1943
+ };
1944
+ }
1945
+ }),
1946
+ delete_webhook: ai.tool({
1947
+ description: desc("delete_webhook"),
1948
+ inputSchema: zod.z.object({ webhook_id: zod.z.string() }),
1949
+ execute: async ({ webhook_id }) => {
1950
+ await client.deleteWebhook(webhook_id);
1951
+ return { webhook_id, deleted: true };
1952
+ }
1032
1953
  })
1033
1954
  };
1034
1955
  }
@@ -1229,6 +2150,17 @@ zod.z.object({
1229
2150
  }).passthrough()
1230
2151
  )
1231
2152
  }).passthrough();
2153
+ zod.z.object({
2154
+ in_store_order_id: zod.z.string(),
2155
+ qr_data: zod.z.string()
2156
+ }).passthrough();
2157
+ zod.z.object({
2158
+ id: zod.z.string(),
2159
+ status: zod.z.string().optional(),
2160
+ date_due: zod.z.string().optional(),
2161
+ card_id: zod.z.string().optional(),
2162
+ cardholder: zod.z.unknown().optional()
2163
+ }).passthrough();
1232
2164
  zod.z.object({
1233
2165
  id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1234
2166
  email: zod.z.string().nullable().optional(),
@@ -1238,6 +2170,105 @@ zod.z.object({
1238
2170
  user_type: zod.z.string().nullable().optional(),
1239
2171
  status: zod.z.object({ user_type: zod.z.string().nullable().optional() }).passthrough().nullable().optional()
1240
2172
  }).passthrough();
2173
+ zod.z.object({
2174
+ id: zod.z.string(),
2175
+ status: zod.z.string(),
2176
+ reason: zod.z.string(),
2177
+ back_url: zod.z.string().url().optional(),
2178
+ external_reference: zod.z.string().nullable().optional(),
2179
+ date_created: zod.z.string(),
2180
+ last_modified: zod.z.string(),
2181
+ auto_recurring: AutoRecurringSchema
2182
+ }).passthrough();
2183
+ zod.z.object({
2184
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
2185
+ name: zod.z.string().optional(),
2186
+ external_id: zod.z.string().optional(),
2187
+ date_creation: zod.z.string().optional(),
2188
+ location: zod.z.object({
2189
+ address_line: zod.z.string().optional(),
2190
+ city_name: zod.z.string().optional(),
2191
+ state_name: zod.z.string().optional(),
2192
+ country_id: zod.z.string().optional(),
2193
+ latitude: zod.z.number().optional(),
2194
+ longitude: zod.z.number().optional()
2195
+ }).passthrough().optional()
2196
+ }).passthrough();
2197
+ zod.z.object({
2198
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
2199
+ name: zod.z.string().optional(),
2200
+ external_id: zod.z.string().optional(),
2201
+ store_id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
2202
+ category: zod.z.number().int().optional(),
2203
+ fixed_amount: zod.z.boolean().optional(),
2204
+ qr: zod.z.object({
2205
+ template_image: zod.z.string().optional(),
2206
+ image: zod.z.string().optional()
2207
+ }).passthrough().optional(),
2208
+ date_creation: zod.z.string().optional()
2209
+ }).passthrough();
2210
+ zod.z.object({
2211
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
2212
+ status: zod.z.string(),
2213
+ resource: zod.z.string().optional(),
2214
+ resource_id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
2215
+ amount: zod.z.number().optional(),
2216
+ date_created: zod.z.string().optional(),
2217
+ reason: zod.z.string().optional(),
2218
+ resolution: zod.z.object({
2219
+ reason: zod.z.string().optional(),
2220
+ result: zod.z.string().optional(),
2221
+ date: zod.z.string().optional()
2222
+ }).passthrough().optional(),
2223
+ /** Documents the buyer / seller submitted as evidence. */
2224
+ documents: zod.z.array(zod.z.unknown()).optional(),
2225
+ /** Buyer's stated complaint. */
2226
+ reason_description: zod.z.string().optional()
2227
+ }).passthrough();
2228
+ zod.z.object({
2229
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
2230
+ preapproval_id: zod.z.string().optional(),
2231
+ status: zod.z.string(),
2232
+ payment_id: zod.z.union([zod.z.string(), zod.z.number()]).nullable().optional(),
2233
+ transaction_amount: zod.z.number().optional(),
2234
+ currency_id: zod.z.string().optional(),
2235
+ date_created: zod.z.string().optional(),
2236
+ debit_date: zod.z.string().optional(),
2237
+ next_retry_date: zod.z.string().nullable().optional(),
2238
+ retry_attempt: zod.z.number().optional(),
2239
+ reason: zod.z.string().optional()
2240
+ }).passthrough();
2241
+ zod.z.object({
2242
+ id: zod.z.string(),
2243
+ name: zod.z.string(),
2244
+ type: zod.z.string(),
2245
+ min_length: zod.z.number().optional(),
2246
+ max_length: zod.z.number().optional()
2247
+ }).passthrough();
2248
+ zod.z.object({
2249
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
2250
+ name: zod.z.string(),
2251
+ secure_thumbnail: zod.z.string().nullable().optional(),
2252
+ thumbnail: zod.z.string().nullable().optional(),
2253
+ processing_mode: zod.z.string().optional(),
2254
+ status: zod.z.string().optional()
2255
+ }).passthrough();
2256
+ zod.z.enum([
2257
+ "payment",
2258
+ "subscription_authorized_payment",
2259
+ "subscription_preapproval",
2260
+ "merchant_order",
2261
+ "point_integration_wh",
2262
+ "stop_delivery_op_wh"
2263
+ ]);
2264
+ zod.z.object({
2265
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
2266
+ url: zod.z.string().url().optional(),
2267
+ status: zod.z.string().optional(),
2268
+ topic: zod.z.string().optional(),
2269
+ date_created: zod.z.string().optional(),
2270
+ date_modified: zod.z.string().optional()
2271
+ }).passthrough();
1241
2272
 
1242
2273
  // src/webhook.ts
1243
2274
  function parseWebhookEvent(body, searchParams) {
@@ -1276,9 +2307,11 @@ exports.MercadoPagoAuthorizeForbiddenError = MercadoPagoAuthorizeForbiddenError;
1276
2307
  exports.MercadoPagoBackUrlInvalidError = MercadoPagoBackUrlInvalidError;
1277
2308
  exports.MercadoPagoClient = MercadoPagoClient;
1278
2309
  exports.MercadoPagoError = MercadoPagoError;
2310
+ exports.MercadoPagoOverloadedError = MercadoPagoOverloadedError;
1279
2311
  exports.MercadoPagoPaymentRejectedError = MercadoPagoPaymentRejectedError;
1280
2312
  exports.MercadoPagoRateLimitError = MercadoPagoRateLimitError;
1281
2313
  exports.MercadoPagoSelfPaymentError = MercadoPagoSelfPaymentError;
2314
+ exports.MercadoPagoTimeoutError = MercadoPagoTimeoutError;
1282
2315
  exports.classifyError = classifyError;
1283
2316
  exports.mercadoPagoTools = mercadoPagoTools;
1284
2317
  exports.parseWebhookEvent = parseWebhookEvent;