@ar-agents/mercadopago 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/dist/index.cjs +367 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -2
- package/dist/index.d.ts +138 -2
- package/dist/index.js +366 -22
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual, createHash } from 'crypto';
|
|
1
2
|
import { tool } from 'ai';
|
|
2
3
|
import { z } from 'zod';
|
|
3
|
-
import { createHmac, timingSafeEqual } from 'crypto';
|
|
4
4
|
|
|
5
5
|
// src/errors.ts
|
|
6
6
|
var MercadoPagoError = class extends Error {
|
|
@@ -98,12 +98,38 @@ var MercadoPagoRateLimitError = class extends MercadoPagoError {
|
|
|
98
98
|
}
|
|
99
99
|
retryAfterSeconds;
|
|
100
100
|
};
|
|
101
|
+
var MercadoPagoOverloadedError = class extends MercadoPagoError {
|
|
102
|
+
constructor(endpoint, status) {
|
|
103
|
+
super(
|
|
104
|
+
`Mercado Pago appears overloaded \u2014 returned a non-JSON ${status} response for ${endpoint}. Wait a few seconds and retry.`,
|
|
105
|
+
status,
|
|
106
|
+
endpoint
|
|
107
|
+
);
|
|
108
|
+
this.name = "MercadoPagoOverloadedError";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var MercadoPagoTimeoutError = class extends MercadoPagoError {
|
|
112
|
+
constructor(endpoint, timeoutMs) {
|
|
113
|
+
super(
|
|
114
|
+
`Mercado Pago request timed out after ${timeoutMs}ms on ${endpoint}. Increase requestTimeoutMs or check connectivity.`,
|
|
115
|
+
0,
|
|
116
|
+
endpoint
|
|
117
|
+
);
|
|
118
|
+
this.timeoutMs = timeoutMs;
|
|
119
|
+
this.name = "MercadoPagoTimeoutError";
|
|
120
|
+
}
|
|
121
|
+
timeoutMs;
|
|
122
|
+
};
|
|
101
123
|
function classifyError(status, endpoint, body, context) {
|
|
102
124
|
const bodyText = typeof body === "string" ? body : body && typeof body === "object" ? JSON.stringify(body) : "";
|
|
103
125
|
const lower = bodyText.toLowerCase();
|
|
104
126
|
if (status === 401) return new MercadoPagoAuthError(endpoint, body);
|
|
105
127
|
if (status === 429) {
|
|
106
|
-
|
|
128
|
+
let retryAfter = null;
|
|
129
|
+
const obj = body;
|
|
130
|
+
if (obj?.retry_after) retryAfter = Number(obj.retry_after);
|
|
131
|
+
else if (obj?.["retry-after"]) retryAfter = Number(obj["retry-after"]);
|
|
132
|
+
return new MercadoPagoRateLimitError(endpoint, retryAfter, body);
|
|
107
133
|
}
|
|
108
134
|
if (status === 400) {
|
|
109
135
|
if (lower.includes("back_url") && lower.includes("not a valid url")) {
|
|
@@ -129,10 +155,16 @@ function classifyError(status, endpoint, body, context) {
|
|
|
129
155
|
|
|
130
156
|
// src/client.ts
|
|
131
157
|
var DEFAULT_BASE_URL = "https://api.mercadopago.com";
|
|
158
|
+
function sleep(ms) {
|
|
159
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
160
|
+
}
|
|
132
161
|
var MercadoPagoClient = class {
|
|
133
162
|
accessToken;
|
|
134
163
|
baseUrl;
|
|
135
164
|
fetchImpl;
|
|
165
|
+
requestTimeoutMs;
|
|
166
|
+
maxRetries;
|
|
167
|
+
onCall;
|
|
136
168
|
constructor(options) {
|
|
137
169
|
if (!options.accessToken) {
|
|
138
170
|
throw new Error(
|
|
@@ -142,6 +174,9 @@ var MercadoPagoClient = class {
|
|
|
142
174
|
this.accessToken = options.accessToken;
|
|
143
175
|
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
144
176
|
this.fetchImpl = options.fetch;
|
|
177
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
178
|
+
this.maxRetries = Math.max(0, options.maxRetries ?? 1);
|
|
179
|
+
this.onCall = options.onCall;
|
|
145
180
|
}
|
|
146
181
|
async request(method, path, body, options) {
|
|
147
182
|
const headers = {
|
|
@@ -151,10 +186,6 @@ var MercadoPagoClient = class {
|
|
|
151
186
|
if (options?.idempotencyKey) {
|
|
152
187
|
headers["X-Idempotency-Key"] = options.idempotencyKey;
|
|
153
188
|
}
|
|
154
|
-
const init = { method, headers };
|
|
155
|
-
if (body !== void 0) {
|
|
156
|
-
init.body = JSON.stringify(body);
|
|
157
|
-
}
|
|
158
189
|
let url = `${this.baseUrl}${path}`;
|
|
159
190
|
if (options?.query) {
|
|
160
191
|
const search = new URLSearchParams();
|
|
@@ -167,20 +198,95 @@ var MercadoPagoClient = class {
|
|
|
167
198
|
if (qs) url += `?${qs}`;
|
|
168
199
|
}
|
|
169
200
|
const fetchFn = this.fetchImpl ?? globalThis.fetch;
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
201
|
+
const t0 = Date.now();
|
|
202
|
+
let attempt = 0;
|
|
203
|
+
let lastError;
|
|
204
|
+
let lastStatus = null;
|
|
205
|
+
while (attempt <= this.maxRetries) {
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
208
|
+
const init = { method, headers, signal: controller.signal };
|
|
209
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
174
210
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
211
|
+
const res = await fetchFn(url, init);
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
lastStatus = res.status;
|
|
214
|
+
if (res.ok) {
|
|
215
|
+
const text2 = await res.text();
|
|
216
|
+
this.onCall?.({
|
|
217
|
+
method,
|
|
218
|
+
path,
|
|
219
|
+
durationMs: Date.now() - t0,
|
|
220
|
+
httpStatus: res.status,
|
|
221
|
+
retried: attempt,
|
|
222
|
+
success: true
|
|
223
|
+
});
|
|
224
|
+
if (!text2) return void 0;
|
|
225
|
+
return JSON.parse(text2);
|
|
226
|
+
}
|
|
227
|
+
const isRetryable = res.status >= 500 || res.status === 429;
|
|
228
|
+
if (isRetryable && attempt < this.maxRetries) {
|
|
229
|
+
const retryAfter = res.headers.get("retry-after");
|
|
230
|
+
const waitMs = retryAfter ? Number(retryAfter) * 1e3 : 250 * Math.pow(2, attempt);
|
|
231
|
+
attempt++;
|
|
232
|
+
await sleep(waitMs);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
236
|
+
if (res.status >= 500 && !contentType.includes("application/json")) {
|
|
237
|
+
this.onCall?.({
|
|
238
|
+
method,
|
|
239
|
+
path,
|
|
240
|
+
durationMs: Date.now() - t0,
|
|
241
|
+
httpStatus: res.status,
|
|
242
|
+
retried: attempt,
|
|
243
|
+
success: false
|
|
244
|
+
});
|
|
245
|
+
throw new MercadoPagoOverloadedError(path, res.status);
|
|
246
|
+
}
|
|
247
|
+
let parsed;
|
|
248
|
+
const text = await res.text();
|
|
249
|
+
try {
|
|
250
|
+
parsed = JSON.parse(text);
|
|
251
|
+
} catch {
|
|
252
|
+
parsed = text;
|
|
253
|
+
}
|
|
254
|
+
const err = classifyError(res.status, path, parsed, options?.classifyContext);
|
|
255
|
+
this.onCall?.({
|
|
256
|
+
method,
|
|
257
|
+
path,
|
|
258
|
+
durationMs: Date.now() - t0,
|
|
259
|
+
httpStatus: res.status,
|
|
260
|
+
retried: attempt,
|
|
261
|
+
success: false
|
|
262
|
+
});
|
|
263
|
+
throw err;
|
|
264
|
+
} catch (err) {
|
|
265
|
+
clearTimeout(timer);
|
|
266
|
+
if (err instanceof MercadoPagoError) throw err;
|
|
267
|
+
const isAbort = err instanceof Error && err.name === "AbortError";
|
|
268
|
+
const isNetwork = !lastStatus && !isAbort;
|
|
269
|
+
if ((isNetwork || isAbort) && attempt < this.maxRetries) {
|
|
270
|
+
lastError = err;
|
|
271
|
+
attempt++;
|
|
272
|
+
await sleep(250 * Math.pow(2, attempt - 1));
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
this.onCall?.({
|
|
276
|
+
method,
|
|
277
|
+
path,
|
|
278
|
+
durationMs: Date.now() - t0,
|
|
279
|
+
httpStatus: lastStatus,
|
|
280
|
+
retried: attempt,
|
|
281
|
+
success: false
|
|
282
|
+
});
|
|
283
|
+
if (isAbort) {
|
|
284
|
+
throw new MercadoPagoTimeoutError(path, this.requestTimeoutMs);
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
178
287
|
}
|
|
179
|
-
throw classifyError(res.status, path, parsed, options?.classifyContext);
|
|
180
288
|
}
|
|
181
|
-
|
|
182
|
-
if (!text) return void 0;
|
|
183
|
-
return JSON.parse(text);
|
|
289
|
+
throw lastError ?? new Error(`MercadoPago request failed after ${this.maxRetries} retries`);
|
|
184
290
|
}
|
|
185
291
|
// ───────────────────────────────────────────────────────────────────────────
|
|
186
292
|
// Subscriptions (Preapprovals) — v0.1 surface, kept stable
|
|
@@ -498,7 +604,119 @@ var MercadoPagoClient = class {
|
|
|
498
604
|
async getMe() {
|
|
499
605
|
return this.request("GET", "/users/me");
|
|
500
606
|
}
|
|
607
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
608
|
+
// Card tokens (server-side, for saved-card retokenization)
|
|
609
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
610
|
+
/**
|
|
611
|
+
* Create a single-use card token from a saved card. This is the server-side
|
|
612
|
+
* retokenization path (PCI-safe because the card data lives in MP's vault,
|
|
613
|
+
* we only pass the saved card_id + customer_id + the user-supplied CVV).
|
|
614
|
+
*
|
|
615
|
+
* Tokens expire in 7 days but typically burn on first use. AR currently
|
|
616
|
+
* REQUIRES CVV on every charge (MP doesn't store it); skipping CVV requires
|
|
617
|
+
* a private MP product enablement, not a public API.
|
|
618
|
+
*/
|
|
619
|
+
async createCardToken(params) {
|
|
620
|
+
return this.request("POST", "/v1/card_tokens", {
|
|
621
|
+
card_id: params.cardId,
|
|
622
|
+
customer_id: params.customerId,
|
|
623
|
+
security_code: params.securityCode
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* High-level helper: charge a saved card in 3 steps.
|
|
628
|
+
* 1. Mint a card token from {customer_id, card_id, security_code}
|
|
629
|
+
* 2. Lookup card to fill payment_method_id (avoids agent guessing)
|
|
630
|
+
* 3. Create the payment with the token + idempotency key
|
|
631
|
+
*
|
|
632
|
+
* Returns the resulting Payment. Uses deterministic idempotency from
|
|
633
|
+
* (card_id, amount, externalReference) so retries dedupe on MP's side.
|
|
634
|
+
*/
|
|
635
|
+
async chargeSavedCard(params) {
|
|
636
|
+
const token = await this.createCardToken({
|
|
637
|
+
cardId: params.cardId,
|
|
638
|
+
customerId: params.customerId,
|
|
639
|
+
securityCode: params.securityCode
|
|
640
|
+
});
|
|
641
|
+
const card = await this.getCustomerCard(params.customerId, params.cardId);
|
|
642
|
+
const paymentMethodId = card.payment_method?.id;
|
|
643
|
+
if (!paymentMethodId) {
|
|
644
|
+
throw new MercadoPagoError(
|
|
645
|
+
`Saved card ${params.cardId} has no payment_method.id. Cannot charge.`,
|
|
646
|
+
0,
|
|
647
|
+
`/v1/customers/${params.customerId}/cards/${params.cardId}`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
const body = {
|
|
651
|
+
transaction_amount: params.amount,
|
|
652
|
+
token: token.id,
|
|
653
|
+
payment_method_id: paymentMethodId,
|
|
654
|
+
installments: params.installments ?? 1,
|
|
655
|
+
description: params.description,
|
|
656
|
+
payer: { type: "customer", id: params.customerId }
|
|
657
|
+
};
|
|
658
|
+
if (params.externalReference) body.external_reference = params.externalReference;
|
|
659
|
+
if (params.statementDescriptor) body.statement_descriptor = params.statementDescriptor;
|
|
660
|
+
return this.request("POST", "/v1/payments", body, {
|
|
661
|
+
...params.idempotencyKey !== void 0 ? { idempotencyKey: params.idempotencyKey } : {},
|
|
662
|
+
classifyContext: { customerId: params.customerId }
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
666
|
+
// QR (in-store dynamic) — Section 2 of v0.3 spec
|
|
667
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
668
|
+
/**
|
|
669
|
+
* Create a dynamic in-store QR order. Returns `qr_data` (EMVCo TLV string)
|
|
670
|
+
* + `in_store_order_id`. The buyer scans the QR with any AR wallet (Modo,
|
|
671
|
+
* BNA+, Cuenta DNI, Naranja X, etc. — interop is mandated by Transferencias
|
|
672
|
+
* 3.0). On payment, MP fires `point_integration_wh` then `payment` topics.
|
|
673
|
+
*
|
|
674
|
+
* Requires a pre-configured POS (`external_pos_id` from MP dashboard or
|
|
675
|
+
* `POST /pos`). The seller's `user_id` is auto-fetched from `/users/me`.
|
|
676
|
+
*
|
|
677
|
+
* The lib does NOT render the QR image — pass `qr_data` to a QR renderer
|
|
678
|
+
* (e.g., `qrcode` package) to get a data URL. The agent tool layer wraps
|
|
679
|
+
* this and returns both raw + data URL.
|
|
680
|
+
*/
|
|
681
|
+
async createQrPayment(userId, params) {
|
|
682
|
+
const body = {
|
|
683
|
+
total_amount: params.totalAmount,
|
|
684
|
+
title: params.title
|
|
685
|
+
};
|
|
686
|
+
if (params.description) body.description = params.description;
|
|
687
|
+
if (params.notificationUrl) body.notification_url = params.notificationUrl;
|
|
688
|
+
if (params.externalReference) body.external_reference = params.externalReference;
|
|
689
|
+
if (params.expirationDate) body.expiration_date = params.expirationDate;
|
|
690
|
+
body.items = params.items ?? [
|
|
691
|
+
{
|
|
692
|
+
title: params.title,
|
|
693
|
+
quantity: 1,
|
|
694
|
+
unit_price: params.totalAmount,
|
|
695
|
+
unit_measure: "unit",
|
|
696
|
+
total_amount: params.totalAmount
|
|
697
|
+
}
|
|
698
|
+
];
|
|
699
|
+
return this.request(
|
|
700
|
+
"PUT",
|
|
701
|
+
`/instore/orders/qr/seller/collectors/${encodeURIComponent(userId)}/pos/${encodeURIComponent(params.externalPosId)}/qrs`,
|
|
702
|
+
body
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Cancel a pending QR order on a POS. Necessary if the buyer never scans
|
|
707
|
+
* — otherwise the next `createQrPayment` on the same POS returns 409.
|
|
708
|
+
*/
|
|
709
|
+
async cancelQrPayment(userId, externalPosId) {
|
|
710
|
+
await this.request(
|
|
711
|
+
"DELETE",
|
|
712
|
+
`/instore/orders/qr/seller/collectors/${encodeURIComponent(userId)}/pos/${encodeURIComponent(externalPosId)}/qrs`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
501
715
|
};
|
|
716
|
+
function deterministicIdempotencyKey(...parts) {
|
|
717
|
+
const payload = parts.filter((p) => p !== void 0 && p !== null).map(String).join("|");
|
|
718
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 32);
|
|
719
|
+
}
|
|
502
720
|
var DEFAULT_DESCRIPTIONS = {
|
|
503
721
|
// ── Subscriptions ────────────────────────────────────────────────────────
|
|
504
722
|
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.",
|
|
@@ -527,7 +745,12 @@ var DEFAULT_DESCRIPTIONS = {
|
|
|
527
745
|
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.",
|
|
528
746
|
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.",
|
|
529
747
|
// ── Account ──────────────────────────────────────────────────────────────
|
|
530
|
-
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."
|
|
748
|
+
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.",
|
|
749
|
+
// ── Saved-card charging (v0.3) ───────────────────────────────────────────
|
|
750
|
+
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.",
|
|
751
|
+
// ── QR in-store (v0.3) ───────────────────────────────────────────────────
|
|
752
|
+
create_qr_payment: "Generate a dynamic in-store QR for a buyer to scan with any AR wallet (Modo, BNA+, Cuenta DNI, Naranja X, Mercado Pago, etc. \u2014 interop is mandated by Transferencias 3.0). Requires a pre-configured POS external_id (one-time setup in MP dashboard). Returns the qr_data string + a base64 PNG data URL ready to display. The QR expires in `expires_in_seconds` (default 600). MP fires `point_integration_wh` then `payment` webhooks when scanned.",
|
|
753
|
+
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."
|
|
531
754
|
};
|
|
532
755
|
function mercadoPagoTools(client, options) {
|
|
533
756
|
const desc = (name) => options.descriptions?.[name] ?? DEFAULT_DESCRIPTIONS[name];
|
|
@@ -669,8 +892,15 @@ function mercadoPagoTools(client, options) {
|
|
|
669
892
|
...input.identification !== void 0 ? { identification: input.identification } : {},
|
|
670
893
|
...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
|
|
671
894
|
...options.notificationUrl !== void 0 ? { notificationUrl: options.notificationUrl } : {},
|
|
672
|
-
//
|
|
673
|
-
|
|
895
|
+
// Deterministic idempotency key — safe to retry, same inputs always
|
|
896
|
+
// produce the same key (MP dedupes on its side).
|
|
897
|
+
idempotencyKey: deterministicIdempotencyKey(
|
|
898
|
+
"create_payment",
|
|
899
|
+
input.external_reference ?? input.payer_email,
|
|
900
|
+
input.amount_ars,
|
|
901
|
+
input.payment_method_id,
|
|
902
|
+
input.token
|
|
903
|
+
)
|
|
674
904
|
});
|
|
675
905
|
return {
|
|
676
906
|
payment_id: payment.id,
|
|
@@ -787,7 +1017,7 @@ function mercadoPagoTools(client, options) {
|
|
|
787
1017
|
const refund = await client.createRefund({
|
|
788
1018
|
paymentId: payment_id,
|
|
789
1019
|
...amount_ars !== void 0 ? { amount: amount_ars } : {},
|
|
790
|
-
idempotencyKey:
|
|
1020
|
+
idempotencyKey: deterministicIdempotencyKey("refund", payment_id, amount_ars ?? "full")
|
|
791
1021
|
});
|
|
792
1022
|
return {
|
|
793
1023
|
refund_id: refund.id,
|
|
@@ -1027,6 +1257,109 @@ function mercadoPagoTools(client, options) {
|
|
|
1027
1257
|
user_type: me.user_type
|
|
1028
1258
|
};
|
|
1029
1259
|
}
|
|
1260
|
+
}),
|
|
1261
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1262
|
+
// Saved-card charging (v0.3)
|
|
1263
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1264
|
+
charge_saved_card: tool({
|
|
1265
|
+
description: desc("charge_saved_card"),
|
|
1266
|
+
inputSchema: z.object({
|
|
1267
|
+
customer_id: z.string().describe("MP customer id (from create_customer / find_customer_by_email)"),
|
|
1268
|
+
card_id: z.string().describe("Saved card id (from list_customer_cards)"),
|
|
1269
|
+
security_code: z.string().regex(/^\d{3,4}$/).describe("CVV \u2014 3 digits (Visa/Master) or 4 (Amex). User must provide this each charge in AR."),
|
|
1270
|
+
amount_ars: z.number().positive(),
|
|
1271
|
+
description: z.string().min(1).max(255),
|
|
1272
|
+
installments: z.number().int().min(1).max(24).optional().describe("Default 1. Use calculate_installments first to pick a valid count."),
|
|
1273
|
+
external_reference: z.string().optional(),
|
|
1274
|
+
statement_descriptor: z.string().max(13).optional()
|
|
1275
|
+
}),
|
|
1276
|
+
execute: async (input) => {
|
|
1277
|
+
const payment = await client.chargeSavedCard({
|
|
1278
|
+
customerId: input.customer_id,
|
|
1279
|
+
cardId: input.card_id,
|
|
1280
|
+
securityCode: input.security_code,
|
|
1281
|
+
amount: input.amount_ars,
|
|
1282
|
+
description: input.description,
|
|
1283
|
+
...input.installments !== void 0 ? { installments: input.installments } : {},
|
|
1284
|
+
...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
|
|
1285
|
+
...input.statement_descriptor !== void 0 ? { statementDescriptor: input.statement_descriptor } : {},
|
|
1286
|
+
idempotencyKey: deterministicIdempotencyKey(
|
|
1287
|
+
"charge_saved_card",
|
|
1288
|
+
input.card_id,
|
|
1289
|
+
input.amount_ars,
|
|
1290
|
+
input.external_reference
|
|
1291
|
+
)
|
|
1292
|
+
});
|
|
1293
|
+
return {
|
|
1294
|
+
payment_id: payment.id,
|
|
1295
|
+
status: payment.status,
|
|
1296
|
+
status_detail: payment.status_detail,
|
|
1297
|
+
amount: payment.transaction_amount,
|
|
1298
|
+
installments: payment.installments,
|
|
1299
|
+
payment_method: payment.payment_method_id,
|
|
1300
|
+
customer_id: input.customer_id,
|
|
1301
|
+
card_id: input.card_id,
|
|
1302
|
+
external_reference: payment.external_reference,
|
|
1303
|
+
date_approved: payment.date_approved
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
}),
|
|
1307
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1308
|
+
// QR in-store (v0.3)
|
|
1309
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1310
|
+
create_qr_payment: tool({
|
|
1311
|
+
description: desc("create_qr_payment"),
|
|
1312
|
+
inputSchema: z.object({
|
|
1313
|
+
external_pos_id: z.string().describe("Pre-configured POS external_id from MP dashboard. Required."),
|
|
1314
|
+
amount_ars: z.number().positive(),
|
|
1315
|
+
title: z.string().min(1).max(80).describe("Display title shown when scanning"),
|
|
1316
|
+
description: z.string().max(255).optional(),
|
|
1317
|
+
external_reference: z.string().optional(),
|
|
1318
|
+
notification_url: z.string().url().optional().describe("Webhook URL \u2014 falls back to dashboard config if omitted"),
|
|
1319
|
+
expires_in_seconds: z.number().int().min(60).max(3600).optional().describe("Default 600 (10 min)")
|
|
1320
|
+
}),
|
|
1321
|
+
execute: async (input) => {
|
|
1322
|
+
const QRCode = (await import('qrcode')).default;
|
|
1323
|
+
const me = await client.getMe();
|
|
1324
|
+
const userId = String(me.id);
|
|
1325
|
+
const expiresAt = new Date(
|
|
1326
|
+
Date.now() + (input.expires_in_seconds ?? 600) * 1e3
|
|
1327
|
+
).toISOString();
|
|
1328
|
+
const qr = await client.createQrPayment(userId, {
|
|
1329
|
+
externalPosId: input.external_pos_id,
|
|
1330
|
+
totalAmount: input.amount_ars,
|
|
1331
|
+
title: input.title,
|
|
1332
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
1333
|
+
...input.external_reference !== void 0 ? { externalReference: input.external_reference } : {},
|
|
1334
|
+
...input.notification_url !== void 0 ? { notificationUrl: input.notification_url } : {},
|
|
1335
|
+
expirationDate: expiresAt
|
|
1336
|
+
});
|
|
1337
|
+
const qrDataUrl = await QRCode.toDataURL(qr.qr_data, {
|
|
1338
|
+
errorCorrectionLevel: "M",
|
|
1339
|
+
margin: 1,
|
|
1340
|
+
width: 512
|
|
1341
|
+
});
|
|
1342
|
+
return {
|
|
1343
|
+
in_store_order_id: qr.in_store_order_id,
|
|
1344
|
+
qr_data: qr.qr_data,
|
|
1345
|
+
qr_data_url: qrDataUrl,
|
|
1346
|
+
expires_at: expiresAt,
|
|
1347
|
+
external_pos_id: input.external_pos_id,
|
|
1348
|
+
amount: input.amount_ars,
|
|
1349
|
+
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."
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
}),
|
|
1353
|
+
cancel_qr_payment: tool({
|
|
1354
|
+
description: desc("cancel_qr_payment"),
|
|
1355
|
+
inputSchema: z.object({
|
|
1356
|
+
external_pos_id: z.string()
|
|
1357
|
+
}),
|
|
1358
|
+
execute: async ({ external_pos_id }) => {
|
|
1359
|
+
const me = await client.getMe();
|
|
1360
|
+
await client.cancelQrPayment(String(me.id), external_pos_id);
|
|
1361
|
+
return { external_pos_id, cancelled: true };
|
|
1362
|
+
}
|
|
1030
1363
|
})
|
|
1031
1364
|
};
|
|
1032
1365
|
}
|
|
@@ -1227,6 +1560,17 @@ z.object({
|
|
|
1227
1560
|
}).passthrough()
|
|
1228
1561
|
)
|
|
1229
1562
|
}).passthrough();
|
|
1563
|
+
z.object({
|
|
1564
|
+
in_store_order_id: z.string(),
|
|
1565
|
+
qr_data: z.string()
|
|
1566
|
+
}).passthrough();
|
|
1567
|
+
z.object({
|
|
1568
|
+
id: z.string(),
|
|
1569
|
+
status: z.string().optional(),
|
|
1570
|
+
date_due: z.string().optional(),
|
|
1571
|
+
card_id: z.string().optional(),
|
|
1572
|
+
cardholder: z.unknown().optional()
|
|
1573
|
+
}).passthrough();
|
|
1230
1574
|
z.object({
|
|
1231
1575
|
id: z.union([z.string(), z.number()]).transform(String),
|
|
1232
1576
|
email: z.string().nullable().optional(),
|
|
@@ -1267,6 +1611,6 @@ function verifyWebhookSignature(params) {
|
|
|
1267
1611
|
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
|
|
1268
1612
|
}
|
|
1269
1613
|
|
|
1270
|
-
export { InMemoryStateAdapter, MercadoPagoAccountTypeMismatchError, MercadoPagoAuthError, MercadoPagoAuthorizeForbiddenError, MercadoPagoBackUrlInvalidError, MercadoPagoClient, MercadoPagoError, MercadoPagoPaymentRejectedError, MercadoPagoRateLimitError, MercadoPagoSelfPaymentError, classifyError, mercadoPagoTools, parseWebhookEvent, verifyWebhookSignature };
|
|
1614
|
+
export { InMemoryStateAdapter, MercadoPagoAccountTypeMismatchError, MercadoPagoAuthError, MercadoPagoAuthorizeForbiddenError, MercadoPagoBackUrlInvalidError, MercadoPagoClient, MercadoPagoError, MercadoPagoOverloadedError, MercadoPagoPaymentRejectedError, MercadoPagoRateLimitError, MercadoPagoSelfPaymentError, MercadoPagoTimeoutError, classifyError, mercadoPagoTools, parseWebhookEvent, verifyWebhookSignature };
|
|
1271
1615
|
//# sourceMappingURL=index.js.map
|
|
1272
1616
|
//# sourceMappingURL=index.js.map
|