@yoryoboy/bi-mcp 1.16.0 → 1.17.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/.tsbuildinfo +1 -1
- package/dist/index.js +34 -1
- package/dist/index.js.map +2 -2
- package/dist/mcp-use.json +2 -2
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js +137 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-api.js +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-api.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-billing.js +93 -1
- package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-payments.js +60 -0
- package/dist/src/services/mercadolibre/mercadolibre-payments.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js +284 -0
- package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-full-storage-charges.js +183 -0
- package/dist/src/tools/mercadolibre/get-full-storage-charges.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-payment-details.js +322 -0
- package/dist/src/tools/mercadolibre/get-payment-details.js.map +7 -0
- package/dist/src/tools/mercadolibre/index.js +20 -0
- package/dist/src/tools/mercadolibre/index.js.map +2 -2
- package/dist/tests/meli/mercadolibre-billing-provisions.test.js +176 -0
- package/dist/tests/meli/mercadolibre-billing-provisions.test.js.map +2 -2
- package/dist/tests/meli/mercadolibre-payment-details.test.js +356 -0
- package/dist/tests/meli/mercadolibre-payment-details.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-payments.test.js +62 -0
- package/dist/tests/meli/mercadolibre-payments.test.js.map +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createMercadoLibreClient } from "../../../services/mercadolibre/mercadolibre-api.js";
|
|
3
|
+
import { getMercadoLibreAccessForProfile } from "../../../config/mercadolibre.js";
|
|
4
|
+
import { getMercadoLibrePaymentDetails, getMercadoLibrePaymentDetailsBatch } from "../mercadolibre-payments.js";
|
|
5
|
+
vi.mock("../../../services/mercadolibre/mercadolibre-api.js", async (importOriginal) => {
|
|
6
|
+
const actual = await importOriginal();
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
createMercadoLibreClient: vi.fn()
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
vi.mock("../../../config/mercadolibre.js", () => ({
|
|
13
|
+
getMercadoLibreAccessForProfile: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
describe("mercadolibre payments service", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
it("fetches payment details from the official payments endpoint", async () => {
|
|
20
|
+
const getMock = vi.fn().mockResolvedValue({
|
|
21
|
+
data: {
|
|
22
|
+
id: 28382111111,
|
|
23
|
+
status: "approved",
|
|
24
|
+
transaction_amount: 100
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({
|
|
28
|
+
accessToken: "stored-token"
|
|
29
|
+
});
|
|
30
|
+
vi.mocked(createMercadoLibreClient).mockReturnValue({ get: getMock });
|
|
31
|
+
await expect(getMercadoLibrePaymentDetails("profile-1", "28382111111")).resolves.toEqual({
|
|
32
|
+
id: 28382111111,
|
|
33
|
+
status: "approved",
|
|
34
|
+
transaction_amount: 100
|
|
35
|
+
});
|
|
36
|
+
expect(getMercadoLibreAccessForProfile).toHaveBeenCalledWith("profile-1");
|
|
37
|
+
expect(createMercadoLibreClient).toHaveBeenCalledWith("stored-token", "https://api.mercadopago.com");
|
|
38
|
+
expect(getMock).toHaveBeenCalledWith("/v1/payments/28382111111");
|
|
39
|
+
});
|
|
40
|
+
it("fetches payment details in parallel for batch requests and keeps failures isolated", async () => {
|
|
41
|
+
const deferredByPaymentId = /* @__PURE__ */ new Map();
|
|
42
|
+
const getMock = vi.fn().mockImplementation((path) => {
|
|
43
|
+
const paymentId = path.split("/").pop() ?? "";
|
|
44
|
+
if (paymentId === "28382111113") {
|
|
45
|
+
return Promise.reject(new Error("Request failed with status code 404"));
|
|
46
|
+
}
|
|
47
|
+
let resolvePromise;
|
|
48
|
+
const promise = new Promise((resolveFn) => {
|
|
49
|
+
resolvePromise = resolveFn;
|
|
50
|
+
});
|
|
51
|
+
deferredByPaymentId.set(paymentId, { resolve: resolvePromise });
|
|
52
|
+
return promise;
|
|
53
|
+
});
|
|
54
|
+
vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({
|
|
55
|
+
accessToken: "stored-token"
|
|
56
|
+
});
|
|
57
|
+
vi.mocked(createMercadoLibreClient).mockReturnValue({ get: getMock });
|
|
58
|
+
const batchPromise = getMercadoLibrePaymentDetailsBatch("profile-1", [
|
|
59
|
+
"28382111111",
|
|
60
|
+
"28382111112",
|
|
61
|
+
"28382111113"
|
|
62
|
+
]);
|
|
63
|
+
await Promise.resolve();
|
|
64
|
+
expect(getMock).toHaveBeenCalledTimes(3);
|
|
65
|
+
deferredByPaymentId.get("28382111111")?.resolve({
|
|
66
|
+
data: { id: "28382111111", status: "approved" }
|
|
67
|
+
});
|
|
68
|
+
deferredByPaymentId.get("28382111112")?.resolve({
|
|
69
|
+
data: { id: "28382111112", status: "pending" }
|
|
70
|
+
});
|
|
71
|
+
await expect(batchPromise).resolves.toEqual({
|
|
72
|
+
successful: [
|
|
73
|
+
{ paymentId: "28382111111", payment: { id: "28382111111", status: "approved" } },
|
|
74
|
+
{ paymentId: "28382111112", payment: { id: "28382111112", status: "pending" } }
|
|
75
|
+
],
|
|
76
|
+
failed: [
|
|
77
|
+
{
|
|
78
|
+
paymentId: "28382111113",
|
|
79
|
+
message: "Request failed with status code 404",
|
|
80
|
+
statusCode: void 0,
|
|
81
|
+
attempts: 1,
|
|
82
|
+
retryable: false
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
expect(getMercadoLibreAccessForProfile).toHaveBeenCalledWith("profile-1");
|
|
87
|
+
expect(createMercadoLibreClient).toHaveBeenCalledWith("stored-token", "https://api.mercadopago.com");
|
|
88
|
+
expect(getMock).toHaveBeenNthCalledWith(1, "/v1/payments/28382111111");
|
|
89
|
+
expect(getMock).toHaveBeenNthCalledWith(2, "/v1/payments/28382111112");
|
|
90
|
+
expect(getMock).toHaveBeenNthCalledWith(3, "/v1/payments/28382111113");
|
|
91
|
+
});
|
|
92
|
+
it("continues processing later chunks when earlier chunks contain failures", async () => {
|
|
93
|
+
const getMock = vi.fn().mockImplementation(async (path) => {
|
|
94
|
+
const paymentId = path.split("/").pop() ?? "";
|
|
95
|
+
if (paymentId === "pay-17" || paymentId === "pay-63") {
|
|
96
|
+
throw new Error(`Request failed with status code 404 for ${paymentId}`);
|
|
97
|
+
}
|
|
98
|
+
return { data: { id: paymentId } };
|
|
99
|
+
});
|
|
100
|
+
vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({
|
|
101
|
+
accessToken: "stored-token"
|
|
102
|
+
});
|
|
103
|
+
vi.mocked(createMercadoLibreClient).mockReturnValue({ get: getMock });
|
|
104
|
+
const paymentIds = Array.from({ length: 101 }, (_, index) => `pay-${index + 1}`);
|
|
105
|
+
const batchPromise = getMercadoLibrePaymentDetailsBatch("profile-1", paymentIds);
|
|
106
|
+
await expect(batchPromise).resolves.toEqual({
|
|
107
|
+
successful: expect.arrayContaining([
|
|
108
|
+
{ paymentId: "pay-1", payment: { id: "pay-1" } },
|
|
109
|
+
{ paymentId: "pay-101", payment: { id: "pay-101" } }
|
|
110
|
+
]),
|
|
111
|
+
failed: [
|
|
112
|
+
{
|
|
113
|
+
paymentId: "pay-17",
|
|
114
|
+
message: "Request failed with status code 404 for pay-17",
|
|
115
|
+
statusCode: void 0,
|
|
116
|
+
attempts: 1,
|
|
117
|
+
retryable: false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
paymentId: "pay-63",
|
|
121
|
+
message: "Request failed with status code 404 for pay-63",
|
|
122
|
+
statusCode: void 0,
|
|
123
|
+
attempts: 1,
|
|
124
|
+
retryable: false
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
expect(getMercadoLibreAccessForProfile).toHaveBeenCalledWith("profile-1");
|
|
129
|
+
expect(createMercadoLibreClient).toHaveBeenCalledWith("stored-token", "https://api.mercadopago.com");
|
|
130
|
+
expect(getMock).toHaveBeenCalledTimes(101);
|
|
131
|
+
expect(getMock).toHaveBeenNthCalledWith(1, "/v1/payments/pay-1");
|
|
132
|
+
expect(getMock).toHaveBeenNthCalledWith(50, "/v1/payments/pay-50");
|
|
133
|
+
expect(getMock).toHaveBeenNthCalledWith(51, "/v1/payments/pay-51");
|
|
134
|
+
expect(getMock).toHaveBeenNthCalledWith(101, "/v1/payments/pay-101");
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=mercadolibre-payments.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/services/mercadolibre/__tests__/mercadolibre-payments.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createMercadoLibreClient } from \"../../../services/mercadolibre/mercadolibre-api.js\";\nimport { getMercadoLibreAccessForProfile } from \"../../../config/mercadolibre.js\";\nimport { getMercadoLibrePaymentDetails, getMercadoLibrePaymentDetailsBatch } from \"../mercadolibre-payments.js\";\n\nvi.mock(\"../../../services/mercadolibre/mercadolibre-api.js\", async (importOriginal) => {\n const actual = await importOriginal<typeof import(\"../../../services/mercadolibre/mercadolibre-api.js\")>();\n\n return {\n ...actual,\n createMercadoLibreClient: vi.fn(),\n };\n});\n\nvi.mock(\"../../../config/mercadolibre.js\", () => ({\n getMercadoLibreAccessForProfile: vi.fn(),\n}));\n\ndescribe(\"mercadolibre payments service\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n it(\"fetches payment details from the official payments endpoint\", async () => {\n const getMock = vi.fn().mockResolvedValue({\n data: {\n id: 28382111111,\n status: \"approved\",\n transaction_amount: 100,\n },\n });\n\n vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({\n accessToken: \"stored-token\",\n } as never);\n vi.mocked(createMercadoLibreClient).mockReturnValue({ get: getMock } as never);\n\n await expect(getMercadoLibrePaymentDetails(\"profile-1\", \"28382111111\")).resolves.toEqual({\n id: 28382111111,\n status: \"approved\",\n transaction_amount: 100,\n });\n\n expect(getMercadoLibreAccessForProfile).toHaveBeenCalledWith(\"profile-1\");\n expect(createMercadoLibreClient).toHaveBeenCalledWith(\"stored-token\", \"https://api.mercadopago.com\");\n expect(getMock).toHaveBeenCalledWith(\"/v1/payments/28382111111\");\n });\n\n it(\"fetches payment details in parallel for batch requests and keeps failures isolated\", async () => {\n const deferredByPaymentId = new Map<string, { resolve: (value: { data: Record<string, unknown> }) => void }>();\n const getMock = vi.fn().mockImplementation((path: string) => {\n const paymentId = path.split(\"/\").pop() ?? \"\";\n if (paymentId === \"28382111113\") {\n return Promise.reject(new Error(\"Request failed with status code 404\"));\n }\n\n let resolvePromise!: (value: { data: Record<string, unknown> }) => void;\n\n const promise = new Promise<{ data: Record<string, unknown> }>((resolveFn) => {\n resolvePromise = resolveFn;\n });\n\n deferredByPaymentId.set(paymentId, { resolve: resolvePromise });\n return promise;\n });\n\n vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({\n accessToken: \"stored-token\",\n } as never);\n vi.mocked(createMercadoLibreClient).mockReturnValue({ get: getMock } as never);\n\n const batchPromise = getMercadoLibrePaymentDetailsBatch(\"profile-1\", [\n \"28382111111\",\n \"28382111112\",\n \"28382111113\",\n ]);\n\n await Promise.resolve();\n expect(getMock).toHaveBeenCalledTimes(3);\n\n deferredByPaymentId.get(\"28382111111\")?.resolve({\n data: { id: \"28382111111\", status: \"approved\" },\n });\n deferredByPaymentId.get(\"28382111112\")?.resolve({\n data: { id: \"28382111112\", status: \"pending\" },\n });\n\n await expect(batchPromise).resolves.toEqual({\n successful: [\n { paymentId: \"28382111111\", payment: { id: \"28382111111\", status: \"approved\" } },\n { paymentId: \"28382111112\", payment: { id: \"28382111112\", status: \"pending\" } },\n ],\n failed: [\n {\n paymentId: \"28382111113\",\n message: \"Request failed with status code 404\",\n statusCode: undefined,\n attempts: 1,\n retryable: false,\n },\n ],\n });\n\n expect(getMercadoLibreAccessForProfile).toHaveBeenCalledWith(\"profile-1\");\n expect(createMercadoLibreClient).toHaveBeenCalledWith(\"stored-token\", \"https://api.mercadopago.com\");\n expect(getMock).toHaveBeenNthCalledWith(1, \"/v1/payments/28382111111\");\n expect(getMock).toHaveBeenNthCalledWith(2, \"/v1/payments/28382111112\");\n expect(getMock).toHaveBeenNthCalledWith(3, \"/v1/payments/28382111113\");\n });\n\n it(\"continues processing later chunks when earlier chunks contain failures\", async () => {\n const getMock = vi.fn().mockImplementation(async (path: string) => {\n const paymentId = path.split(\"/\").pop() ?? \"\";\n\n if (paymentId === \"pay-17\" || paymentId === \"pay-63\") {\n throw new Error(`Request failed with status code 404 for ${paymentId}`);\n }\n\n return { data: { id: paymentId } };\n });\n\n vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({\n accessToken: \"stored-token\",\n } as never);\n vi.mocked(createMercadoLibreClient).mockReturnValue({ get: getMock } as never);\n\n const paymentIds = Array.from({ length: 101 }, (_, index) => `pay-${index + 1}`);\n const batchPromise = getMercadoLibrePaymentDetailsBatch(\"profile-1\", paymentIds);\n\n await expect(batchPromise).resolves.toEqual({\n successful: expect.arrayContaining([\n { paymentId: \"pay-1\", payment: { id: \"pay-1\" } },\n { paymentId: \"pay-101\", payment: { id: \"pay-101\" } },\n ]),\n failed: [\n {\n paymentId: \"pay-17\",\n message: \"Request failed with status code 404 for pay-17\",\n statusCode: undefined,\n attempts: 1,\n retryable: false,\n },\n {\n paymentId: \"pay-63\",\n message: \"Request failed with status code 404 for pay-63\",\n statusCode: undefined,\n attempts: 1,\n retryable: false,\n },\n ],\n });\n\n expect(getMercadoLibreAccessForProfile).toHaveBeenCalledWith(\"profile-1\");\n expect(createMercadoLibreClient).toHaveBeenCalledWith(\"stored-token\", \"https://api.mercadopago.com\");\n expect(getMock).toHaveBeenCalledTimes(101);\n expect(getMock).toHaveBeenNthCalledWith(1, \"/v1/payments/pay-1\");\n expect(getMock).toHaveBeenNthCalledWith(50, \"/v1/payments/pay-50\");\n expect(getMock).toHaveBeenNthCalledWith(51, \"/v1/payments/pay-51\");\n expect(getMock).toHaveBeenNthCalledWith(101, \"/v1/payments/pay-101\");\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -63,9 +63,9 @@ function formatMercadoLibreError(error, fallback = "Unexpected MercadoLibre API
|
|
|
63
63
|
}
|
|
64
64
|
return sanitizeMercadoLibreText(fallback);
|
|
65
65
|
}
|
|
66
|
-
function createMercadoLibreClient(accessToken) {
|
|
66
|
+
function createMercadoLibreClient(accessToken, baseURL = BASE_URL) {
|
|
67
67
|
const client = axios.create({
|
|
68
|
-
baseURL
|
|
68
|
+
baseURL,
|
|
69
69
|
timeout: 3e4,
|
|
70
70
|
headers: {
|
|
71
71
|
Accept: "application/json",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/services/mercadolibre/mercadolibre-api.ts"],
|
|
4
|
-
"sourcesContent": ["import axios from \"axios\";\nimport type { AxiosError, AxiosInstance, AxiosResponse } from \"axios\";\n\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\n\nconst BASE_URL = \"https://api.mercadolibre.com\";\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class MercadoLibreApiError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly code?: string,\n public readonly retryAfterMs?: number\n ) {\n super(message);\n this.name = \"MercadoLibreApiError\";\n }\n}\n\nexport interface MercadoLibreBatchFetchOptions {\n maxConcurrency?: number;\n maxRetries?: number;\n baseRetryDelayMs?: number;\n}\n\nexport interface MercadoLibreDocumentSuccess<T> {\n id: string;\n document: T;\n}\n\nexport interface MercadoLibreDocumentFailure {\n id: string;\n message: string;\n statusCode?: number;\n attempts: number;\n retryable: boolean;\n}\n\nexport interface MercadoLibreDocumentsBatchResult<T> {\n successful: Array<MercadoLibreDocumentSuccess<T>>;\n failed: MercadoLibreDocumentFailure[];\n}\n\nexport function sanitizeMercadoLibreText(value: unknown): string {\n const raw = typeof value === \"string\" ? value : String(value ?? \"MercadoLibre request failed\");\n return raw\n .replace(/access_token[=:][^\\s&]+/gi, \"access_token=[redacted]\")\n .replace(/refresh_token[=:][^\\s&]+/gi, \"refresh_token=[redacted]\")\n .replace(/client_secret[=:][^\\s&]+/gi, \"client_secret=[redacted]\")\n .replace(/code[=:][^\\s&]+/gi, \"code=[redacted]\")\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, \"Bearer [redacted]\")\n .slice(0, 220);\n}\n\nfunction extractRetryAfterMs(error: AxiosError): number | undefined {\n const rawHeader = error.response?.headers?.[\"retry-after\"];\n const candidate = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n if (!candidate) {\n return undefined;\n }\n\n const seconds = Number(candidate);\n if (Number.isFinite(seconds) && seconds > 0) {\n return seconds * 1000;\n }\n\n const dateMs = new Date(candidate).getTime();\n if (Number.isFinite(dateMs)) {\n return Math.max(0, dateMs - Date.now());\n }\n\n return undefined;\n}\n\nfunction buildApiError(error: AxiosError): MercadoLibreApiError {\n const status = error.response?.status;\n const responseData = error.response?.data;\n let code: string | undefined;\n let message: string | undefined;\n\n if (responseData && typeof responseData === \"object\") {\n const candidate = responseData as {\n error?: unknown;\n message?: unknown;\n error_description?: unknown;\n cause?: Array<{ code?: unknown; message?: unknown }>;\n };\n code = typeof candidate.error === \"string\" ? candidate.error : undefined;\n message =\n (typeof candidate.message === \"string\" ? candidate.message : undefined) ??\n (typeof candidate.error_description === \"string\" ? candidate.error_description : undefined) ??\n (Array.isArray(candidate.cause)\n ? candidate.cause\n .map((entry) => (typeof entry.message === \"string\" ? entry.message : null))\n .filter(Boolean)\n .join(\"; \")\n : undefined);\n } else if (typeof responseData === \"string\") {\n message = responseData;\n }\n\n const safeMessage = sanitizeMercadoLibreText(message ?? error.message ?? \"MercadoLibre request failed\");\n return new MercadoLibreApiError(\n `MercadoLibre API request failed${status ? ` (${status})` : \"\"}: ${safeMessage}`,\n status,\n code,\n extractRetryAfterMs(error)\n );\n}\n\nexport function formatMercadoLibreError(\n error: unknown,\n fallback = \"Unexpected MercadoLibre API error\"\n): string {\n if (error instanceof MercadoLibreApiError) {\n return error.message;\n }\n\n if (axios.isAxiosError(error)) {\n return buildApiError(error).message;\n }\n\n if (error instanceof Error) {\n return sanitizeMercadoLibreText(error.message);\n }\n\n return sanitizeMercadoLibreText(fallback);\n}\n\nexport function createMercadoLibreClient(accessToken: string): AxiosInstance {\n const client = axios.create({\n baseURL
|
|
5
|
-
"mappings": "AAAA,OAAO,WAAW;AAGlB,SAAS,uCAAuC;AAEhD,MAAM,WAAW;AACjB,MAAM,yBAAyB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAE9D,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YACE,SACgB,QACA,MACA,cAChB;AACA,UAAM,OAAO;AAJG;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AA0BO,SAAS,yBAAyB,OAAwB;AAC/D,QAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,OAAO,SAAS,6BAA6B;AAC7F,SAAO,IACJ,QAAQ,6BAA6B,yBAAyB,EAC9D,QAAQ,8BAA8B,0BAA0B,EAChE,QAAQ,8BAA8B,0BAA0B,EAChE,QAAQ,qBAAqB,iBAAiB,EAC9C,QAAQ,8BAA8B,mBAAmB,EACzD,MAAM,GAAG,GAAG;AACjB;AAEA,SAAS,oBAAoB,OAAuC;AAClE,QAAM,YAAY,MAAM,UAAU,UAAU,aAAa;AACzD,QAAM,YAAY,MAAM,QAAQ,SAAS,IAAI,UAAU,CAAC,IAAI;AAC5D,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,OAAO,SAAS;AAChC,MAAI,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC3C,WAAO,UAAU;AAAA,EACnB;AAEA,QAAM,SAAS,IAAI,KAAK,SAAS,EAAE,QAAQ;AAC3C,MAAI,OAAO,SAAS,MAAM,GAAG;AAC3B,WAAO,KAAK,IAAI,GAAG,SAAS,KAAK,IAAI,CAAC;AAAA,EACxC;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,OAAyC;AAC9D,QAAM,SAAS,MAAM,UAAU;AAC/B,QAAM,eAAe,MAAM,UAAU;AACrC,MAAI;AACJ,MAAI;AAEJ,MAAI,gBAAgB,OAAO,iBAAiB,UAAU;AACpD,UAAM,YAAY;AAMlB,WAAO,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ;AAC/D,eACG,OAAO,UAAU,YAAY,WAAW,UAAU,UAAU,YAC5D,OAAO,UAAU,sBAAsB,WAAW,UAAU,oBAAoB,YAChF,MAAM,QAAQ,UAAU,KAAK,IAC1B,UAAU,MACP,IAAI,CAAC,UAAW,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,IAAK,EACzE,OAAO,OAAO,EACd,KAAK,IAAI,IACZ;AAAA,EACR,WAAW,OAAO,iBAAiB,UAAU;AAC3C,cAAU;AAAA,EACZ;AAEA,QAAM,cAAc,yBAAyB,WAAW,MAAM,WAAW,6BAA6B;AACtG,SAAO,IAAI;AAAA,IACT,kCAAkC,SAAS,KAAK,MAAM,MAAM,EAAE,KAAK,WAAW;AAAA,IAC9E;AAAA,IACA;AAAA,IACA,oBAAoB,KAAK;AAAA,EAC3B;AACF;AAEO,SAAS,wBACd,OACA,WAAW,qCACH;AACR,MAAI,iBAAiB,sBAAsB;AACzC,WAAO,MAAM;AAAA,EACf;AAEA,MAAI,MAAM,aAAa,KAAK,GAAG;AAC7B,WAAO,cAAc,KAAK,EAAE;AAAA,EAC9B;AAEA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,yBAAyB,MAAM,OAAO;AAAA,EAC/C;AAEA,SAAO,yBAAyB,QAAQ;AAC1C;AAEO,SAAS,yBAAyB,
|
|
4
|
+
"sourcesContent": ["import axios from \"axios\";\nimport type { AxiosError, AxiosInstance, AxiosResponse } from \"axios\";\n\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\n\nconst BASE_URL = \"https://api.mercadolibre.com\";\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class MercadoLibreApiError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly code?: string,\n public readonly retryAfterMs?: number\n ) {\n super(message);\n this.name = \"MercadoLibreApiError\";\n }\n}\n\nexport interface MercadoLibreBatchFetchOptions {\n maxConcurrency?: number;\n maxRetries?: number;\n baseRetryDelayMs?: number;\n}\n\nexport interface MercadoLibreDocumentSuccess<T> {\n id: string;\n document: T;\n}\n\nexport interface MercadoLibreDocumentFailure {\n id: string;\n message: string;\n statusCode?: number;\n attempts: number;\n retryable: boolean;\n}\n\nexport interface MercadoLibreDocumentsBatchResult<T> {\n successful: Array<MercadoLibreDocumentSuccess<T>>;\n failed: MercadoLibreDocumentFailure[];\n}\n\nexport function sanitizeMercadoLibreText(value: unknown): string {\n const raw = typeof value === \"string\" ? value : String(value ?? \"MercadoLibre request failed\");\n return raw\n .replace(/access_token[=:][^\\s&]+/gi, \"access_token=[redacted]\")\n .replace(/refresh_token[=:][^\\s&]+/gi, \"refresh_token=[redacted]\")\n .replace(/client_secret[=:][^\\s&]+/gi, \"client_secret=[redacted]\")\n .replace(/code[=:][^\\s&]+/gi, \"code=[redacted]\")\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, \"Bearer [redacted]\")\n .slice(0, 220);\n}\n\nfunction extractRetryAfterMs(error: AxiosError): number | undefined {\n const rawHeader = error.response?.headers?.[\"retry-after\"];\n const candidate = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n if (!candidate) {\n return undefined;\n }\n\n const seconds = Number(candidate);\n if (Number.isFinite(seconds) && seconds > 0) {\n return seconds * 1000;\n }\n\n const dateMs = new Date(candidate).getTime();\n if (Number.isFinite(dateMs)) {\n return Math.max(0, dateMs - Date.now());\n }\n\n return undefined;\n}\n\nfunction buildApiError(error: AxiosError): MercadoLibreApiError {\n const status = error.response?.status;\n const responseData = error.response?.data;\n let code: string | undefined;\n let message: string | undefined;\n\n if (responseData && typeof responseData === \"object\") {\n const candidate = responseData as {\n error?: unknown;\n message?: unknown;\n error_description?: unknown;\n cause?: Array<{ code?: unknown; message?: unknown }>;\n };\n code = typeof candidate.error === \"string\" ? candidate.error : undefined;\n message =\n (typeof candidate.message === \"string\" ? candidate.message : undefined) ??\n (typeof candidate.error_description === \"string\" ? candidate.error_description : undefined) ??\n (Array.isArray(candidate.cause)\n ? candidate.cause\n .map((entry) => (typeof entry.message === \"string\" ? entry.message : null))\n .filter(Boolean)\n .join(\"; \")\n : undefined);\n } else if (typeof responseData === \"string\") {\n message = responseData;\n }\n\n const safeMessage = sanitizeMercadoLibreText(message ?? error.message ?? \"MercadoLibre request failed\");\n return new MercadoLibreApiError(\n `MercadoLibre API request failed${status ? ` (${status})` : \"\"}: ${safeMessage}`,\n status,\n code,\n extractRetryAfterMs(error)\n );\n}\n\nexport function formatMercadoLibreError(\n error: unknown,\n fallback = \"Unexpected MercadoLibre API error\"\n): string {\n if (error instanceof MercadoLibreApiError) {\n return error.message;\n }\n\n if (axios.isAxiosError(error)) {\n return buildApiError(error).message;\n }\n\n if (error instanceof Error) {\n return sanitizeMercadoLibreText(error.message);\n }\n\n return sanitizeMercadoLibreText(fallback);\n}\n\nexport function createMercadoLibreClient(accessToken: string, baseURL = BASE_URL): AxiosInstance {\n const client = axios.create({\n baseURL,\n timeout: 30000,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n client.interceptors.response.use(\n (response) => response,\n (error: AxiosError) => {\n const method = error.config?.method?.toUpperCase() ?? \"UNKNOWN\";\n const url = error.config?.url ?? \"UNKNOWN_URL\";\n const status = error.response?.status ?? \"NO_STATUS\";\n console.error(`[MERCADOLIBRE API ERROR] ${method} ${url} -> ${status}`);\n return Promise.reject(buildApiError(error));\n }\n );\n\n return client;\n}\n\nexport async function getMercadoLibreApiContext(profileId: string) {\n const access = await getMercadoLibreAccessForProfile(profileId);\n\n return {\n ...access,\n api: createMercadoLibreClient(access.accessToken),\n };\n}\n\nexport async function getMercadoLibreUsersMe(profileId: string): Promise<Record<string, unknown>> {\n const { api } = await getMercadoLibreApiContext(profileId);\n const response = await api.get<Record<string, unknown>>(\"/users/me\");\n return response.data;\n}\n\nexport function isRetryableMercadoLibreError(error: unknown): boolean {\n if (error instanceof MercadoLibreApiError) {\n return error.status ? RETRYABLE_STATUS_CODES.has(error.status) : true;\n }\n\n return false;\n}\n\nfunction classifyMercadoLibreBatchFailure(\n id: string,\n error: unknown,\n attempts: number\n): MercadoLibreDocumentFailure {\n const statusCode = error instanceof MercadoLibreApiError ? error.status : undefined;\n return {\n id,\n message: formatMercadoLibreError(error, `Failed to fetch MercadoLibre resource ${id}`),\n statusCode,\n attempts,\n retryable: isRetryableMercadoLibreError(error),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function withMercadoLibreRetry<T>(\n id: string,\n fetcher: (id: string) => Promise<T>,\n options: MercadoLibreBatchFetchOptions = {}\n): Promise<MercadoLibreDocumentSuccess<T>> {\n const maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 2));\n const baseRetryDelayMs = Math.max(50, Math.floor(options.baseRetryDelayMs ?? 250));\n let attempts = 0;\n\n while (true) {\n attempts += 1;\n\n try {\n const document = await fetcher(id);\n return { id, document };\n } catch (error) {\n const retryable = isRetryableMercadoLibreError(error);\n if (!retryable || attempts > maxRetries + 1) {\n throw classifyMercadoLibreBatchFailure(id, error, attempts);\n }\n\n const retryAfterMs = error instanceof MercadoLibreApiError ? error.retryAfterMs : undefined;\n const jitterMs = Math.floor(Math.random() * 100);\n const delayMs = retryAfterMs ?? baseRetryDelayMs * 2 ** (attempts - 1) + jitterMs;\n await sleep(delayMs);\n }\n }\n}\n\nexport async function runMercadoLibreBatch<T>(\n ids: string[],\n fetcher: (id: string) => Promise<T>,\n options: MercadoLibreBatchFetchOptions = {}\n): Promise<MercadoLibreDocumentsBatchResult<T>> {\n const maxConcurrency = Math.max(1, Math.floor(options.maxConcurrency ?? 10));\n const successful: Array<MercadoLibreDocumentSuccess<T>> = [];\n const failed: MercadoLibreDocumentFailure[] = [];\n\n for (let start = 0; start < ids.length; start += maxConcurrency) {\n const chunk = ids.slice(start, start + maxConcurrency);\n const settled = await Promise.allSettled(\n chunk.map((id) => withMercadoLibreRetry(id, fetcher, options))\n );\n\n settled.forEach((result, index) => {\n const id = chunk[index];\n if (result.status === \"fulfilled\") {\n successful.push(result.value);\n return;\n }\n\n const reason = result.reason;\n if (\n reason &&\n typeof reason === \"object\" &&\n \"id\" in reason &&\n \"attempts\" in reason &&\n \"message\" in reason\n ) {\n failed.push(reason as MercadoLibreDocumentFailure);\n return;\n }\n\n failed.push(classifyMercadoLibreBatchFailure(id, reason, 1));\n });\n }\n\n return { successful, failed };\n}\n\nexport function normalizeMercadoLibrePaging(input: Record<string, unknown> | null | undefined) {\n const paging = input ?? {};\n const total = Number((paging.total as number | undefined) ?? 0);\n const limit = Number((paging.limit as number | undefined) ?? 0);\n const offset = Number((paging.offset as number | undefined) ?? 0);\n\n return {\n total: Number.isFinite(total) ? Math.max(0, total) : 0,\n limit: Number.isFinite(limit) ? Math.max(0, limit) : 0,\n offset: Number.isFinite(offset) ? Math.max(0, offset) : 0,\n };\n}\n\nexport function buildMercadoLibrePaginationMetadata(params: {\n total: number;\n limit: number;\n offset: number;\n returned: number;\n nextField: string;\n}) {\n const effectiveLimit = params.limit > 0 ? params.limit : params.returned;\n const hasMore = params.offset + params.returned < params.total;\n\n return {\n total: params.total,\n limit: effectiveLimit,\n offset: params.offset,\n returned: params.returned,\n has_more: hasMore,\n continuation:\n hasMore && effectiveLimit > 0\n ? `Reenviar la misma tool con ${params.nextField}=${params.offset + effectiveLimit} para continuar.`\n : \"No more pages for this query.\",\n };\n}\n\nexport async function parseMercadoLibreMultiget<T>(\n response: AxiosResponse<Array<{ code?: number; body?: T }>>\n): Promise<T[]> {\n return response.data\n .filter((entry) => Number(entry.code ?? 0) >= 200 && Number(entry.code ?? 0) < 300 && entry.body)\n .map((entry) => entry.body as T);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getMercadoLibreApiContext, formatMercadoLibreError } from "./mercadolibre-api.js";
|
|
2
|
+
import { asArray, asRecord, normalizeScalarString } from "../../tools/mercadolibre/helpers.js";
|
|
2
3
|
function scopeToGroupPath(scope) {
|
|
3
4
|
if (scope === "flex") return "ML/flex";
|
|
4
5
|
if (scope === "full") return "ML/full";
|
|
@@ -24,7 +25,8 @@ async function getMercadoLibreBillingProvisionPage(profileId, params) {
|
|
|
24
25
|
);
|
|
25
26
|
const data = response.data;
|
|
26
27
|
const results = Array.isArray(data.results) ? data.results : [];
|
|
27
|
-
const
|
|
28
|
+
const rawLastId = data.last_id;
|
|
29
|
+
const lastId = rawLastId != null && rawLastId !== 0 && rawLastId !== "0" ? String(rawLastId) : null;
|
|
28
30
|
const pageTotal = Number(data.total ?? 0);
|
|
29
31
|
const pageLimit = Number(data.limit ?? params.limit);
|
|
30
32
|
return {
|
|
@@ -165,11 +167,101 @@ async function getMercadoLibreBillingDetails(profileId, params) {
|
|
|
165
167
|
failures
|
|
166
168
|
};
|
|
167
169
|
}
|
|
170
|
+
const BY_ORDER_CHUNK_SIZE = 60;
|
|
171
|
+
const BY_ORDER_CONCURRENCY = 5;
|
|
172
|
+
function extractOrderIdFromBillingOrder(row) {
|
|
173
|
+
const topLevelOrderId = normalizeScalarString(row.order_id);
|
|
174
|
+
if (topLevelOrderId) return topLevelOrderId;
|
|
175
|
+
const paymentInfo = asRecord(row.payment_info);
|
|
176
|
+
const paymentOrderId = normalizeScalarString(paymentInfo.order_id);
|
|
177
|
+
if (paymentOrderId) return paymentOrderId;
|
|
178
|
+
const details = asArray(row.details);
|
|
179
|
+
for (const detail of details) {
|
|
180
|
+
const detailOrderId = normalizeScalarString(detail.order_id);
|
|
181
|
+
if (detailOrderId) return detailOrderId;
|
|
182
|
+
const detailSales = asArray(detail.sales_info ?? detail.details_sales);
|
|
183
|
+
for (const sale of detailSales) {
|
|
184
|
+
const saleOrderId = normalizeScalarString(sale.order_id);
|
|
185
|
+
if (saleOrderId) return saleOrderId;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
async function getMercadoLibreBillingByOrderChunk(profileId, orderIds) {
|
|
191
|
+
const { api } = await getMercadoLibreApiContext(profileId);
|
|
192
|
+
const response = await api.get(
|
|
193
|
+
"/billing/integration/group/ML/order/details",
|
|
194
|
+
{
|
|
195
|
+
params: {
|
|
196
|
+
order_ids: orderIds.join(",")
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
const data = response.data;
|
|
201
|
+
const results = Array.isArray(data.results) ? data.results : Array.isArray(data) ? data : [];
|
|
202
|
+
return {
|
|
203
|
+
results,
|
|
204
|
+
order_ids_requested: orderIds
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function getMercadoLibreBillingProvisionsByOrder(profileId, params) {
|
|
208
|
+
const uniqueIds = Array.from(new Set(params.orderIds));
|
|
209
|
+
const chunks = [];
|
|
210
|
+
for (let i = 0; i < uniqueIds.length; i += BY_ORDER_CHUNK_SIZE) {
|
|
211
|
+
chunks.push(uniqueIds.slice(i, i + BY_ORDER_CHUNK_SIZE));
|
|
212
|
+
}
|
|
213
|
+
const allResults = [];
|
|
214
|
+
const failures = [];
|
|
215
|
+
let chunksSucceeded = 0;
|
|
216
|
+
let chunksFailed = 0;
|
|
217
|
+
for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx += BY_ORDER_CONCURRENCY) {
|
|
218
|
+
const batch = chunks.slice(chunkIdx, chunkIdx + BY_ORDER_CONCURRENCY);
|
|
219
|
+
const settled = await Promise.allSettled(
|
|
220
|
+
batch.map((chunk) => getMercadoLibreBillingByOrderChunk(profileId, chunk))
|
|
221
|
+
);
|
|
222
|
+
for (let i = 0; i < settled.length; i++) {
|
|
223
|
+
const result = settled[i];
|
|
224
|
+
if (result.status === "fulfilled") {
|
|
225
|
+
chunksSucceeded += 1;
|
|
226
|
+
allResults.push(...result.value.results);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
chunksFailed += 1;
|
|
230
|
+
const chunkOrderIds = batch[i];
|
|
231
|
+
failures.push({
|
|
232
|
+
chunk_index: chunkIdx + i,
|
|
233
|
+
order_ids: chunkOrderIds,
|
|
234
|
+
message: formatMercadoLibreError(
|
|
235
|
+
result.reason,
|
|
236
|
+
"Failed to fetch billing provisions by order chunk"
|
|
237
|
+
)
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const foundOrderIds = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const row of allResults) {
|
|
243
|
+
const orderId = extractOrderIdFromBillingOrder(row);
|
|
244
|
+
if (orderId) foundOrderIds.add(orderId);
|
|
245
|
+
}
|
|
246
|
+
const notFoundOrderIds = uniqueIds.filter((id) => !foundOrderIds.has(id));
|
|
247
|
+
return {
|
|
248
|
+
results: allResults,
|
|
249
|
+
order_ids_requested: uniqueIds,
|
|
250
|
+
order_ids_found: [...foundOrderIds],
|
|
251
|
+
order_ids_not_found: notFoundOrderIds,
|
|
252
|
+
chunks_total: chunks.length,
|
|
253
|
+
chunks_succeeded: chunksSucceeded,
|
|
254
|
+
chunks_failed: chunksFailed,
|
|
255
|
+
coverage_complete: chunksFailed === 0,
|
|
256
|
+
failures
|
|
257
|
+
};
|
|
258
|
+
}
|
|
168
259
|
export {
|
|
169
260
|
getMercadoLibreBillingDetails,
|
|
170
261
|
getMercadoLibreBillingDetailsPage,
|
|
171
262
|
getMercadoLibreBillingProvisionPage,
|
|
172
263
|
getMercadoLibreBillingProvisions,
|
|
264
|
+
getMercadoLibreBillingProvisionsByOrder,
|
|
173
265
|
scopeToGroupPath
|
|
174
266
|
};
|
|
175
267
|
//# sourceMappingURL=mercadolibre-billing.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/services/mercadolibre/mercadolibre-billing.ts"],
|
|
4
|
-
"sourcesContent": ["import { getMercadoLibreApiContext, formatMercadoLibreError } from \"./mercadolibre-api.js\";\n\nexport interface MercadoLibreBillingDetailsParams {\n periodKey: string;\n documentType?: \"BILL\" | \"CREDIT_NOTE\";\n group?: \"ML\" | \"MP\" | \"flex\";\n limit?: number;\n offset?: number;\n}\n\nexport interface MercadoLibreBillingPage {\n offset: number;\n limit: number;\n total: number;\n results: Record<string, unknown>[];\n}\n\nexport interface MercadoLibreBillingFetchResult {\n period_key: string;\n group: string;\n document_type: string;\n pages_requested: number;\n pages_succeeded: number;\n pages_failed: number;\n total_reported: number;\n rows: Record<string, unknown>[];\n failures: Array<{\n offset: number;\n limit: number;\n message: string;\n }>;\n}\n\n// \u2500\u2500 Provisions (from_id cursor) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type BillingProvisionScope = \"ml\" | \"mp\" | \"flex\" | \"full\";\n\nexport interface BillingProvisionPage {\n results: Record<string, unknown>[];\n last_id: string | null;\n total: number;\n limit: number;\n}\n\nexport interface BillingProvisionFetchResult {\n scope: BillingProvisionScope;\n period_key: string;\n document_type: string;\n pages_requested: number;\n pages_succeeded: number;\n pages_failed: number;\n detail_count_total: number;\n last_id_seen: string | null;\n coverage_complete: boolean;\n rows: Record<string, unknown>[];\n failures: Array<{ page: number; from_id: string | null; message: string }>;\n}\n\n/**\n * Resolves the URL path segment for a given scope.\n * ml \u2192 group/ML/details\n * mp \u2192 group/MP/details\n * flex \u2192 group/ML/flex/details\n * full \u2192 group/ML/full/details\n */\nexport function scopeToGroupPath(scope: BillingProvisionScope): string {\n if (scope === \"flex\") return \"ML/flex\";\n if (scope === \"full\") return \"ML/full\";\n return scope.toUpperCase(); // \"ml\" \u2192 \"ML\", \"mp\" \u2192 \"MP\"\n}\n\n/**\n * Fetch one page of billing provisions using from_id cursor.\n */\nexport async function getMercadoLibreBillingProvisionPage(\n profileId: string,\n params: {\n periodKey: string;\n scope: BillingProvisionScope;\n documentType: \"BILL\" | \"CREDIT_NOTE\";\n limit: number;\n fromId?: string | null;\n detailType?: string;\n detailSubTypes?: string[];\n excludeDetailSubTypes?: string[];\n marketplaceType?: string;\n orderIds?: string[];\n itemIds?: string[];\n }\n): Promise<BillingProvisionPage> {\n const { api } = await getMercadoLibreApiContext(profileId);\n\n const queryParams: Record<string, unknown> = {\n document_type: params.documentType,\n limit: params.limit,\n };\n if (params.fromId) queryParams.from_id = params.fromId;\n if (params.detailType) queryParams.detail_type = params.detailType;\n if (params.detailSubTypes?.length) queryParams.detail_sub_types = params.detailSubTypes.join(\",\");\n if (params.excludeDetailSubTypes?.length) queryParams.exclude_detail_sub_types = params.excludeDetailSubTypes.join(\",\");\n if (params.marketplaceType) queryParams.marketplace_type = params.marketplaceType;\n if (params.orderIds?.length) queryParams.order_ids = params.orderIds.join(\",\");\n if (params.itemIds?.length) queryParams.item_ids = params.itemIds.join(\",\");\n\n const groupPath = scopeToGroupPath(params.scope);\n const response = await api.get<Record<string, unknown>>(\n `/billing/integration/periods/key/${params.periodKey}/group/${groupPath}/details`,\n { params: queryParams }\n );\n\n const data = response.data;\n const results = Array.isArray(data.results) ? (data.results as Record<string, unknown>[]) : [];\n const lastId = typeof data.last_id === \"string\" && data.last_id ? data.last_id : null;\n const pageTotal = Number(data.total ?? 0);\n const pageLimit = Number(data.limit ?? params.limit);\n\n return {\n results,\n last_id: lastId,\n total: isFinite(pageTotal) ? Math.max(0, pageTotal) : 0,\n limit: isFinite(pageLimit) ? Math.max(0, pageLimit) : params.limit,\n };\n}\n\nconst PROVISIONS_PAGE_LIMIT = 1000;\nconst PROVISIONS_MAX_PAGES = 200;\n\n/**\n * Fetch all billing provisions for one scope by walking from_id cursor.\n * Deduplicates by detail_id. Stops if last_id doesn't advance.\n * Returns partial coverage when pages fail.\n */\nexport async function getMercadoLibreBillingProvisions(\n profileId: string,\n params: {\n periodKey: string;\n scope: BillingProvisionScope;\n documentType: \"BILL\" | \"CREDIT_NOTE\";\n detailType?: string;\n detailSubTypes?: string[];\n excludeDetailSubTypes?: string[];\n marketplaceType?: string;\n orderIds?: string[];\n itemIds?: string[];\n /** Resume cursor: start fetching from this from_id instead of the first page. */\n fromId?: string | null;\n }\n): Promise<BillingProvisionFetchResult> {\n const seen = new Set<string>();\n const rows: Record<string, unknown>[] = [];\n const failures: BillingProvisionFetchResult[\"failures\"] = [];\n let lastId: string | null = params.fromId ?? null;\n let pagesRequested = 0;\n let pagesSucceeded = 0;\n let lastIdSeen: string | null = null;\n\n for (let page = 0; page < PROVISIONS_MAX_PAGES; page++) {\n pagesRequested += 1;\n let pageResult: BillingProvisionPage;\n\n try {\n pageResult = await getMercadoLibreBillingProvisionPage(profileId, {\n ...params,\n documentType: params.documentType,\n limit: PROVISIONS_PAGE_LIMIT,\n fromId: lastId,\n });\n pagesSucceeded += 1;\n } catch (error) {\n failures.push({\n page: page + 1,\n from_id: lastId,\n message: formatMercadoLibreError(error, \"Failed to fetch billing provisions page\"),\n });\n break;\n }\n\n const newRows: Record<string, unknown>[] = [];\n for (const row of pageResult.results) {\n // Real MELI payload nests detail fields under charge_info\n const chargeInfo = (row.charge_info as Record<string, unknown>) ?? row;\n const detailId = typeof chargeInfo.detail_id === \"string\"\n ? chargeInfo.detail_id\n : String(chargeInfo.detail_id ?? \"\");\n if (!detailId || seen.has(detailId)) continue;\n seen.add(detailId);\n newRows.push(row);\n }\n rows.push(...newRows);\n lastIdSeen = pageResult.last_id;\n\n // Stop conditions\n if (!pageResult.last_id) break;\n if (pageResult.last_id === lastId) break; // cursor not advancing\n if (pageResult.results.length === 0) break;\n\n lastId = pageResult.last_id;\n }\n\n const pagesFailed = pagesRequested - pagesSucceeded;\n const coverageComplete = pagesFailed === 0 && lastIdSeen === null;\n\n return {\n scope: params.scope,\n period_key: params.periodKey,\n document_type: params.documentType,\n pages_requested: pagesRequested,\n pages_succeeded: pagesSucceeded,\n pages_failed: pagesFailed,\n detail_count_total: rows.length,\n last_id_seen: lastIdSeen,\n coverage_complete: coverageComplete,\n rows,\n failures,\n };\n}\n\n// \u2500\u2500 Existing offset-based billing (preserved for backward compat) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function getMercadoLibreBillingDetailsPage(\n profileId: string,\n params: MercadoLibreBillingDetailsParams\n): Promise<MercadoLibreBillingPage> {\n const { api } = await getMercadoLibreApiContext(profileId);\n const documentType = params.documentType ?? \"BILL\";\n const group = params.group ?? \"ML\";\n const limit = Math.min(150, Math.max(1, Math.floor(params.limit ?? 150)));\n const offset = Math.max(0, Math.floor(params.offset ?? 0));\n\n const response = await api.get<Record<string, unknown>>(\n `/billing/integration/periods/key/${params.periodKey}/group/${group}/details`,\n {\n params: {\n document_type: documentType,\n limit,\n offset,\n },\n }\n );\n\n return {\n offset: Number(response.data.offset ?? offset),\n limit: Number(response.data.limit ?? limit),\n total: Number(response.data.total ?? 0),\n results: Array.isArray(response.data.results)\n ? (response.data.results as Record<string, unknown>[])\n : [],\n };\n}\n\nexport async function getMercadoLibreBillingDetails(\n profileId: string,\n params: Omit<MercadoLibreBillingDetailsParams, \"offset\"> & { maxPages?: number }\n): Promise<MercadoLibreBillingFetchResult> {\n const limit = Math.min(150, Math.max(1, Math.floor(params.limit ?? 150)));\n const maxPages = Math.max(1, Math.floor(params.maxPages ?? 20));\n const documentType = params.documentType ?? \"BILL\";\n const group = params.group ?? \"ML\";\n const rows: Record<string, unknown>[] = [];\n const failures: MercadoLibreBillingFetchResult[\"failures\"] = [];\n\n let totalReported = 0;\n let pagesRequested = 0;\n let pagesSucceeded = 0;\n\n for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {\n const offset = pageIndex * limit;\n pagesRequested += 1;\n\n try {\n const page = await getMercadoLibreBillingDetailsPage(profileId, {\n ...params,\n group,\n documentType,\n limit,\n offset,\n });\n pagesSucceeded += 1;\n totalReported = Math.max(totalReported, page.total);\n rows.push(...page.results);\n\n if (offset + page.results.length >= page.total || page.results.length === 0) {\n break;\n }\n } catch (error) {\n failures.push({\n offset,\n limit,\n message: formatMercadoLibreError(error, \"Failed to fetch MercadoLibre billing details\"),\n });\n break;\n }\n }\n\n return {\n period_key: params.periodKey,\n group,\n document_type: documentType,\n pages_requested: pagesRequested,\n pages_succeeded: pagesSucceeded,\n pages_failed: failures.length,\n total_reported: totalReported,\n rows,\n failures,\n };\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,2BAA2B,+BAA+B;
|
|
4
|
+
"sourcesContent": ["import { getMercadoLibreApiContext, formatMercadoLibreError } from \"./mercadolibre-api.js\";\nimport { asArray, asRecord, normalizeScalarString, normalizeString } from \"../../tools/mercadolibre/helpers.js\";\n\nexport interface MercadoLibreBillingDetailsParams {\n periodKey: string;\n documentType?: \"BILL\" | \"CREDIT_NOTE\";\n group?: \"ML\" | \"MP\" | \"flex\";\n limit?: number;\n offset?: number;\n}\n\nexport interface MercadoLibreBillingPage {\n offset: number;\n limit: number;\n total: number;\n results: Record<string, unknown>[];\n}\n\nexport interface MercadoLibreBillingFetchResult {\n period_key: string;\n group: string;\n document_type: string;\n pages_requested: number;\n pages_succeeded: number;\n pages_failed: number;\n total_reported: number;\n rows: Record<string, unknown>[];\n failures: Array<{\n offset: number;\n limit: number;\n message: string;\n }>;\n}\n\n// \u2500\u2500 Provisions (from_id cursor) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type BillingProvisionScope = \"ml\" | \"mp\" | \"flex\" | \"full\";\n\nexport interface BillingProvisionPage {\n results: Record<string, unknown>[];\n last_id: string | null;\n total: number;\n limit: number;\n}\n\nexport interface BillingProvisionFetchResult {\n scope: BillingProvisionScope;\n period_key: string;\n document_type: string;\n pages_requested: number;\n pages_succeeded: number;\n pages_failed: number;\n detail_count_total: number;\n last_id_seen: string | null;\n coverage_complete: boolean;\n rows: Record<string, unknown>[];\n failures: Array<{ page: number; from_id: string | null; message: string }>;\n}\n\n/**\n * Resolves the URL path segment for a given scope.\n * ml \u2192 group/ML/details\n * mp \u2192 group/MP/details\n * flex \u2192 group/ML/flex/details\n * full \u2192 group/ML/full/details\n */\nexport function scopeToGroupPath(scope: BillingProvisionScope): string {\n if (scope === \"flex\") return \"ML/flex\";\n if (scope === \"full\") return \"ML/full\";\n return scope.toUpperCase(); // \"ml\" \u2192 \"ML\", \"mp\" \u2192 \"MP\"\n}\n\n/**\n * Fetch one page of billing provisions using from_id cursor.\n */\nexport async function getMercadoLibreBillingProvisionPage(\n profileId: string,\n params: {\n periodKey: string;\n scope: BillingProvisionScope;\n documentType: \"BILL\" | \"CREDIT_NOTE\";\n limit: number;\n fromId?: string | null;\n detailType?: string;\n detailSubTypes?: string[];\n excludeDetailSubTypes?: string[];\n marketplaceType?: string;\n orderIds?: string[];\n itemIds?: string[];\n }\n): Promise<BillingProvisionPage> {\n const { api } = await getMercadoLibreApiContext(profileId);\n\n const queryParams: Record<string, unknown> = {\n document_type: params.documentType,\n limit: params.limit,\n };\n if (params.fromId) queryParams.from_id = params.fromId;\n if (params.detailType) queryParams.detail_type = params.detailType;\n if (params.detailSubTypes?.length) queryParams.detail_sub_types = params.detailSubTypes.join(\",\");\n if (params.excludeDetailSubTypes?.length) queryParams.exclude_detail_sub_types = params.excludeDetailSubTypes.join(\",\");\n if (params.marketplaceType) queryParams.marketplace_type = params.marketplaceType;\n if (params.orderIds?.length) queryParams.order_ids = params.orderIds.join(\",\");\n if (params.itemIds?.length) queryParams.item_ids = params.itemIds.join(\",\");\n\n const groupPath = scopeToGroupPath(params.scope);\n const response = await api.get<Record<string, unknown>>(\n `/billing/integration/periods/key/${params.periodKey}/group/${groupPath}/details`,\n { params: queryParams }\n );\n\n const data = response.data;\n const results = Array.isArray(data.results) ? (data.results as Record<string, unknown>[]) : [];\n // last_id can be numeric (MELI sends number, not string) \u2014 handle both safely.\n // MELI uses 0 (or \"0\") as a terminal cursor meaning \"no more pages\".\n // Normalize terminal sentinels to null so pagination stops correctly.\n const rawLastId = data.last_id;\n const lastId = rawLastId != null && rawLastId !== 0 && rawLastId !== \"0\"\n ? String(rawLastId)\n : null;\n const pageTotal = Number(data.total ?? 0);\n const pageLimit = Number(data.limit ?? params.limit);\n\n return {\n results,\n last_id: lastId,\n total: isFinite(pageTotal) ? Math.max(0, pageTotal) : 0,\n limit: isFinite(pageLimit) ? Math.max(0, pageLimit) : params.limit,\n };\n}\n\nconst PROVISIONS_PAGE_LIMIT = 1000;\nconst PROVISIONS_MAX_PAGES = 200;\n\n/**\n * Fetch all billing provisions for one scope by walking from_id cursor.\n * Deduplicates by detail_id. Stops if last_id doesn't advance.\n * Returns partial coverage when pages fail.\n */\nexport async function getMercadoLibreBillingProvisions(\n profileId: string,\n params: {\n periodKey: string;\n scope: BillingProvisionScope;\n documentType: \"BILL\" | \"CREDIT_NOTE\";\n detailType?: string;\n detailSubTypes?: string[];\n excludeDetailSubTypes?: string[];\n marketplaceType?: string;\n orderIds?: string[];\n itemIds?: string[];\n /** Resume cursor: start fetching from this from_id instead of the first page. */\n fromId?: string | null;\n }\n): Promise<BillingProvisionFetchResult> {\n const seen = new Set<string>();\n const rows: Record<string, unknown>[] = [];\n const failures: BillingProvisionFetchResult[\"failures\"] = [];\n let lastId: string | null = params.fromId ?? null;\n let pagesRequested = 0;\n let pagesSucceeded = 0;\n let lastIdSeen: string | null = null;\n\n for (let page = 0; page < PROVISIONS_MAX_PAGES; page++) {\n pagesRequested += 1;\n let pageResult: BillingProvisionPage;\n\n try {\n pageResult = await getMercadoLibreBillingProvisionPage(profileId, {\n ...params,\n documentType: params.documentType,\n limit: PROVISIONS_PAGE_LIMIT,\n fromId: lastId,\n });\n pagesSucceeded += 1;\n } catch (error) {\n failures.push({\n page: page + 1,\n from_id: lastId,\n message: formatMercadoLibreError(error, \"Failed to fetch billing provisions page\"),\n });\n break;\n }\n\n const newRows: Record<string, unknown>[] = [];\n for (const row of pageResult.results) {\n // Real MELI payload nests detail fields under charge_info\n const chargeInfo = (row.charge_info as Record<string, unknown>) ?? row;\n const detailId = typeof chargeInfo.detail_id === \"string\"\n ? chargeInfo.detail_id\n : String(chargeInfo.detail_id ?? \"\");\n if (!detailId || seen.has(detailId)) continue;\n seen.add(detailId);\n newRows.push(row);\n }\n rows.push(...newRows);\n lastIdSeen = pageResult.last_id;\n\n // Stop conditions\n if (!pageResult.last_id) break;\n if (pageResult.last_id === lastId) break; // cursor not advancing\n if (pageResult.results.length === 0) break;\n\n lastId = pageResult.last_id;\n }\n\n const pagesFailed = pagesRequested - pagesSucceeded;\n const coverageComplete = pagesFailed === 0 && lastIdSeen === null;\n\n return {\n scope: params.scope,\n period_key: params.periodKey,\n document_type: params.documentType,\n pages_requested: pagesRequested,\n pages_succeeded: pagesSucceeded,\n pages_failed: pagesFailed,\n detail_count_total: rows.length,\n last_id_seen: lastIdSeen,\n coverage_complete: coverageComplete,\n rows,\n failures,\n };\n}\n\n// \u2500\u2500 Existing offset-based billing (preserved for backward compat) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function getMercadoLibreBillingDetailsPage(\n profileId: string,\n params: MercadoLibreBillingDetailsParams\n): Promise<MercadoLibreBillingPage> {\n const { api } = await getMercadoLibreApiContext(profileId);\n const documentType = params.documentType ?? \"BILL\";\n const group = params.group ?? \"ML\";\n const limit = Math.min(150, Math.max(1, Math.floor(params.limit ?? 150)));\n const offset = Math.max(0, Math.floor(params.offset ?? 0));\n\n const response = await api.get<Record<string, unknown>>(\n `/billing/integration/periods/key/${params.periodKey}/group/${group}/details`,\n {\n params: {\n document_type: documentType,\n limit,\n offset,\n },\n }\n );\n\n return {\n offset: Number(response.data.offset ?? offset),\n limit: Number(response.data.limit ?? limit),\n total: Number(response.data.total ?? 0),\n results: Array.isArray(response.data.results)\n ? (response.data.results as Record<string, unknown>[])\n : [],\n };\n}\n\nexport async function getMercadoLibreBillingDetails(\n profileId: string,\n params: Omit<MercadoLibreBillingDetailsParams, \"offset\"> & { maxPages?: number }\n): Promise<MercadoLibreBillingFetchResult> {\n const limit = Math.min(150, Math.max(1, Math.floor(params.limit ?? 150)));\n const maxPages = Math.max(1, Math.floor(params.maxPages ?? 20));\n const documentType = params.documentType ?? \"BILL\";\n const group = params.group ?? \"ML\";\n const rows: Record<string, unknown>[] = [];\n const failures: MercadoLibreBillingFetchResult[\"failures\"] = [];\n\n let totalReported = 0;\n let pagesRequested = 0;\n let pagesSucceeded = 0;\n\n for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {\n const offset = pageIndex * limit;\n pagesRequested += 1;\n\n try {\n const page = await getMercadoLibreBillingDetailsPage(profileId, {\n ...params,\n group,\n documentType,\n limit,\n offset,\n });\n pagesSucceeded += 1;\n totalReported = Math.max(totalReported, page.total);\n rows.push(...page.results);\n\n if (offset + page.results.length >= page.total || page.results.length === 0) {\n break;\n }\n } catch (error) {\n failures.push({\n offset,\n limit,\n message: formatMercadoLibreError(error, \"Failed to fetch MercadoLibre billing details\"),\n });\n break;\n }\n }\n\n return {\n period_key: params.periodKey,\n group,\n document_type: documentType,\n pages_requested: pagesRequested,\n pages_succeeded: pagesSucceeded,\n pages_failed: failures.length,\n total_reported: totalReported,\n rows,\n failures,\n };\n}\n\n// \u2500\u2500 Billing provisions by order (official order-centric endpoint) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst BY_ORDER_CHUNK_SIZE = 60;\nconst BY_ORDER_CONCURRENCY = 5;\n\nexport interface BillingProvisionByOrderChunkResult {\n results: Record<string, unknown>[];\n order_ids_requested: string[];\n}\n\nexport interface BillingProvisionByOrderResult {\n results: Record<string, unknown>[];\n order_ids_requested: string[];\n order_ids_found: string[];\n order_ids_not_found: string[];\n chunks_total: number;\n chunks_succeeded: number;\n chunks_failed: number;\n coverage_complete: boolean;\n failures: Array<{ chunk_index: number; order_ids: string[]; message: string }>;\n}\n\nfunction extractOrderIdFromBillingOrder(row: Record<string, unknown>): string | null {\n const topLevelOrderId = normalizeScalarString(row.order_id);\n if (topLevelOrderId) return topLevelOrderId;\n\n const paymentInfo = asRecord(row.payment_info);\n const paymentOrderId = normalizeScalarString(paymentInfo.order_id);\n if (paymentOrderId) return paymentOrderId;\n\n const details = asArray<Record<string, unknown>>(row.details);\n for (const detail of details) {\n const detailOrderId = normalizeScalarString(detail.order_id);\n if (detailOrderId) return detailOrderId;\n\n const detailSales = asArray<Record<string, unknown>>(detail.sales_info ?? detail.details_sales);\n for (const sale of detailSales) {\n const saleOrderId = normalizeScalarString(sale.order_id);\n if (saleOrderId) return saleOrderId;\n }\n }\n\n return null;\n}\n\n/**\n * Fetch billing provisions for a single chunk of order IDs using the official\n * documented Mercado Libre order-centric billing endpoint.\n */\nasync function getMercadoLibreBillingByOrderChunk(\n profileId: string,\n orderIds: string[]\n): Promise<BillingProvisionByOrderChunkResult> {\n const { api } = await getMercadoLibreApiContext(profileId);\n\n const response = await api.get<Record<string, unknown>>(\n \"/billing/integration/group/ML/order/details\",\n {\n params: {\n order_ids: orderIds.join(\",\"),\n },\n }\n );\n\n const data = response.data;\n const results = Array.isArray(data.results)\n ? (data.results as Record<string, unknown>[])\n : Array.isArray(data)\n ? (data as Record<string, unknown>[])\n : [];\n\n return {\n results,\n order_ids_requested: orderIds,\n };\n}\n\n/**\n * Fetch billing provisions by order across multiple order IDs, chunked in\n * groups of up to 60 orders per request. Consolidates results and tracks\n * which orders were found vs. not found.\n */\nexport async function getMercadoLibreBillingProvisionsByOrder(\n profileId: string,\n params: {\n orderIds: string[];\n }\n): Promise<BillingProvisionByOrderResult> {\n const uniqueIds = Array.from(new Set(params.orderIds));\n const chunks: string[][] = [];\n\n for (let i = 0; i < uniqueIds.length; i += BY_ORDER_CHUNK_SIZE) {\n chunks.push(uniqueIds.slice(i, i + BY_ORDER_CHUNK_SIZE));\n }\n\n const allResults: Record<string, unknown>[] = [];\n const failures: BillingProvisionByOrderResult[\"failures\"] = [];\n let chunksSucceeded = 0;\n let chunksFailed = 0;\n\n for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx += BY_ORDER_CONCURRENCY) {\n const batch = chunks.slice(chunkIdx, chunkIdx + BY_ORDER_CONCURRENCY);\n const settled = await Promise.allSettled(\n batch.map((chunk) => getMercadoLibreBillingByOrderChunk(profileId, chunk))\n );\n\n for (let i = 0; i < settled.length; i++) {\n const result = settled[i];\n if (result.status === \"fulfilled\") {\n chunksSucceeded += 1;\n allResults.push(...result.value.results);\n continue;\n }\n\n chunksFailed += 1;\n const chunkOrderIds = batch[i];\n failures.push({\n chunk_index: chunkIdx + i,\n order_ids: chunkOrderIds,\n message: formatMercadoLibreError(\n result.reason,\n \"Failed to fetch billing provisions by order chunk\"\n ),\n });\n }\n }\n\n const foundOrderIds = new Set<string>();\n for (const row of allResults) {\n const orderId = extractOrderIdFromBillingOrder(row);\n if (orderId) foundOrderIds.add(orderId);\n }\n\n const notFoundOrderIds = uniqueIds.filter((id) => !foundOrderIds.has(id));\n\n return {\n results: allResults,\n order_ids_requested: uniqueIds,\n order_ids_found: [...foundOrderIds],\n order_ids_not_found: notFoundOrderIds,\n chunks_total: chunks.length,\n chunks_succeeded: chunksSucceeded,\n chunks_failed: chunksFailed,\n coverage_complete: chunksFailed === 0,\n failures,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMercadoLibreClient,
|
|
3
|
+
formatMercadoLibreError,
|
|
4
|
+
isRetryableMercadoLibreError,
|
|
5
|
+
MercadoLibreApiError
|
|
6
|
+
} from "./mercadolibre-api.js";
|
|
7
|
+
import { getMercadoLibreAccessForProfile } from "../../config/mercadolibre.js";
|
|
8
|
+
const BASE_URL = "https://api.mercadopago.com";
|
|
9
|
+
const PAYMENT_DETAILS_CHUNK_SIZE = 50;
|
|
10
|
+
function buildPaymentPath(paymentId) {
|
|
11
|
+
return `/v1/payments/${encodeURIComponent(paymentId)}`;
|
|
12
|
+
}
|
|
13
|
+
async function fetchPayment(api, paymentId) {
|
|
14
|
+
const response = await api.get(buildPaymentPath(paymentId));
|
|
15
|
+
return response.data;
|
|
16
|
+
}
|
|
17
|
+
async function getMercadoLibrePaymentDetails(profileId, paymentId) {
|
|
18
|
+
const { accessToken } = await getMercadoLibreAccessForProfile(profileId);
|
|
19
|
+
const api = createMercadoLibreClient(accessToken, BASE_URL);
|
|
20
|
+
return fetchPayment(api, paymentId);
|
|
21
|
+
}
|
|
22
|
+
function classifyPaymentFailure(paymentId, error) {
|
|
23
|
+
const statusCode = error instanceof MercadoLibreApiError ? error.status : void 0;
|
|
24
|
+
return {
|
|
25
|
+
paymentId,
|
|
26
|
+
message: formatMercadoLibreError(error, `Failed to fetch MercadoLibre payment ${paymentId}`),
|
|
27
|
+
statusCode,
|
|
28
|
+
attempts: 1,
|
|
29
|
+
retryable: isRetryableMercadoLibreError(error)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function getMercadoLibrePaymentDetailsBatch(profileId, paymentIds) {
|
|
33
|
+
const { accessToken } = await getMercadoLibreAccessForProfile(profileId);
|
|
34
|
+
const api = createMercadoLibreClient(accessToken, BASE_URL);
|
|
35
|
+
const successful = [];
|
|
36
|
+
const failed = [];
|
|
37
|
+
for (let start = 0; start < paymentIds.length; start += PAYMENT_DETAILS_CHUNK_SIZE) {
|
|
38
|
+
const chunk = paymentIds.slice(start, start + PAYMENT_DETAILS_CHUNK_SIZE);
|
|
39
|
+
const settled = await Promise.allSettled(
|
|
40
|
+
chunk.map(async (paymentId) => ({
|
|
41
|
+
paymentId,
|
|
42
|
+
payment: await fetchPayment(api, paymentId)
|
|
43
|
+
}))
|
|
44
|
+
);
|
|
45
|
+
settled.forEach((result, index) => {
|
|
46
|
+
const paymentId = chunk[index];
|
|
47
|
+
if (result.status === "fulfilled") {
|
|
48
|
+
successful.push(result.value);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
failed.push(classifyPaymentFailure(paymentId, result.reason));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return { successful, failed };
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
getMercadoLibrePaymentDetails,
|
|
58
|
+
getMercadoLibrePaymentDetailsBatch
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=mercadolibre-payments.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/services/mercadolibre/mercadolibre-payments.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AxiosInstance } from \"axios\";\n\nimport {\n createMercadoLibreClient,\n formatMercadoLibreError,\n isRetryableMercadoLibreError,\n MercadoLibreApiError,\n} from \"./mercadolibre-api.js\";\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\n\nconst BASE_URL = \"https://api.mercadopago.com\";\nconst PAYMENT_DETAILS_CHUNK_SIZE = 50;\n\nfunction buildPaymentPath(paymentId: string): string {\n return `/v1/payments/${encodeURIComponent(paymentId)}`;\n}\n\nasync function fetchPayment(api: AxiosInstance, paymentId: string): Promise<Record<string, unknown>> {\n const response = await api.get<Record<string, unknown>>(buildPaymentPath(paymentId));\n return response.data;\n}\n\nexport async function getMercadoLibrePaymentDetails(profileId: string, paymentId: string) {\n const { accessToken } = await getMercadoLibreAccessForProfile(profileId);\n const api = createMercadoLibreClient(accessToken, BASE_URL);\n return fetchPayment(api, paymentId);\n}\n\nexport interface MercadoLibrePaymentDetailsBatchSuccess {\n paymentId: string;\n payment: Record<string, unknown>;\n}\n\nexport interface MercadoLibrePaymentDetailsBatchFailure {\n paymentId: string;\n message: string;\n statusCode?: number;\n attempts: number;\n retryable: boolean;\n}\n\nexport interface MercadoLibrePaymentDetailsBatchResult {\n successful: MercadoLibrePaymentDetailsBatchSuccess[];\n failed: MercadoLibrePaymentDetailsBatchFailure[];\n}\n\nfunction classifyPaymentFailure(paymentId: string, error: unknown): MercadoLibrePaymentDetailsBatchFailure {\n const statusCode = error instanceof MercadoLibreApiError ? error.status : undefined;\n\n return {\n paymentId,\n message: formatMercadoLibreError(error, `Failed to fetch MercadoLibre payment ${paymentId}`),\n statusCode,\n attempts: 1,\n retryable: isRetryableMercadoLibreError(error),\n };\n}\n\nexport async function getMercadoLibrePaymentDetailsBatch(\n profileId: string,\n paymentIds: string[]\n): Promise<MercadoLibrePaymentDetailsBatchResult> {\n const { accessToken } = await getMercadoLibreAccessForProfile(profileId);\n const api = createMercadoLibreClient(accessToken, BASE_URL);\n\n const successful: MercadoLibrePaymentDetailsBatchSuccess[] = [];\n const failed: MercadoLibrePaymentDetailsBatchFailure[] = [];\n\n for (let start = 0; start < paymentIds.length; start += PAYMENT_DETAILS_CHUNK_SIZE) {\n const chunk = paymentIds.slice(start, start + PAYMENT_DETAILS_CHUNK_SIZE);\n const settled = await Promise.allSettled(\n chunk.map(async (paymentId) => ({\n paymentId,\n payment: await fetchPayment(api, paymentId),\n }))\n );\n\n settled.forEach((result, index) => {\n const paymentId = chunk[index];\n\n if (result.status === \"fulfilled\") {\n successful.push(result.value);\n return;\n }\n\n failed.push(classifyPaymentFailure(paymentId, result.reason));\n });\n }\n\n return { successful, failed };\n}\n"],
|
|
5
|
+
"mappings": "AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,uCAAuC;AAEhD,MAAM,WAAW;AACjB,MAAM,6BAA6B;AAEnC,SAAS,iBAAiB,WAA2B;AACnD,SAAO,gBAAgB,mBAAmB,SAAS,CAAC;AACtD;AAEA,eAAe,aAAa,KAAoB,WAAqD;AACnG,QAAM,WAAW,MAAM,IAAI,IAA6B,iBAAiB,SAAS,CAAC;AACnF,SAAO,SAAS;AAClB;AAEA,eAAsB,8BAA8B,WAAmB,WAAmB;AACxF,QAAM,EAAE,YAAY,IAAI,MAAM,gCAAgC,SAAS;AACvE,QAAM,MAAM,yBAAyB,aAAa,QAAQ;AAC1D,SAAO,aAAa,KAAK,SAAS;AACpC;AAoBA,SAAS,uBAAuB,WAAmB,OAAwD;AACzG,QAAM,aAAa,iBAAiB,uBAAuB,MAAM,SAAS;AAE1E,SAAO;AAAA,IACL;AAAA,IACA,SAAS,wBAAwB,OAAO,wCAAwC,SAAS,EAAE;AAAA,IAC3F;AAAA,IACA,UAAU;AAAA,IACV,WAAW,6BAA6B,KAAK;AAAA,EAC/C;AACF;AAEA,eAAsB,mCACpB,WACA,YACgD;AAChD,QAAM,EAAE,YAAY,IAAI,MAAM,gCAAgC,SAAS;AACvE,QAAM,MAAM,yBAAyB,aAAa,QAAQ;AAE1D,QAAM,aAAuD,CAAC;AAC9D,QAAM,SAAmD,CAAC;AAE1D,WAAS,QAAQ,GAAG,QAAQ,WAAW,QAAQ,SAAS,4BAA4B;AAClF,UAAM,QAAQ,WAAW,MAAM,OAAO,QAAQ,0BAA0B;AACxE,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,MAAM,IAAI,OAAO,eAAe;AAAA,QAC9B;AAAA,QACA,SAAS,MAAM,aAAa,KAAK,SAAS;AAAA,MAC5C,EAAE;AAAA,IACJ;AAEA,YAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,YAAM,YAAY,MAAM,KAAK;AAE7B,UAAI,OAAO,WAAW,aAAa;AACjC,mBAAW,KAAK,OAAO,KAAK;AAC5B;AAAA,MACF;AAEA,aAAO,KAAK,uBAAuB,WAAW,OAAO,MAAM,CAAC;AAAA,IAC9D,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,YAAY,OAAO;AAC9B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|