@ar-agents/mercadopago 0.6.0 → 0.8.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,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var crypto = require('crypto');
4
3
  var ai = require('ai');
5
4
  var zod = require('zod');
6
5
 
@@ -1025,7 +1024,264 @@ var MercadoPagoClient = class {
1025
1024
  async getSettlement(id) {
1026
1025
  return this.request("GET", `/v1/account/release_money/${id}`);
1027
1026
  }
1027
+ // ──────────────────────────────────────────────────────────────────────────
1028
+ // v0.7 — Customer + Card extensions (close gaps)
1029
+ // ──────────────────────────────────────────────────────────────────────────
1030
+ /**
1031
+ * Update a customer's profile (name, last name, address, etc.). MP merges
1032
+ * the patch — fields you don't send remain unchanged.
1033
+ */
1034
+ async updateCustomer(id, patch) {
1035
+ return this.request("PUT", `/v1/customers/${id}`, patch);
1036
+ }
1037
+ /**
1038
+ * Add a saved card to a customer using a card token (one-time, get from
1039
+ * MP's frontend Cardform). The card is then chargeable with charge_saved_card.
1040
+ */
1041
+ async createCustomerCard(customerId, cardToken) {
1042
+ return this.request(
1043
+ "POST",
1044
+ `/v1/customers/${customerId}/cards`,
1045
+ { token: cardToken }
1046
+ );
1047
+ }
1048
+ // ──────────────────────────────────────────────────────────────────────────
1049
+ // v0.7 — Subscription extensions
1050
+ // ──────────────────────────────────────────────────────────────────────────
1051
+ /**
1052
+ * Update an existing subscription. Common patches:
1053
+ * - `transaction_amount` to change the recurring amount
1054
+ * - `card_token_id` to switch payment method (e.g., expired card)
1055
+ * - `status: "cancelled" | "paused"` (alternative to dedicated cancel/pause endpoints)
1056
+ * - `reason` to update the description shown to the buyer
1057
+ */
1058
+ async updatePreapproval(id, patch) {
1059
+ return this.request("PUT", `/preapproval/${id}`, patch);
1060
+ }
1061
+ /**
1062
+ * Search subscriptions across the seller's account. Common filters:
1063
+ * `status` (pending/authorized/paused/cancelled), `payer_email`,
1064
+ * `external_reference`. Paginated.
1065
+ */
1066
+ async searchPreapprovals(params = {}) {
1067
+ const query = {};
1068
+ if (params.status) query.status = params.status;
1069
+ if (params.payerEmail) query.payer_email = params.payerEmail;
1070
+ if (params.externalReference) query.external_reference = params.externalReference;
1071
+ if (params.preapproval_plan_id) query.preapproval_plan_id = params.preapproval_plan_id;
1072
+ if (params.limit !== void 0) query.limit = params.limit;
1073
+ if (params.offset !== void 0) query.offset = params.offset;
1074
+ const result = await this.request("GET", "/preapproval/search", void 0, { query });
1075
+ return {
1076
+ results: result.results ?? [],
1077
+ paging: result.paging ?? { limit: params.limit ?? 25, offset: params.offset ?? 0, total: 0 }
1078
+ };
1079
+ }
1080
+ // ──────────────────────────────────────────────────────────────────────────
1081
+ // v0.7 — Merchant Orders (parent of Payments grouped under a Preference)
1082
+ // ──────────────────────────────────────────────────────────────────────────
1083
+ /**
1084
+ * Get a merchant_order with all its associated payments + shipments.
1085
+ * Useful for reconciling "which payments belong to which preference"
1086
+ * — typical webhook handler use case.
1087
+ */
1088
+ async getMerchantOrder(id) {
1089
+ return this.request("GET", `/merchant_orders/${id}`);
1090
+ }
1091
+ /**
1092
+ * Search merchant_orders by external_reference, preference_id, or status.
1093
+ */
1094
+ async searchMerchantOrders(params = {}) {
1095
+ const query = {};
1096
+ if (params.preferenceId) query.preference_id = params.preferenceId;
1097
+ if (params.externalReference) query.external_reference = params.externalReference;
1098
+ if (params.status) query.status = params.status;
1099
+ if (params.limit !== void 0) query.limit = params.limit;
1100
+ if (params.offset !== void 0) query.offset = params.offset;
1101
+ const result = await this.request("GET", "/merchant_orders/search", void 0, { query });
1102
+ return {
1103
+ elements: result.elements ?? [],
1104
+ paging: result.paging ?? { limit: params.limit ?? 25, offset: params.offset ?? 0, total: 0 }
1105
+ };
1106
+ }
1107
+ /**
1108
+ * Update a merchant_order — typically to add items or update shipping.
1109
+ */
1110
+ async updateMerchantOrder(id, patch) {
1111
+ return this.request("PUT", `/merchant_orders/${id}`, patch);
1112
+ }
1113
+ // ──────────────────────────────────────────────────────────────────────────
1114
+ // v0.7 — Stores + POS CRUD completion
1115
+ // ──────────────────────────────────────────────────────────────────────────
1116
+ async getStore(userId, storeId) {
1117
+ return this.request("GET", `/users/${userId}/stores/${storeId}`);
1118
+ }
1119
+ async updateStore(userId, storeId, patch) {
1120
+ return this.request("PUT", `/users/${userId}/stores/${storeId}`, patch);
1121
+ }
1122
+ async deleteStore(userId, storeId) {
1123
+ await this.request("DELETE", `/users/${userId}/stores/${storeId}`);
1124
+ }
1125
+ async getPos(posId) {
1126
+ return this.request("GET", `/pos/${posId}`);
1127
+ }
1128
+ async updatePos(posId, patch) {
1129
+ return this.request("PUT", `/pos/${posId}`, patch);
1130
+ }
1131
+ async deletePos(posId) {
1132
+ await this.request("DELETE", `/pos/${posId}`);
1133
+ }
1134
+ // ──────────────────────────────────────────────────────────────────────────
1135
+ // v0.7 — Bank Accounts (the CBUs the seller has registered for payouts)
1136
+ // ──────────────────────────────────────────────────────────────────────────
1137
+ /**
1138
+ * List bank accounts registered by the seller. The default is the one
1139
+ * that receives `release_money` settlements.
1140
+ */
1141
+ async listBankAccounts() {
1142
+ const result = await this.request(
1143
+ "GET",
1144
+ "/users/me/bank_accounts"
1145
+ );
1146
+ if (Array.isArray(result)) return result;
1147
+ return result.results ?? [];
1148
+ }
1149
+ /**
1150
+ * Register a new bank account (CBU) for the seller. Note: MP usually
1151
+ * requires this through the dashboard for compliance — this endpoint may
1152
+ * not work for all sellers.
1153
+ */
1154
+ async registerBankAccount(params) {
1155
+ return this.request("POST", "/users/me/bank_accounts", params);
1156
+ }
1157
+ // ──────────────────────────────────────────────────────────────────────────
1158
+ // v0.7 — Point Devices (physical terminal hardware: Smart, Tap to Pay)
1159
+ //
1160
+ // Distinct from the logical `Pos` entity. PointDevices are the actual
1161
+ // physical terminals you have at brick-and-mortar shops.
1162
+ // ──────────────────────────────────────────────────────────────────────────
1163
+ /**
1164
+ * List the Point devices linked to the seller's MP account. Each device
1165
+ * has an id (the device serial), an operating_mode (PDV vs STANDALONE),
1166
+ * and an optional pos_id (when bound to a logical POS).
1167
+ */
1168
+ async listPointDevices(params = {}) {
1169
+ const query = {};
1170
+ if (params.posId !== void 0) query["pos.id"] = params.posId;
1171
+ if (params.limit !== void 0) query.limit = params.limit;
1172
+ if (params.offset !== void 0) query.offset = params.offset;
1173
+ const result = await this.request("GET", "/point/integration-api/devices", void 0, { query });
1174
+ return {
1175
+ devices: result.devices ?? [],
1176
+ paging: result.paging ?? { total: 0, limit: params.limit ?? 50, offset: params.offset ?? 0 }
1177
+ };
1178
+ }
1179
+ /**
1180
+ * Switch a Point device's operating mode:
1181
+ * - "PDV": device is bound to a logical Pos and only takes payments
1182
+ * triggered through that Pos (typical for cash-register integrations).
1183
+ * - "STANDALONE": device works independently, accepts any payment.
1184
+ */
1185
+ async updatePointDeviceOperatingMode(deviceId, operatingMode) {
1186
+ return this.request(
1187
+ "PATCH",
1188
+ `/point/integration-api/devices/${encodeURIComponent(deviceId)}`,
1189
+ { operating_mode: operatingMode }
1190
+ );
1191
+ }
1192
+ /**
1193
+ * Create a payment intent on a Point device — the device prompts the buyer
1194
+ * to tap/insert/swipe. Returns immediately with intent id; query state via
1195
+ * `getPointPaymentIntent()` or wait for `point_integration_wh` webhook.
1196
+ *
1197
+ * NOTE: amount is in CENTAVOS (Point API differs from Payments API which
1198
+ * uses pesos). 100 = $1 ARS, 1000 = $10, 10000 = $100, etc.
1199
+ */
1200
+ async createPointPaymentIntent(deviceId, params) {
1201
+ const body = {
1202
+ amount: params.amount,
1203
+ ...params.description ? { description: params.description } : {},
1204
+ ...params.externalReference ? { additional_info: { external_reference: params.externalReference } } : {},
1205
+ payment: {
1206
+ installments: params.installments ?? 1,
1207
+ ...params.installmentsCost ? { installments_cost: params.installmentsCost } : {},
1208
+ ...params.printOnTerminal !== void 0 ? { print_on_terminal: params.printOnTerminal } : {},
1209
+ ...params.ticketNumber ? { ticket_number: params.ticketNumber } : {}
1210
+ }
1211
+ };
1212
+ return this.request(
1213
+ "POST",
1214
+ `/point/integration-api/devices/${encodeURIComponent(deviceId)}/payment-intents`,
1215
+ body
1216
+ );
1217
+ }
1218
+ /** Get the current state of a Point payment intent. */
1219
+ async getPointPaymentIntent(intentId) {
1220
+ return this.request(
1221
+ "GET",
1222
+ `/point/integration-api/payment-intents/${encodeURIComponent(intentId)}`
1223
+ );
1224
+ }
1225
+ /**
1226
+ * Cancel an OPEN payment intent before the buyer interacts with the device.
1227
+ * Only works while state is "OPEN" — once the buyer taps, you can't cancel.
1228
+ */
1229
+ async cancelPointPaymentIntent(deviceId, intentId) {
1230
+ await this.request(
1231
+ "DELETE",
1232
+ `/point/integration-api/devices/${encodeURIComponent(deviceId)}/payment-intents/${encodeURIComponent(intentId)}`
1233
+ );
1234
+ return { id: intentId, canceled: true };
1235
+ }
1028
1236
  };
1237
+
1238
+ // src/crypto.ts
1239
+ var subtle = (() => {
1240
+ const c = globalThis.crypto;
1241
+ if (!c?.subtle) {
1242
+ throw new Error(
1243
+ "@ar-agents/mercadopago: Web Crypto API is not available in this runtime. Use Node 18+, Vercel Edge Runtime, Cloudflare Workers, or any modern browser."
1244
+ );
1245
+ }
1246
+ return c.subtle;
1247
+ })();
1248
+ var encoder = new TextEncoder();
1249
+ async function hmacSha256Hex(secret, message) {
1250
+ const keyMaterial = await subtle.importKey(
1251
+ "raw",
1252
+ encoder.encode(secret),
1253
+ { name: "HMAC", hash: "SHA-256" },
1254
+ false,
1255
+ ["sign"]
1256
+ );
1257
+ const sigBuf = await subtle.sign(
1258
+ "HMAC",
1259
+ keyMaterial,
1260
+ encoder.encode(message)
1261
+ );
1262
+ return bufferToHex(sigBuf);
1263
+ }
1264
+ async function sha256Hex(input) {
1265
+ const digest = await subtle.digest("SHA-256", encoder.encode(input));
1266
+ return bufferToHex(digest);
1267
+ }
1268
+ function timingSafeEqualHex(a, b) {
1269
+ if (a.length !== b.length) return false;
1270
+ let diff = 0;
1271
+ for (let i = 0; i < a.length; i++) {
1272
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1273
+ }
1274
+ return diff === 0;
1275
+ }
1276
+ function bufferToHex(buf) {
1277
+ const bytes = new Uint8Array(buf);
1278
+ let hex = "";
1279
+ for (let i = 0; i < bytes.length; i++) {
1280
+ const b = bytes[i];
1281
+ hex += (b < 16 ? "0" : "") + b.toString(16);
1282
+ }
1283
+ return hex;
1284
+ }
1029
1285
  zod.z.enum(["MLA", "MLB", "MLM", "MCO", "MLC", "MLU"]);
1030
1286
  var CurrencyIdSchema = zod.z.enum(["ARS", "USD", "BRL", "MXN"]);
1031
1287
  var FrequencyTypeSchema = zod.z.enum(["months", "days"]);
@@ -1405,6 +1661,63 @@ zod.z.object({
1405
1661
  bank_name: zod.z.string().optional()
1406
1662
  }).passthrough().optional()
1407
1663
  }).passthrough();
1664
+ zod.z.object({
1665
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1666
+ status: zod.z.string().optional(),
1667
+ external_reference: zod.z.string().nullable().optional(),
1668
+ preference_id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
1669
+ payments: zod.z.array(zod.z.unknown()).optional(),
1670
+ shipments: zod.z.array(zod.z.unknown()).optional(),
1671
+ payer: zod.z.unknown().optional(),
1672
+ collector: zod.z.unknown().optional(),
1673
+ marketplace: zod.z.string().optional(),
1674
+ total_amount: zod.z.number().optional(),
1675
+ paid_amount: zod.z.number().optional(),
1676
+ refunded_amount: zod.z.number().optional(),
1677
+ shipping_cost: zod.z.number().optional(),
1678
+ date_created: zod.z.string().optional(),
1679
+ last_updated: zod.z.string().optional(),
1680
+ site_id: zod.z.string().optional(),
1681
+ order_status: zod.z.string().optional()
1682
+ }).passthrough();
1683
+ zod.z.object({
1684
+ id: zod.z.union([zod.z.string(), zod.z.number()]).transform(String),
1685
+ cbu: zod.z.string().optional(),
1686
+ alias: zod.z.string().nullable().optional(),
1687
+ bank_name: zod.z.string().optional(),
1688
+ account_type: zod.z.string().optional(),
1689
+ status: zod.z.string().optional(),
1690
+ is_default: zod.z.boolean().optional(),
1691
+ date_created: zod.z.string().optional()
1692
+ }).passthrough();
1693
+ zod.z.object({
1694
+ id: zod.z.string(),
1695
+ pos_id: zod.z.union([zod.z.string(), zod.z.number()]).nullable().optional(),
1696
+ store_id: zod.z.union([zod.z.string(), zod.z.number()]).nullable().optional(),
1697
+ external_pos_id: zod.z.string().nullable().optional(),
1698
+ operating_mode: zod.z.union([zod.z.literal("PDV"), zod.z.literal("STANDALONE"), zod.z.string()]).optional()
1699
+ }).passthrough();
1700
+ zod.z.object({
1701
+ id: zod.z.string(),
1702
+ device_id: zod.z.string().optional(),
1703
+ amount: zod.z.number().optional(),
1704
+ state: zod.z.union([
1705
+ zod.z.literal("OPEN"),
1706
+ zod.z.literal("PROCESSING"),
1707
+ zod.z.literal("FINISHED"),
1708
+ zod.z.literal("CANCELED"),
1709
+ zod.z.literal("ERROR"),
1710
+ zod.z.string()
1711
+ ]).optional(),
1712
+ payment: zod.z.object({
1713
+ id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
1714
+ type: zod.z.string().optional(),
1715
+ installments: zod.z.number().optional(),
1716
+ installments_cost: zod.z.string().optional(),
1717
+ print_on_terminal: zod.z.boolean().optional()
1718
+ }).passthrough().optional(),
1719
+ additional_info: zod.z.unknown().optional()
1720
+ }).passthrough();
1408
1721
 
1409
1722
  // src/oauth.ts
1410
1723
  var DEFAULT_AUTHORIZE_URL = "https://auth.mercadopago.com.ar/authorization";
@@ -1472,6 +1785,243 @@ function isExpiringSoon(expirationMs, skewSeconds = 300) {
1472
1785
  return Date.now() + skewSeconds * 1e3 >= expirationMs;
1473
1786
  }
1474
1787
 
1788
+ // src/helpers.ts
1789
+ function computeMarketplaceFee(amountArs, rule) {
1790
+ if (amountArs <= 0) return 0;
1791
+ const percentPart = (rule.percent ?? 0) > 0 ? amountArs * (rule.percent / 100) : 0;
1792
+ const flatPart = rule.flatArs ?? 0;
1793
+ let fee = percentPart + flatPart;
1794
+ if (rule.minArs !== void 0) fee = Math.max(fee, rule.minArs);
1795
+ if (rule.maxArs !== void 0) fee = Math.min(fee, rule.maxArs);
1796
+ if (fee > amountArs) fee = amountArs;
1797
+ if (rule.round !== false) fee = Math.round(fee);
1798
+ return fee;
1799
+ }
1800
+ var STATUS_DETAIL_MAP = {
1801
+ // Approved
1802
+ accredited: {
1803
+ summary: "Pago aprobado y acreditado.",
1804
+ recommendedAction: "Confirmar al cliente y continuar con el flujo (env\xEDo, factura).",
1805
+ retryable: false
1806
+ },
1807
+ partially_refunded: {
1808
+ summary: "Pago aprobado con reembolso parcial.",
1809
+ recommendedAction: "Mostrar el monto neto y el reembolso. Verificar inventario si corresponde.",
1810
+ retryable: false
1811
+ },
1812
+ // Pending
1813
+ pending_contingency: {
1814
+ summary: "Pago en proceso por contingencia. MP est\xE1 procesando \u2014 puede demorar minutos a horas.",
1815
+ recommendedAction: "No reintentar todav\xEDa. Esperar webhook de actualizaci\xF3n (puede demorar 24h m\xE1x).",
1816
+ retryable: false
1817
+ },
1818
+ pending_review_manual: {
1819
+ summary: "Pago en revisi\xF3n manual por el equipo de seguridad de MP.",
1820
+ recommendedAction: "No reintentar. MP responder\xE1 con webhook en 24-72h. Avisar al cliente sobre la demora.",
1821
+ retryable: false
1822
+ },
1823
+ pending_waiting_payment: {
1824
+ summary: "Esperando que el comprador complete el pago (t\xEDpico de account_money / boleto / Rapipago).",
1825
+ recommendedAction: "Mostrar instrucciones de pago. MP avisar\xE1 por webhook cuando se complete.",
1826
+ retryable: false
1827
+ },
1828
+ pending_waiting_transfer: {
1829
+ summary: "Esperando confirmaci\xF3n de transferencia bancaria.",
1830
+ recommendedAction: "Esperar webhook. Sin acci\xF3n del agente.",
1831
+ retryable: false
1832
+ },
1833
+ pending_challenge: {
1834
+ summary: "El emisor de la tarjeta requiri\xF3 autenticaci\xF3n 3DS. El comprador debe completar el desaf\xEDo.",
1835
+ recommendedAction: "Redirigir al comprador a `payment.three_ds_info.external_resource_url` (us\xE1 analyze_payment_3ds para obtenerlo).",
1836
+ retryable: false
1837
+ },
1838
+ // Rejected — RETRYABLE (user can fix and try again)
1839
+ cc_rejected_bad_filled_card_number: {
1840
+ summary: "El n\xFAmero de tarjeta es incorrecto.",
1841
+ recommendedAction: "Pedir al cliente que verifique el n\xFAmero. Reintentable.",
1842
+ retryable: true
1843
+ },
1844
+ cc_rejected_bad_filled_security_code: {
1845
+ summary: "El CVV es incorrecto.",
1846
+ recommendedAction: "Pedir el CVV nuevamente. Reintentable.",
1847
+ retryable: true
1848
+ },
1849
+ cc_rejected_bad_filled_date: {
1850
+ summary: "La fecha de vencimiento es incorrecta.",
1851
+ recommendedAction: "Pedir al cliente que verifique mes/a\xF1o. Reintentable.",
1852
+ retryable: true
1853
+ },
1854
+ cc_rejected_bad_filled_other: {
1855
+ summary: "Alg\xFAn dato de la tarjeta es incorrecto.",
1856
+ recommendedAction: "Pedir al cliente que revise todos los datos. Reintentable.",
1857
+ retryable: true
1858
+ },
1859
+ cc_rejected_call_for_authorize: {
1860
+ summary: "El emisor requiere que el cliente autorice el pago llamando al banco.",
1861
+ recommendedAction: "Mostrar el tel\xE9fono del banco emisor (us\xE1 list_issuers para obtenerlo). Reintentable despu\xE9s de la llamada.",
1862
+ retryable: true
1863
+ },
1864
+ cc_rejected_card_disabled: {
1865
+ summary: "La tarjeta est\xE1 inhabilitada por el banco emisor.",
1866
+ recommendedAction: "El cliente debe contactar a su banco. Probar con otra tarjeta. Reintentable con otra tarjeta.",
1867
+ retryable: true
1868
+ },
1869
+ cc_rejected_insufficient_amount: {
1870
+ summary: "Saldo insuficiente en la tarjeta.",
1871
+ recommendedAction: "Sugerir tarjeta alternativa o monto menor. Reintentable.",
1872
+ retryable: true
1873
+ },
1874
+ cc_rejected_invalid_installments: {
1875
+ summary: "El emisor no soporta esa cantidad de cuotas para esta tarjeta.",
1876
+ recommendedAction: "Llamar a calculate_installments para ver opciones v\xE1lidas y sugerir otra. Reintentable.",
1877
+ retryable: true
1878
+ },
1879
+ cc_rejected_other_reason: {
1880
+ summary: "Pago rechazado por raz\xF3n no especificada del emisor.",
1881
+ recommendedAction: "Sugerir otra tarjeta o m\xE9todo de pago. Reintentable con otra tarjeta.",
1882
+ retryable: true
1883
+ },
1884
+ // Rejected — NON-RETRYABLE (don't retry the same card)
1885
+ cc_rejected_blacklist: {
1886
+ summary: "Pago rechazado por blacklist de seguridad de MP. NO REINTENTAR con esta tarjeta.",
1887
+ recommendedAction: "Sugerir un m\xE9todo de pago alternativo (account_money, otra tarjeta de otro titular).",
1888
+ retryable: false
1889
+ },
1890
+ cc_rejected_high_risk: {
1891
+ summary: "Rechazo por an\xE1lisis de riesgo de MP. La tarjeta es v\xE1lida pero MP detect\xF3 fraude potencial.",
1892
+ recommendedAction: "Sugerir otro medio de pago o pedirle al cliente que verifique su identidad en MP.",
1893
+ retryable: false
1894
+ },
1895
+ cc_rejected_max_attempts: {
1896
+ summary: "Excedi\xF3 el n\xFAmero m\xE1ximo de intentos con esta tarjeta.",
1897
+ recommendedAction: "Pedir al cliente que use otra tarjeta. NO REINTENTAR la misma.",
1898
+ retryable: false
1899
+ },
1900
+ cc_rejected_duplicated_payment: {
1901
+ summary: "Ya se proces\xF3 un pago id\xE9ntico en los \xFAltimos minutos (deduplicaci\xF3n de MP).",
1902
+ recommendedAction: "Verificar con search_payments si el pago anterior se acredit\xF3. Sin acci\xF3n adicional necesaria.",
1903
+ retryable: false
1904
+ },
1905
+ // Cancelled / refunded / mediation
1906
+ by_collector: {
1907
+ summary: "El vendedor cancel\xF3 el pago.",
1908
+ recommendedAction: "Sin acci\xF3n. Estado final.",
1909
+ retryable: false
1910
+ },
1911
+ by_payer: {
1912
+ summary: "El comprador cancel\xF3 el pago.",
1913
+ recommendedAction: "Sin acci\xF3n. Estado final.",
1914
+ retryable: false
1915
+ },
1916
+ refunded: {
1917
+ summary: "Pago reembolsado al comprador.",
1918
+ recommendedAction: "Estado final. Reflejar el reembolso en tu sistema.",
1919
+ retryable: false
1920
+ },
1921
+ charged_back: {
1922
+ summary: "Contracargo (chargeback) iniciado por el banco emisor.",
1923
+ recommendedAction: "Revisar list_payment_disputes para responder. Surface al equipo de ops.",
1924
+ retryable: false
1925
+ }
1926
+ };
1927
+ function explainPaymentStatus(payment) {
1928
+ const status = payment.status;
1929
+ const statusDetail = payment.status_detail ?? "";
1930
+ const detail = STATUS_DETAIL_MAP[statusDetail];
1931
+ if (detail) {
1932
+ const isFinal = status === "approved" || status === "rejected" || status === "cancelled" || status === "refunded" || status === "charged_back";
1933
+ return {
1934
+ summary: detail.summary,
1935
+ recommendedAction: detail.recommendedAction,
1936
+ final: isFinal,
1937
+ paid: status === "approved",
1938
+ retryable: detail.retryable
1939
+ };
1940
+ }
1941
+ switch (status) {
1942
+ case "approved":
1943
+ return {
1944
+ summary: "Pago aprobado.",
1945
+ recommendedAction: "Continuar con el flujo posterior (env\xEDo, factura, notificaci\xF3n).",
1946
+ final: true,
1947
+ paid: true,
1948
+ retryable: false
1949
+ };
1950
+ case "authorized":
1951
+ return {
1952
+ summary: "Pago autorizado pero no capturado (auth-only).",
1953
+ recommendedAction: "Llamar a capture_payment cuando complet\xE9s el servicio. Vence en 7 d\xEDas si no captur\xE1s.",
1954
+ final: false,
1955
+ paid: false,
1956
+ retryable: false
1957
+ };
1958
+ case "in_process":
1959
+ return {
1960
+ summary: "Pago en proceso.",
1961
+ recommendedAction: "Esperar webhook. Sin acci\xF3n inmediata.",
1962
+ final: false,
1963
+ paid: false,
1964
+ retryable: false
1965
+ };
1966
+ case "in_mediation":
1967
+ return {
1968
+ summary: "Pago en mediaci\xF3n con MP por disputa del comprador.",
1969
+ recommendedAction: "Revisar list_payment_disputes y responder via dashboard.",
1970
+ final: false,
1971
+ paid: false,
1972
+ retryable: false
1973
+ };
1974
+ case "pending":
1975
+ return {
1976
+ summary: "Pago pendiente. El comprador no complet\xF3 el pago todav\xEDa o MP est\xE1 procesando.",
1977
+ recommendedAction: "Esperar webhook (puede demorar minutos a 72h seg\xFAn el m\xE9todo).",
1978
+ final: false,
1979
+ paid: false,
1980
+ retryable: false
1981
+ };
1982
+ case "rejected":
1983
+ return {
1984
+ summary: `Pago rechazado (status_detail: ${statusDetail || "no especificado"}).`,
1985
+ recommendedAction: "Verificar status_detail. Considerar otro m\xE9todo de pago.",
1986
+ final: true,
1987
+ paid: false,
1988
+ retryable: true
1989
+ };
1990
+ case "cancelled":
1991
+ return {
1992
+ summary: "Pago cancelado.",
1993
+ recommendedAction: "Estado final. Sin acci\xF3n.",
1994
+ final: true,
1995
+ paid: false,
1996
+ retryable: false
1997
+ };
1998
+ case "refunded":
1999
+ return {
2000
+ summary: "Pago reembolsado.",
2001
+ recommendedAction: "Estado final. Reflejar el reembolso.",
2002
+ final: true,
2003
+ paid: false,
2004
+ retryable: false
2005
+ };
2006
+ case "charged_back":
2007
+ return {
2008
+ summary: "Pago con contracargo del banco.",
2009
+ recommendedAction: "Surfacear a ops. Revisar disputas.",
2010
+ final: true,
2011
+ paid: false,
2012
+ retryable: false
2013
+ };
2014
+ default:
2015
+ return {
2016
+ summary: `Status no reconocido: '${status}'.`,
2017
+ recommendedAction: "Inspeccionar payment.status + payment.status_detail manualmente.",
2018
+ final: false,
2019
+ paid: false,
2020
+ retryable: false
2021
+ };
2022
+ }
2023
+ }
2024
+
1475
2025
  // src/test-cards.ts
1476
2026
  var TEST_CARDS_AR = {
1477
2027
  VISA_CREDIT: {
@@ -1599,6 +2149,8 @@ function analyze3DS(payment) {
1599
2149
  description: "No se pudo determinar el estado 3DS \u2014 revisar payment.three_d_secure_mode + payment.status_detail manualmente."
1600
2150
  };
1601
2151
  }
2152
+
2153
+ // src/webhook.ts
1602
2154
  function parseWebhookEvent(body, searchParams) {
1603
2155
  const parseResult = WebhookBodySchema.safeParse(body ?? {});
1604
2156
  const parsedBody = parseResult.success ? parseResult.data : {};
@@ -1614,7 +2166,8 @@ function parseWebhookEvent(body, searchParams) {
1614
2166
  raw: parsedBody
1615
2167
  };
1616
2168
  }
1617
- function verifyWebhookSignature(params) {
2169
+ var DEFAULT_REPLAY_TOLERANCE_SECONDS = 300;
2170
+ async function verifyWebhookSignature(params) {
1618
2171
  if (!params.signatureHeader || !params.requestId) return false;
1619
2172
  const parts = Object.fromEntries(
1620
2173
  params.signatureHeader.split(",").map((segment) => segment.trim().split("="))
@@ -1622,16 +2175,20 @@ function verifyWebhookSignature(params) {
1622
2175
  const ts = parts.ts;
1623
2176
  const v1 = parts.v1;
1624
2177
  if (!ts || !v1) return false;
2178
+ const tolerance = params.replayToleranceSeconds ?? DEFAULT_REPLAY_TOLERANCE_SECONDS;
2179
+ const tsNumber = Number(ts);
2180
+ if (!Number.isFinite(tsNumber)) return false;
2181
+ const ageSeconds = Math.abs(Math.floor(Date.now() / 1e3) - tsNumber);
2182
+ if (ageSeconds > tolerance) return false;
1625
2183
  const manifest = `id:${params.dataId};request-id:${params.requestId};ts:${ts};`;
1626
- const expected = crypto.createHmac("sha256", params.secret).update(manifest).digest("hex");
1627
- if (expected.length !== v1.length) return false;
1628
- return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
2184
+ const expected = await hmacSha256Hex(params.secret, manifest);
2185
+ return timingSafeEqualHex(expected, v1);
1629
2186
  }
1630
2187
 
1631
2188
  // src/tools.ts
1632
- function deterministicIdempotencyKey(...parts) {
2189
+ async function deterministicIdempotencyKey(...parts) {
1633
2190
  const payload = parts.filter((p) => p !== void 0 && p !== null).map(String).join("|");
1634
- return crypto.createHash("sha256").update(payload).digest("hex").slice(0, 32);
2191
+ return (await sha256Hex(payload)).slice(0, 32);
1635
2192
  }
1636
2193
  var DEFAULT_DESCRIPTIONS = {
1637
2194
  // ── Subscriptions ────────────────────────────────────────────────────────
@@ -1709,7 +2266,41 @@ var DEFAULT_DESCRIPTIONS = {
1709
2266
  // ── 3DS analyzer (v0.6 — pure) ───────────────────────────────────────────
1710
2267
  analyze_payment_3ds: "Pure local analyzer for a Payment's 3DS (Strong Customer Authentication) state. Pass a payment_id (string) and the tool fetches the Payment then derives { status: 'not_required'|'frictionless'|'challenge_required'|'rejected'|'unknown', mode, challengeUrl, description }. USE THIS after every create_payment for credit cards: when challengeUrl !== null, you MUST redirect the buyer there before the payment can complete. Without 3DS, payments stay in 'pending' indefinitely if the issuer demanded a challenge.",
1711
2268
  // ── Test cards (v0.6 — pure) ─────────────────────────────────────────────
1712
- get_test_cards: "Pure helper that returns the official MP test cards for AR (MLA): VISA/Mastercard/Amex credit + debit, with the 'magic' holder names that route the payment to specific status_detail values (APRO=approved, OTHE=rejected, CONT=pending, FUND=insufficient_amount, etc.). USE WHEN you need to demo a payment flow without a real card, or to script integration tests. Pure data \u2014 no network call."
2269
+ get_test_cards: "Pure helper that returns the official MP test cards for AR (MLA): VISA/Mastercard/Amex credit + debit, with the 'magic' holder names that route the payment to specific status_detail values (APRO=approved, OTHE=rejected, CONT=pending, FUND=insufficient_amount, etc.). USE WHEN you need to demo a payment flow without a real card, or to script integration tests. Pure data \u2014 no network call.",
2270
+ // ── Customer + Card extensions (v0.7) ────────────────────────────────────
2271
+ get_customer: "Get a customer by id. Returns full Customer object: email, first_name, last_name, identification, address, default_card, registered cards. PURE READ. USE WHEN you have the customer_id from a previous create_customer / find_customer_by_email / payment.payer.id and want the full record.",
2272
+ update_customer: "Update a customer's profile (first_name, last_name, phone, identification, address, default_card). MP merges the patch \u2014 fields you don't send remain unchanged. Use to keep customer records in sync (e.g., shipping address changes) or to set a default card for charge_saved_card.",
2273
+ create_customer_card: "Add a saved card to an existing customer using a card_token (one-time token from MP frontend Cardform \u2014 agents should NEVER take raw card data, that's a PCI violation). Returns the saved CustomerCard with id usable in charge_saved_card. Persists across charges (no need to re-tokenize each time).",
2274
+ get_customer_card: "Get details of a single saved card by (customer_id, card_id). Returns last 4 digits, expiration, brand, issuer. PURE READ \u2014 useful before charge_saved_card to confirm the card is still valid.",
2275
+ // ── Subscription / Plan / Refund / Preference extensions (v0.7) ─────────
2276
+ get_subscription_plan: "Fetch a subscription plan by id. Returns plan config: amount, frequency, status, init_point. Use to inspect a plan before subscribing customers, or to display plan details to the user.",
2277
+ update_subscription: "Update a subscription's amount, status, reason, external_reference, OR card_token_id (to switch payment method when the buyer's card is expired/declined). For card swap: pass card_token_id from a fresh tokenization. CONSTRAINTS: status changes only support 'paused' | 'cancelled' (use authorize via init_point flow to re-activate).",
2278
+ search_subscriptions: "Search subscriptions across the seller's account. Filter by status (pending/authorized/paused/cancelled), payer_email, external_reference, or preapproval_plan_id (to find all subscribers of a plan). Paginated. USE WHEN you need to enumerate active subscribers, audit cancellations, or find a subscription by external reference.",
2279
+ get_refund: "Fetch a single refund by (payment_id, refund_id). Returns the Refund object with amount, status, date_created. PURE READ \u2014 useful to verify a refund processed or to reconcile partial-refund history.",
2280
+ update_payment_preference: "Update a Checkout Pro preference (notification_url, back_urls, items, payer info, payment_methods exclusion list). Only works on preferences NOT yet paid. Common use: regenerate the link with a new notification_url after deployment, or change items if the buyer requested adjustments before paying.",
2281
+ // ── Merchant Orders (v0.7) ────────────────────────────────────────────────
2282
+ get_merchant_order: "Get a merchant_order with all its associated payments + shipments. MerchantOrder is the parent entity for Payments associated with a single Preference \u2014 one Order can have multiple partial Payments (retries, installments). USE THIS in webhooks with topic='merchant_order' to get the aggregate paid_amount, refunded_amount, and shipping status in one call.",
2283
+ search_merchant_orders: "Search merchant_orders by preference_id, external_reference, or status. Paginated. Returns up to 50 per page. USE WHEN you have a preference_id and want all its derived merchant_orders, or when reconciling 'which payments belong to which preference'.",
2284
+ update_merchant_order: "Update a merchant_order \u2014 typically to add items or shipping info. Most agent flows don't need this; use only when integrating with a custom shipping flow that requires updating the MO mid-lifecycle.",
2285
+ // ── Stores + POS CRUD completion (v0.7) ──────────────────────────────────
2286
+ get_store: "Fetch a single store by (user_id, store_id). Returns store details: name, location, business_hours, external_id. PURE READ.",
2287
+ update_store: "Update a store's properties (name, location, business_hours, external_id). MP merges the patch.",
2288
+ delete_store: "Delete a store. IRREVERSIBLE. Confirm with user before calling. Will fail if the store has associated POSes \u2014 delete those first.",
2289
+ get_pos: "Fetch a POS by id. Returns: name, store_id, category, external_id, qr_template (if configured). PURE READ. Use when you need to find the external_id for create_qr_payment.",
2290
+ update_pos: "Update a POS's properties (name, category, external_id). MP merges the patch.",
2291
+ delete_pos: "Delete a POS. IRREVERSIBLE. Cancels any pending QR orders attached to it. Confirm with user before calling.",
2292
+ // ── Bank Accounts (v0.7) ─────────────────────────────────────────────────
2293
+ list_bank_accounts: "List the bank accounts (CBUs) the seller has registered with MP for receiving payouts. Returns an array \u2014 the one with `is_default: true` is where settlements (release_money) go. USE BEFORE list_settlements when the user asks 'a qu\xE9 cuenta me deposita MP'.",
2294
+ register_bank_account: "Register a new bank account (CBU) for the seller. NOTE: MP usually requires this through the dashboard for compliance \u2014 this endpoint may not work for all accounts. If it fails with 403, redirect the user to https://www.mercadopago.com.ar/banking/dashboard.",
2295
+ // ── Point Devices físicos (v0.7) ─────────────────────────────────────────
2296
+ list_point_devices: "List the physical Point devices (Smart, Tap to Pay, etc.) linked to the seller's MP account. Distinct from logical POS \u2014 these are actual terminals at brick-and-mortar shops. Returns each device's id (serial), operating_mode (PDV vs STANDALONE), and pos_id (when bound to a logical POS). Filter by pos_id to find devices for a specific cash register.",
2297
+ update_point_device_mode: "Switch a Point device's operating_mode between 'PDV' (bound to a logical POS, takes payments triggered through that POS) and 'STANDALONE' (works independently, accepts any payment). PDV is for cash-register integrations; STANDALONE is for free-form retail. Affects how payments hit the device.",
2298
+ create_point_payment_intent: "Create a payment intent on a physical Point device \u2014 the device prompts the buyer to tap/insert/swipe their card. Returns immediately with intent_id; query state via get_point_payment_intent or wait for point_integration_wh webhook. **AMOUNT IS IN CENTAVOS**, NOT pesos (Point API differs from Payments API): 100 = $1, 1000 = $10, 10000 = $100.",
2299
+ get_point_payment_intent: "Get the current state of a Point payment intent (OPEN, PROCESSING, FINISHED, CANCELED, ERROR). USE in polling loops if you can't wait for the webhook. When state=FINISHED, the intent.payment.id is the resulting Payment id usable with get_payment.",
2300
+ cancel_point_payment_intent: "Cancel an OPEN point payment intent before the buyer interacts with the device. ONLY WORKS while state='OPEN' \u2014 once the buyer taps, you can't cancel; refund_payment after the fact instead.",
2301
+ // ── Pure helpers (v0.7) ──────────────────────────────────────────────────
2302
+ compute_marketplace_fee: "PURE HELPER (no network) \u2014 given a transaction amount + fee rule (% or flat ARS, with optional min/max floors), returns the exact `marketplace_fee` value in ARS to pass to create_order or create_payment_preference. USE WHEN your platform takes a commission and you need to compute the exact fee per transaction. Examples: { percent: 5, minArs: 50, maxArs: 5000 } for percentage with floor + cap; { flatArs: 200, percent: 2 } for fixed + percentage.",
2303
+ explain_payment_status: "PURE HELPER (no network) \u2014 given a Payment object (from get_payment / create_payment / handle_webhook), returns { summary, recommendedAction, final, paid, retryable } in Spanish. Translates MP's cryptic status_detail codes to plain Spanish + actionable guidance ('reintentar con otra tarjeta' vs 'esperar webhook' vs 'estado final'). USE THIS instead of having to memorize 30+ status_detail codes \u2014 surface summary + recommendedAction directly to the user."
1713
2304
  };
1714
2305
  function mercadoPagoTools(client, options) {
1715
2306
  const desc = (name) => options.descriptions?.[name] ?? DEFAULT_DESCRIPTIONS[name];
@@ -1853,7 +2444,7 @@ function mercadoPagoTools(client, options) {
1853
2444
  ...options.notificationUrl !== void 0 ? { notificationUrl: options.notificationUrl } : {},
1854
2445
  // Deterministic idempotency key — safe to retry, same inputs always
1855
2446
  // produce the same key (MP dedupes on its side).
1856
- idempotencyKey: deterministicIdempotencyKey(
2447
+ idempotencyKey: await deterministicIdempotencyKey(
1857
2448
  "create_payment",
1858
2449
  input.external_reference ?? input.payer_email,
1859
2450
  input.amount_ars,
@@ -1976,7 +2567,7 @@ function mercadoPagoTools(client, options) {
1976
2567
  const refund = await client.createRefund({
1977
2568
  paymentId: payment_id,
1978
2569
  ...amount_ars !== void 0 ? { amount: amount_ars } : {},
1979
- idempotencyKey: deterministicIdempotencyKey("refund", payment_id, amount_ars ?? "full")
2570
+ idempotencyKey: await deterministicIdempotencyKey("refund", payment_id, amount_ars ?? "full")
1980
2571
  });
1981
2572
  return {
1982
2573
  refund_id: refund.id,
@@ -2242,7 +2833,7 @@ function mercadoPagoTools(client, options) {
2242
2833
  ...input.installments !== void 0 ? { installments: input.installments } : {},
2243
2834
  ...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
2244
2835
  ...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
2245
- idempotencyKey: deterministicIdempotencyKey(
2836
+ idempotencyKey: await deterministicIdempotencyKey(
2246
2837
  "charge_saved_card",
2247
2838
  input.card_id,
2248
2839
  input.amount_ars,
@@ -2753,7 +3344,7 @@ function mercadoPagoTools(client, options) {
2753
3344
  resource: null
2754
3345
  };
2755
3346
  }
2756
- const verified = verifyWebhookSignature({
3347
+ const verified = await verifyWebhookSignature({
2757
3348
  requestId: request_id_header,
2758
3349
  dataId: event.dataId,
2759
3350
  signatureHeader: signature_header,
@@ -2951,7 +3542,7 @@ function mercadoPagoTools(client, options) {
2951
3542
  if (input.marketplace_fee !== void 0) params.marketplace_fee = input.marketplace_fee;
2952
3543
  if (input.collector_id !== void 0) params.collector_id = input.collector_id;
2953
3544
  const order = await client.createOrder(params, {
2954
- idempotencyKey: deterministicIdempotencyKey(
3545
+ idempotencyKey: await deterministicIdempotencyKey(
2955
3546
  "create_order",
2956
3547
  input.external_reference,
2957
3548
  input.total_amount,
@@ -3103,6 +3694,396 @@ function mercadoPagoTools(client, options) {
3103
3694
  usage: "Pass holderName='APRO' for an approved payment, 'OTHE' for rejected, 'CONT' for pending, 'FUND' for insufficient amount, 'CALL' for call-for-authorize. Use a NEW payer email per call (append a timestamp) to avoid MP idempotency-on-email deduping."
3104
3695
  };
3105
3696
  }
3697
+ }),
3698
+ // ─────────────────────────────────────────────────────────────────────────
3699
+ // v0.7 — Customer + Card extensions
3700
+ // ─────────────────────────────────────────────────────────────────────────
3701
+ get_customer: ai.tool({
3702
+ description: desc("get_customer"),
3703
+ inputSchema: zod.z.object({ customer_id: zod.z.string() }),
3704
+ execute: async ({ customer_id }) => {
3705
+ return client.getCustomer(customer_id);
3706
+ }
3707
+ }),
3708
+ update_customer: ai.tool({
3709
+ description: desc("update_customer"),
3710
+ inputSchema: zod.z.object({
3711
+ customer_id: zod.z.string(),
3712
+ first_name: zod.z.string().optional(),
3713
+ last_name: zod.z.string().optional(),
3714
+ phone: zod.z.object({ area_code: zod.z.string().optional(), number: zod.z.string().optional() }).optional(),
3715
+ identification: zod.z.object({ type: zod.z.string(), number: zod.z.string() }).optional(),
3716
+ address: zod.z.object({
3717
+ street_name: zod.z.string().optional(),
3718
+ street_number: zod.z.number().optional(),
3719
+ zip_code: zod.z.string().optional()
3720
+ }).optional(),
3721
+ description: zod.z.string().optional(),
3722
+ default_card: zod.z.string().optional()
3723
+ }),
3724
+ execute: async ({ customer_id, ...patch }) => {
3725
+ const cleaned = {};
3726
+ for (const [k, v] of Object.entries(patch)) {
3727
+ if (v !== void 0) cleaned[k] = v;
3728
+ }
3729
+ return client.updateCustomer(customer_id, cleaned);
3730
+ }
3731
+ }),
3732
+ create_customer_card: ai.tool({
3733
+ description: desc("create_customer_card"),
3734
+ inputSchema: zod.z.object({
3735
+ customer_id: zod.z.string(),
3736
+ card_token: zod.z.string().describe("Card token from MP frontend Cardform OR create_card_token.")
3737
+ }),
3738
+ execute: async ({ customer_id, card_token }) => {
3739
+ return client.createCustomerCard(customer_id, card_token);
3740
+ }
3741
+ }),
3742
+ get_customer_card: ai.tool({
3743
+ description: desc("get_customer_card"),
3744
+ inputSchema: zod.z.object({
3745
+ customer_id: zod.z.string(),
3746
+ card_id: zod.z.string()
3747
+ }),
3748
+ execute: async ({ customer_id, card_id }) => {
3749
+ return client.getCustomerCard(customer_id, card_id);
3750
+ }
3751
+ }),
3752
+ // ─────────────────────────────────────────────────────────────────────────
3753
+ // v0.7 — Subscription / Plan / Refund / Preference extensions
3754
+ // ─────────────────────────────────────────────────────────────────────────
3755
+ get_subscription_plan: ai.tool({
3756
+ description: desc("get_subscription_plan"),
3757
+ inputSchema: zod.z.object({ plan_id: zod.z.string() }),
3758
+ execute: async ({ plan_id }) => {
3759
+ return client.getSubscriptionPlan(plan_id);
3760
+ }
3761
+ }),
3762
+ update_subscription: ai.tool({
3763
+ description: desc("update_subscription"),
3764
+ inputSchema: zod.z.object({
3765
+ subscription_id: zod.z.string(),
3766
+ transaction_amount: zod.z.number().positive().optional(),
3767
+ card_token_id: zod.z.string().optional(),
3768
+ status: zod.z.enum(["authorized", "paused", "cancelled"]).optional(),
3769
+ reason: zod.z.string().optional(),
3770
+ external_reference: zod.z.string().optional()
3771
+ }),
3772
+ execute: async ({ subscription_id, ...patch }) => {
3773
+ const cleaned = {};
3774
+ for (const [k, v] of Object.entries(patch)) {
3775
+ if (v !== void 0) cleaned[k] = v;
3776
+ }
3777
+ return client.updatePreapproval(subscription_id, cleaned);
3778
+ }
3779
+ }),
3780
+ search_subscriptions: ai.tool({
3781
+ description: desc("search_subscriptions"),
3782
+ inputSchema: zod.z.object({
3783
+ status: zod.z.string().optional(),
3784
+ payer_email: zod.z.string().email().optional(),
3785
+ external_reference: zod.z.string().optional(),
3786
+ plan_id: zod.z.string().optional(),
3787
+ limit: zod.z.number().int().min(1).max(100).optional(),
3788
+ offset: zod.z.number().int().min(0).optional()
3789
+ }),
3790
+ execute: async ({ status, payer_email, external_reference, plan_id, limit, offset }) => {
3791
+ const params = {};
3792
+ if (status !== void 0) params.status = status;
3793
+ if (payer_email !== void 0) params.payerEmail = payer_email;
3794
+ if (external_reference !== void 0) params.externalReference = external_reference;
3795
+ if (plan_id !== void 0) params.preapproval_plan_id = plan_id;
3796
+ if (limit !== void 0) params.limit = limit;
3797
+ if (offset !== void 0) params.offset = offset;
3798
+ return client.searchPreapprovals(params);
3799
+ }
3800
+ }),
3801
+ get_refund: ai.tool({
3802
+ description: desc("get_refund"),
3803
+ inputSchema: zod.z.object({
3804
+ payment_id: zod.z.string(),
3805
+ refund_id: zod.z.string()
3806
+ }),
3807
+ execute: async ({ payment_id, refund_id }) => {
3808
+ return client.getRefund(payment_id, refund_id);
3809
+ }
3810
+ }),
3811
+ update_payment_preference: ai.tool({
3812
+ description: desc("update_payment_preference"),
3813
+ inputSchema: zod.z.object({
3814
+ preference_id: zod.z.string(),
3815
+ notification_url: zod.z.string().url().optional(),
3816
+ external_reference: zod.z.string().optional(),
3817
+ statement_descriptor: zod.z.string().optional()
3818
+ }),
3819
+ execute: async ({ preference_id, ...patch }) => {
3820
+ const cleaned = {};
3821
+ for (const [k, v] of Object.entries(patch)) {
3822
+ if (v !== void 0) cleaned[k] = v;
3823
+ }
3824
+ return client.updatePreference(preference_id, cleaned);
3825
+ }
3826
+ }),
3827
+ // ─────────────────────────────────────────────────────────────────────────
3828
+ // v0.7 — Merchant Orders
3829
+ // ─────────────────────────────────────────────────────────────────────────
3830
+ get_merchant_order: ai.tool({
3831
+ description: desc("get_merchant_order"),
3832
+ inputSchema: zod.z.object({ merchant_order_id: zod.z.string() }),
3833
+ execute: async ({ merchant_order_id }) => {
3834
+ return client.getMerchantOrder(merchant_order_id);
3835
+ }
3836
+ }),
3837
+ search_merchant_orders: ai.tool({
3838
+ description: desc("search_merchant_orders"),
3839
+ inputSchema: zod.z.object({
3840
+ preference_id: zod.z.string().optional(),
3841
+ external_reference: zod.z.string().optional(),
3842
+ status: zod.z.string().optional(),
3843
+ limit: zod.z.number().int().min(1).max(100).optional(),
3844
+ offset: zod.z.number().int().min(0).optional()
3845
+ }),
3846
+ execute: async ({ preference_id, external_reference, status, limit, offset }) => {
3847
+ const params = {};
3848
+ if (preference_id !== void 0) params.preferenceId = preference_id;
3849
+ if (external_reference !== void 0) params.externalReference = external_reference;
3850
+ if (status !== void 0) params.status = status;
3851
+ if (limit !== void 0) params.limit = limit;
3852
+ if (offset !== void 0) params.offset = offset;
3853
+ return client.searchMerchantOrders(params);
3854
+ }
3855
+ }),
3856
+ update_merchant_order: ai.tool({
3857
+ description: desc("update_merchant_order"),
3858
+ inputSchema: zod.z.object({
3859
+ merchant_order_id: zod.z.string(),
3860
+ patch: zod.z.record(zod.z.string(), zod.z.unknown())
3861
+ }),
3862
+ execute: async ({ merchant_order_id, patch }) => {
3863
+ return client.updateMerchantOrder(merchant_order_id, patch);
3864
+ }
3865
+ }),
3866
+ // ─────────────────────────────────────────────────────────────────────────
3867
+ // v0.7 — Stores + POS CRUD completion
3868
+ // ─────────────────────────────────────────────────────────────────────────
3869
+ get_store: ai.tool({
3870
+ description: desc("get_store"),
3871
+ inputSchema: zod.z.object({
3872
+ user_id: zod.z.string(),
3873
+ store_id: zod.z.string()
3874
+ }),
3875
+ execute: async ({ user_id, store_id }) => {
3876
+ return client.getStore(user_id, store_id);
3877
+ }
3878
+ }),
3879
+ update_store: ai.tool({
3880
+ description: desc("update_store"),
3881
+ inputSchema: zod.z.object({
3882
+ user_id: zod.z.string(),
3883
+ store_id: zod.z.string(),
3884
+ name: zod.z.string().optional(),
3885
+ external_id: zod.z.string().optional()
3886
+ }),
3887
+ execute: async ({ user_id, store_id, ...patch }) => {
3888
+ const cleaned = {};
3889
+ for (const [k, v] of Object.entries(patch)) {
3890
+ if (v !== void 0) cleaned[k] = v;
3891
+ }
3892
+ return client.updateStore(user_id, store_id, cleaned);
3893
+ }
3894
+ }),
3895
+ delete_store: ai.tool({
3896
+ description: desc("delete_store"),
3897
+ inputSchema: zod.z.object({
3898
+ user_id: zod.z.string(),
3899
+ store_id: zod.z.string()
3900
+ }),
3901
+ execute: async ({ user_id, store_id }) => {
3902
+ await client.deleteStore(user_id, store_id);
3903
+ return { user_id, store_id, deleted: true };
3904
+ }
3905
+ }),
3906
+ get_pos: ai.tool({
3907
+ description: desc("get_pos"),
3908
+ inputSchema: zod.z.object({ pos_id: zod.z.string() }),
3909
+ execute: async ({ pos_id }) => {
3910
+ return client.getPos(pos_id);
3911
+ }
3912
+ }),
3913
+ update_pos: ai.tool({
3914
+ description: desc("update_pos"),
3915
+ inputSchema: zod.z.object({
3916
+ pos_id: zod.z.string(),
3917
+ name: zod.z.string().optional(),
3918
+ external_id: zod.z.string().optional(),
3919
+ category: zod.z.number().optional()
3920
+ }),
3921
+ execute: async ({ pos_id, ...patch }) => {
3922
+ const cleaned = {};
3923
+ for (const [k, v] of Object.entries(patch)) {
3924
+ if (v !== void 0) cleaned[k] = v;
3925
+ }
3926
+ return client.updatePos(pos_id, cleaned);
3927
+ }
3928
+ }),
3929
+ delete_pos: ai.tool({
3930
+ description: desc("delete_pos"),
3931
+ inputSchema: zod.z.object({ pos_id: zod.z.string() }),
3932
+ execute: async ({ pos_id }) => {
3933
+ await client.deletePos(pos_id);
3934
+ return { pos_id, deleted: true };
3935
+ }
3936
+ }),
3937
+ // ─────────────────────────────────────────────────────────────────────────
3938
+ // v0.7 — Bank Accounts
3939
+ // ─────────────────────────────────────────────────────────────────────────
3940
+ list_bank_accounts: ai.tool({
3941
+ description: desc("list_bank_accounts"),
3942
+ inputSchema: zod.z.object({}),
3943
+ execute: async () => {
3944
+ const accounts = await client.listBankAccounts();
3945
+ return { accounts };
3946
+ }
3947
+ }),
3948
+ register_bank_account: ai.tool({
3949
+ description: desc("register_bank_account"),
3950
+ inputSchema: zod.z.object({
3951
+ cbu: zod.z.string().regex(/^\d{22}$/),
3952
+ alias: zod.z.string().optional()
3953
+ }),
3954
+ execute: async (input) => {
3955
+ const params = {
3956
+ cbu: input.cbu
3957
+ };
3958
+ if (input.alias !== void 0) params.alias = input.alias;
3959
+ return client.registerBankAccount(params);
3960
+ }
3961
+ }),
3962
+ // ─────────────────────────────────────────────────────────────────────────
3963
+ // v0.7 — Point Devices físicos
3964
+ // ─────────────────────────────────────────────────────────────────────────
3965
+ list_point_devices: ai.tool({
3966
+ description: desc("list_point_devices"),
3967
+ inputSchema: zod.z.object({
3968
+ pos_id: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
3969
+ limit: zod.z.number().int().min(1).max(100).optional(),
3970
+ offset: zod.z.number().int().min(0).optional()
3971
+ }),
3972
+ execute: async ({ pos_id, limit, offset }) => {
3973
+ const params = {};
3974
+ if (pos_id !== void 0) params.posId = pos_id;
3975
+ if (limit !== void 0) params.limit = limit;
3976
+ if (offset !== void 0) params.offset = offset;
3977
+ return client.listPointDevices(params);
3978
+ }
3979
+ }),
3980
+ update_point_device_mode: ai.tool({
3981
+ description: desc("update_point_device_mode"),
3982
+ inputSchema: zod.z.object({
3983
+ device_id: zod.z.string(),
3984
+ operating_mode: zod.z.enum(["PDV", "STANDALONE"])
3985
+ }),
3986
+ execute: async ({ device_id, operating_mode }) => {
3987
+ return client.updatePointDeviceOperatingMode(device_id, operating_mode);
3988
+ }
3989
+ }),
3990
+ create_point_payment_intent: ai.tool({
3991
+ description: desc("create_point_payment_intent"),
3992
+ inputSchema: zod.z.object({
3993
+ device_id: zod.z.string(),
3994
+ amount_centavos: zod.z.number().int().positive().describe("Amount in CENTAVOS (NOT pesos). 100 = $1, 1000 = $10, 10000 = $100."),
3995
+ description: zod.z.string().optional(),
3996
+ external_reference: zod.z.string().optional(),
3997
+ installments: zod.z.number().int().min(1).max(24).optional(),
3998
+ installments_cost: zod.z.enum(["seller", "buyer"]).optional(),
3999
+ print_on_terminal: zod.z.boolean().optional(),
4000
+ ticket_number: zod.z.string().optional()
4001
+ }),
4002
+ execute: async (input) => {
4003
+ const params = {
4004
+ amount: input.amount_centavos
4005
+ };
4006
+ if (input.description !== void 0) params.description = input.description;
4007
+ if (input.external_reference !== void 0) params.externalReference = input.external_reference;
4008
+ if (input.installments !== void 0) params.installments = input.installments;
4009
+ if (input.installments_cost !== void 0) params.installmentsCost = input.installments_cost;
4010
+ if (input.print_on_terminal !== void 0) params.printOnTerminal = input.print_on_terminal;
4011
+ if (input.ticket_number !== void 0) params.ticketNumber = input.ticket_number;
4012
+ return client.createPointPaymentIntent(input.device_id, params);
4013
+ }
4014
+ }),
4015
+ get_point_payment_intent: ai.tool({
4016
+ description: desc("get_point_payment_intent"),
4017
+ inputSchema: zod.z.object({ intent_id: zod.z.string() }),
4018
+ execute: async ({ intent_id }) => {
4019
+ return client.getPointPaymentIntent(intent_id);
4020
+ }
4021
+ }),
4022
+ cancel_point_payment_intent: ai.tool({
4023
+ description: desc("cancel_point_payment_intent"),
4024
+ inputSchema: zod.z.object({
4025
+ device_id: zod.z.string(),
4026
+ intent_id: zod.z.string()
4027
+ }),
4028
+ execute: async ({ device_id, intent_id }) => {
4029
+ return client.cancelPointPaymentIntent(device_id, intent_id);
4030
+ }
4031
+ }),
4032
+ // ─────────────────────────────────────────────────────────────────────────
4033
+ // v0.7 — Pure helpers
4034
+ // ─────────────────────────────────────────────────────────────────────────
4035
+ compute_marketplace_fee: ai.tool({
4036
+ description: desc("compute_marketplace_fee"),
4037
+ inputSchema: zod.z.object({
4038
+ amount_ars: zod.z.number().positive(),
4039
+ flat_ars: zod.z.number().nonnegative().optional(),
4040
+ percent: zod.z.number().min(0).max(100).optional(),
4041
+ min_ars: zod.z.number().nonnegative().optional(),
4042
+ max_ars: zod.z.number().nonnegative().optional(),
4043
+ round: zod.z.boolean().optional()
4044
+ }),
4045
+ execute: async (input) => {
4046
+ const rule = {};
4047
+ if (input.flat_ars !== void 0) rule.flatArs = input.flat_ars;
4048
+ if (input.percent !== void 0) rule.percent = input.percent;
4049
+ if (input.min_ars !== void 0) rule.minArs = input.min_ars;
4050
+ if (input.max_ars !== void 0) rule.maxArs = input.max_ars;
4051
+ if (input.round !== void 0) rule.round = input.round;
4052
+ const fee = computeMarketplaceFee(input.amount_ars, rule);
4053
+ return {
4054
+ amount_ars: input.amount_ars,
4055
+ marketplace_fee: fee,
4056
+ seller_receives: input.amount_ars - fee,
4057
+ rule_applied: rule
4058
+ };
4059
+ }
4060
+ }),
4061
+ explain_payment_status: ai.tool({
4062
+ description: desc("explain_payment_status"),
4063
+ inputSchema: zod.z.object({
4064
+ payment_id: zod.z.string().optional().describe("If provided, fetches the Payment first."),
4065
+ payment: zod.z.record(zod.z.string(), zod.z.unknown()).optional().describe("Alternatively, pass a Payment object directly (saves a network round-trip).")
4066
+ }),
4067
+ execute: async ({ payment_id, payment }) => {
4068
+ let p;
4069
+ if (payment) {
4070
+ p = payment;
4071
+ } else if (payment_id) {
4072
+ p = await client.getPayment(payment_id);
4073
+ } else {
4074
+ return {
4075
+ ok: false,
4076
+ error: "Pass either payment_id or payment."
4077
+ };
4078
+ }
4079
+ const explanation = explainPaymentStatus(p);
4080
+ return {
4081
+ ok: true,
4082
+ payment_status: p.status,
4083
+ payment_status_detail: p.status_detail ?? null,
4084
+ ...explanation
4085
+ };
4086
+ }
3106
4087
  })
3107
4088
  };
3108
4089
  }
@@ -3125,7 +4106,50 @@ var InMemoryStateAdapter = class {
3125
4106
  this.store.clear();
3126
4107
  }
3127
4108
  };
4109
+ var InMemoryOAuthTokenStore = class {
4110
+ store = /* @__PURE__ */ new Map();
4111
+ async set(userId, token) {
4112
+ this.store.set(userId, token);
4113
+ }
4114
+ async get(userId) {
4115
+ return this.store.get(userId) ?? null;
4116
+ }
4117
+ async delete(userId) {
4118
+ this.store.delete(userId);
4119
+ }
4120
+ async list() {
4121
+ return Array.from(this.store.keys());
4122
+ }
4123
+ /** Test helper. */
4124
+ reset() {
4125
+ this.store.clear();
4126
+ }
4127
+ };
4128
+ var InMemoryIdempotencyCache = class {
4129
+ store = /* @__PURE__ */ new Map();
4130
+ async get(key) {
4131
+ const entry = this.store.get(key);
4132
+ if (!entry) return null;
4133
+ if (Date.now() > entry.expiresAt) {
4134
+ this.store.delete(key);
4135
+ return null;
4136
+ }
4137
+ return entry.value;
4138
+ }
4139
+ async set(key, value, ttlSeconds = 86400) {
4140
+ this.store.set(key, { value, expiresAt: Date.now() + ttlSeconds * 1e3 });
4141
+ }
4142
+ async delete(key) {
4143
+ this.store.delete(key);
4144
+ }
4145
+ /** Test helper. */
4146
+ reset() {
4147
+ this.store.clear();
4148
+ }
4149
+ };
3128
4150
 
4151
+ exports.InMemoryIdempotencyCache = InMemoryIdempotencyCache;
4152
+ exports.InMemoryOAuthTokenStore = InMemoryOAuthTokenStore;
3129
4153
  exports.InMemoryStateAdapter = InMemoryStateAdapter;
3130
4154
  exports.MercadoPagoAccountTypeMismatchError = MercadoPagoAccountTypeMismatchError;
3131
4155
  exports.MercadoPagoAuthError = MercadoPagoAuthError;
@@ -3144,8 +4168,10 @@ exports.analyze3DS = analyze3DS;
3144
4168
  exports.buildAuthorizeUrl = buildAuthorizeUrl;
3145
4169
  exports.buildTestCardScenario = buildTestCardScenario;
3146
4170
  exports.classifyError = classifyError;
4171
+ exports.computeMarketplaceFee = computeMarketplaceFee;
3147
4172
  exports.exchangeCodeForToken = exchangeCodeForToken;
3148
4173
  exports.expirationTimeMs = expirationTimeMs;
4174
+ exports.explainPaymentStatus = explainPaymentStatus;
3149
4175
  exports.isExpiringSoon = isExpiringSoon;
3150
4176
  exports.mercadoPagoTools = mercadoPagoTools;
3151
4177
  exports.parseWebhookEvent = parseWebhookEvent;