@lonca/trendyol 0.8.0 → 0.10.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/README.md +32 -26
- package/dist/client-0omWgpd_.d.cts +2526 -0
- package/dist/client-0omWgpd_.d.ts +2526 -0
- package/dist/index.cjs +505 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2183
- package/dist/index.d.ts +24 -2183
- package/dist/index.js +501 -20
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +2919 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +46 -0
- package/dist/testing.d.ts +46 -0
- package/dist/testing.js +2917 -0
- package/dist/testing.js.map +1 -0
- package/package.json +8 -3
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,2919 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@lonca/core');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
// src/resources/brands.ts
|
|
7
|
+
var DEFAULT_PAGE_SIZE = 1e3;
|
|
8
|
+
var BrandsResource = class {
|
|
9
|
+
constructor(transport, limiter) {
|
|
10
|
+
this.transport = transport;
|
|
11
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 50, intervalMs: 6e4 });
|
|
12
|
+
}
|
|
13
|
+
transport;
|
|
14
|
+
limiter;
|
|
15
|
+
/**
|
|
16
|
+
* List Trendyol brands, one page at a time.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { paginate } from '@lonca/core';
|
|
21
|
+
* for await (const brand of paginate((p) => client.brands.list(p))) {
|
|
22
|
+
* console.log(brand.id, brand.name);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
async list(params = {}) {
|
|
27
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
28
|
+
const size = params.limit ?? DEFAULT_PAGE_SIZE;
|
|
29
|
+
const data = await this.transport.request({
|
|
30
|
+
method: "GET",
|
|
31
|
+
path: "/integration/product/brands",
|
|
32
|
+
query: { page, size },
|
|
33
|
+
rateLimiter: this.limiter
|
|
34
|
+
});
|
|
35
|
+
const items = data.brands.map((b) => ({ id: String(b.id), name: b.name }));
|
|
36
|
+
const nextCursor = page + 1 < data.totalPages ? String(page + 1) : void 0;
|
|
37
|
+
return nextCursor !== void 0 ? { items, nextCursor } : { items };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Search brands by name. Useful when you need a brand's numeric ID for
|
|
41
|
+
* `createProducts` and don't want to page through the full `list()`
|
|
42
|
+
* (1000 brands per page).
|
|
43
|
+
*
|
|
44
|
+
* **Discovery-first wire fact (verified STAGE 2026-05-25):** Trendyol's
|
|
45
|
+
* doc claims this is a case-sensitive *exact* match, but live behaviour
|
|
46
|
+
* is **substring + case-insensitive** — `search('Trendyol')` returns
|
|
47
|
+
* 17 hits including `TRENDYOLMILLA`, `trendyol vavist`, `Trendyol Üyelik`.
|
|
48
|
+
* Plan for ranking your results client-side if you need an exact match.
|
|
49
|
+
* The endpoint returns an empty array when nothing matches (no 404).
|
|
50
|
+
*
|
|
51
|
+
* @param name The brand name to search for.
|
|
52
|
+
*/
|
|
53
|
+
async search(name) {
|
|
54
|
+
const data = await this.transport.request({
|
|
55
|
+
method: "GET",
|
|
56
|
+
path: "/integration/product/brands/by-name",
|
|
57
|
+
query: { name },
|
|
58
|
+
rateLimiter: this.limiter
|
|
59
|
+
});
|
|
60
|
+
return (data ?? []).map((b) => ({ id: String(b.id), name: b.name }));
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function normalizeCategory(node) {
|
|
64
|
+
return {
|
|
65
|
+
id: String(node.id),
|
|
66
|
+
name: node.name,
|
|
67
|
+
parentId: node.parentId === null || node.parentId === void 0 ? null : String(node.parentId),
|
|
68
|
+
subCategories: (node.subCategories ?? []).map(normalizeCategory)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function normalizeAttribute(node) {
|
|
72
|
+
const attr = node.attribute ?? { id: 0, name: "" };
|
|
73
|
+
const rawValues = node.attributeValues ?? [];
|
|
74
|
+
const out = {
|
|
75
|
+
id: String(attr.id),
|
|
76
|
+
name: attr.name,
|
|
77
|
+
required: !!node.required,
|
|
78
|
+
allowCustom: !!node.allowCustom,
|
|
79
|
+
varianter: !!node.varianter,
|
|
80
|
+
slicer: !!node.slicer,
|
|
81
|
+
values: rawValues.map((v) => ({ id: String(v.id), name: v.name }))
|
|
82
|
+
};
|
|
83
|
+
if (node.categoryId !== void 0) {
|
|
84
|
+
out.categoryId = String(node.categoryId);
|
|
85
|
+
}
|
|
86
|
+
if (node.allowMultipleAttributeValues !== void 0) {
|
|
87
|
+
out.allowMultipleAttributeValues = node.allowMultipleAttributeValues;
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
var MAX_ATTR_VALUES_PAGE_SIZE = 1e3;
|
|
92
|
+
var DEFAULT_ATTR_VALUES_PAGE_SIZE = 100;
|
|
93
|
+
var CategoriesResource = class {
|
|
94
|
+
constructor(transport, limiter) {
|
|
95
|
+
this.transport = transport;
|
|
96
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 50, intervalMs: 6e4 });
|
|
97
|
+
}
|
|
98
|
+
transport;
|
|
99
|
+
limiter;
|
|
100
|
+
/**
|
|
101
|
+
* Fetch the full Trendyol category tree.
|
|
102
|
+
*
|
|
103
|
+
* Trendyol returns the entire tree in one response — there is no pagination.
|
|
104
|
+
* Cache the result aggressively in your application; the tree changes rarely.
|
|
105
|
+
*/
|
|
106
|
+
async list() {
|
|
107
|
+
const data = await this.transport.request({
|
|
108
|
+
method: "GET",
|
|
109
|
+
path: "/integration/product/product-categories",
|
|
110
|
+
rateLimiter: this.limiter
|
|
111
|
+
});
|
|
112
|
+
const list = Array.isArray(data) ? data : data.categories;
|
|
113
|
+
return list.map(normalizeCategory);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Fetch the attributes (required and optional) for a single category.
|
|
117
|
+
*
|
|
118
|
+
* Call this before `createProduct V2` so you know which attributes are
|
|
119
|
+
* mandatory — the API rejects products that omit any `required` attribute.
|
|
120
|
+
*
|
|
121
|
+
* @param categoryId Trendyol numeric category ID; accepts `string` or `number`.
|
|
122
|
+
*/
|
|
123
|
+
async getAttributes(categoryId) {
|
|
124
|
+
const id = encodeURIComponent(String(categoryId));
|
|
125
|
+
const data = await this.transport.request({
|
|
126
|
+
method: "GET",
|
|
127
|
+
path: `/integration/product/categories/${id}/attributes`,
|
|
128
|
+
rateLimiter: this.limiter
|
|
129
|
+
});
|
|
130
|
+
const list = extractAttributeList(data);
|
|
131
|
+
if (!list) {
|
|
132
|
+
console.error(
|
|
133
|
+
"[@lonca/trendyol] getCategoryAttributes: unexpected response shape:",
|
|
134
|
+
JSON.stringify(data).slice(0, 800)
|
|
135
|
+
);
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
return list.map(normalizeAttribute);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Fetch the allowed values for a single category attribute (paginated).
|
|
142
|
+
*
|
|
143
|
+
* `getCategoryAttributes` returns attribute metadata + flags but typically
|
|
144
|
+
* omits the value catalog. Use this method to fetch the catalog for an
|
|
145
|
+
* attribute when `allowCustom` is `false` and you need to map your data
|
|
146
|
+
* onto Trendyol's accepted values.
|
|
147
|
+
*
|
|
148
|
+
* @param categoryId Trendyol numeric category ID; accepts `string` or `number`.
|
|
149
|
+
* @param attributeId Attribute ID returned by `getAttributes`.
|
|
150
|
+
* @param params Cursor pagination (max page size 1000; default 100).
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* import { paginate } from '@lonca/core';
|
|
155
|
+
* for await (const value of paginate((p) =>
|
|
156
|
+
* client.categories.getAttributeValues(catId, attrId, p),
|
|
157
|
+
* )) {
|
|
158
|
+
* console.log(value.id, value.name);
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async getAttributeValues(categoryId, attributeId, params = {}) {
|
|
163
|
+
const catId = encodeURIComponent(String(categoryId));
|
|
164
|
+
const attrId = encodeURIComponent(String(attributeId));
|
|
165
|
+
const size = Math.min(params.limit ?? DEFAULT_ATTR_VALUES_PAGE_SIZE, MAX_ATTR_VALUES_PAGE_SIZE);
|
|
166
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
167
|
+
const data = await this.transport.request({
|
|
168
|
+
method: "GET",
|
|
169
|
+
path: `/integration/product/categories/${catId}/attributes/${attrId}/values`,
|
|
170
|
+
query: { page, size },
|
|
171
|
+
rateLimiter: this.limiter
|
|
172
|
+
});
|
|
173
|
+
const items = (data.content ?? []).map((item) => ({
|
|
174
|
+
id: item.attributeValueId !== void 0 ? String(item.attributeValueId) : "",
|
|
175
|
+
name: item.attributeValue ?? item.attributeValueName ?? ""
|
|
176
|
+
}));
|
|
177
|
+
const result = { items };
|
|
178
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
179
|
+
if (page + 1 < totalPages) {
|
|
180
|
+
result.nextCursor = String(page + 1);
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Look up category info for a list of barcodes (Trendyol Export Center
|
|
186
|
+
* / AutoFT endpoint).
|
|
187
|
+
*
|
|
188
|
+
* **Requires Export Center enrollment.** Sellers who have not joined
|
|
189
|
+
* Trendyol's "İhracat Merkezi" program will get an auth error on this
|
|
190
|
+
* endpoint even though their regular Marketplace credentials are valid.
|
|
191
|
+
*
|
|
192
|
+
* @param barcodes 1–N barcodes to look up.
|
|
193
|
+
* @throws {ValidationError} when `barcodes` is empty.
|
|
194
|
+
*/
|
|
195
|
+
async getByBarcodes(barcodes) {
|
|
196
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
197
|
+
throw new core.ValidationError({ message: "getByBarcodes: barcodes must not be empty" });
|
|
198
|
+
}
|
|
199
|
+
const data = await this.transport.request({
|
|
200
|
+
method: "POST",
|
|
201
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/product-categories/by-barcodes`,
|
|
202
|
+
body: { barcodes },
|
|
203
|
+
rateLimiter: this.limiter
|
|
204
|
+
});
|
|
205
|
+
const matches = Object.entries(data.barcodeCategories ?? {}).map(([barcode, cat]) => ({
|
|
206
|
+
barcode,
|
|
207
|
+
category: {
|
|
208
|
+
id: cat.id !== void 0 ? String(cat.id) : "",
|
|
209
|
+
name: cat.displayName ?? ""
|
|
210
|
+
}
|
|
211
|
+
}));
|
|
212
|
+
return { matches, notFound: data.notFound ?? [] };
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
function extractAttributeList(data) {
|
|
216
|
+
if (Array.isArray(data)) return data;
|
|
217
|
+
if (data && typeof data === "object") {
|
|
218
|
+
const obj = data;
|
|
219
|
+
if (Array.isArray(obj.categoryAttributes)) {
|
|
220
|
+
return obj.categoryAttributes;
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(obj.attributes)) {
|
|
223
|
+
return obj.attributes;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
function toIso(epochMs) {
|
|
229
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
return new Date(epochMs).toISOString();
|
|
233
|
+
}
|
|
234
|
+
function normalizeClaim(node) {
|
|
235
|
+
const out = {
|
|
236
|
+
id: node.id ?? node.claimId ?? "",
|
|
237
|
+
orderNumber: node.orderNumber ?? "",
|
|
238
|
+
raw: node
|
|
239
|
+
};
|
|
240
|
+
const orderDate = toIso(node.orderDate);
|
|
241
|
+
if (orderDate) out.orderDate = orderDate;
|
|
242
|
+
const claimDate = toIso(node.claimDate);
|
|
243
|
+
if (claimDate) out.claimDate = claimDate;
|
|
244
|
+
if (node.customerFirstName !== void 0) out.customerFirstName = node.customerFirstName;
|
|
245
|
+
if (node.customerLastName !== void 0) out.customerLastName = node.customerLastName;
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
var ClaimsResource = class {
|
|
249
|
+
constructor(transport, limiter) {
|
|
250
|
+
this.transport = transport;
|
|
251
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
252
|
+
}
|
|
253
|
+
transport;
|
|
254
|
+
limiter;
|
|
255
|
+
/**
|
|
256
|
+
* Create a return claim against an order. Use this to file a return on
|
|
257
|
+
* behalf of a customer (e.g. when they called your CS line). For
|
|
258
|
+
* customer-initiated returns coming from trendyol.com, you receive them
|
|
259
|
+
* via `claims.list()` — no need to call `create`.
|
|
260
|
+
*
|
|
261
|
+
* Returns whatever Trendyol returns (typically the new claim's identifier).
|
|
262
|
+
*
|
|
263
|
+
* @throws {ValidationError} when `claimItems` is empty.
|
|
264
|
+
*/
|
|
265
|
+
async create(input) {
|
|
266
|
+
if (!Array.isArray(input?.claimItems) || input.claimItems.length === 0) {
|
|
267
|
+
throw new core.ValidationError({ message: "claims.create: claimItems must not be empty" });
|
|
268
|
+
}
|
|
269
|
+
return this.transport.request({
|
|
270
|
+
method: "POST",
|
|
271
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/claims/create`,
|
|
272
|
+
body: input,
|
|
273
|
+
rateLimiter: this.limiter
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* File a seller-side rejection ("ret talebi") against a customer claim.
|
|
278
|
+
*
|
|
279
|
+
* **Wire format: `multipart/form-data`** — the SDK builds the FormData
|
|
280
|
+
* internally from the typed input. `claimItemIdList` is joined with
|
|
281
|
+
* commas (Trendyol expects a single comma-separated string field).
|
|
282
|
+
* Attach supporting docs (PDF / JPEG) via `files: [Blob, ...]`.
|
|
283
|
+
*/
|
|
284
|
+
async createIssue(claimId, input) {
|
|
285
|
+
if (!Array.isArray(input?.claimItemIdList) || input.claimItemIdList.length === 0) {
|
|
286
|
+
throw new core.ValidationError({
|
|
287
|
+
message: "claims.createIssue: claimItemIdList must not be empty"
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (typeof input.description !== "string" || input.description.length === 0) {
|
|
291
|
+
throw new core.ValidationError({ message: "claims.createIssue: description is required" });
|
|
292
|
+
}
|
|
293
|
+
if (input.description.length > 500) {
|
|
294
|
+
throw new core.ValidationError({
|
|
295
|
+
message: `claims.createIssue: description must be \u2264500 chars (got ${input.description.length})`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const form = new FormData();
|
|
299
|
+
form.append("claimIssueReasonId", String(input.claimIssueReasonId));
|
|
300
|
+
form.append("claimItemIdList", input.claimItemIdList.join(","));
|
|
301
|
+
form.append("description", input.description);
|
|
302
|
+
if (input.files) {
|
|
303
|
+
for (const file of input.files) {
|
|
304
|
+
form.append("files", file);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return this.transport.request({
|
|
308
|
+
method: "POST",
|
|
309
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/claims/${encodeURIComponent(claimId)}/issue`,
|
|
310
|
+
body: form,
|
|
311
|
+
rateLimiter: this.limiter
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Approve specific claim line items. After approval, Trendyol moves
|
|
316
|
+
* those line items into the post-approval refund / return-shipping flow.
|
|
317
|
+
*
|
|
318
|
+
* @throws {ValidationError} when `claimLineItemIdList` is empty.
|
|
319
|
+
*/
|
|
320
|
+
async approveLineItems(claimId, input) {
|
|
321
|
+
if (!Array.isArray(input?.claimLineItemIdList) || input.claimLineItemIdList.length === 0) {
|
|
322
|
+
throw new core.ValidationError({
|
|
323
|
+
message: "claims.approveLineItems: claimLineItemIdList must not be empty"
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return this.transport.request({
|
|
327
|
+
method: "PUT",
|
|
328
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/claims/${encodeURIComponent(claimId)}/items/approve`,
|
|
329
|
+
body: input,
|
|
330
|
+
rateLimiter: this.limiter
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* List claims (page-based; SDK exposes opaque cursor convention).
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* import { paginate } from '@lonca/core';
|
|
339
|
+
* for await (const c of paginate((p) =>
|
|
340
|
+
* client.claims.list({ ...p, claimItemStatus: 'WaitingInAction' }),
|
|
341
|
+
* )) {
|
|
342
|
+
* console.log(c.id, c.orderNumber, c.claimDate);
|
|
343
|
+
* }
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
async list(params = {}) {
|
|
347
|
+
const size = Math.min(params.limit ?? 50, 200);
|
|
348
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
349
|
+
const query = { page, size };
|
|
350
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
351
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
352
|
+
if (params.claimItemStatus) query.claimItemStatus = params.claimItemStatus;
|
|
353
|
+
const data = await this.transport.request({
|
|
354
|
+
method: "GET",
|
|
355
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/claims`,
|
|
356
|
+
query,
|
|
357
|
+
rateLimiter: this.limiter
|
|
358
|
+
});
|
|
359
|
+
const items = (data.content ?? []).map(normalizeClaim);
|
|
360
|
+
const result = { items };
|
|
361
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
362
|
+
if (page + 1 < totalPages) {
|
|
363
|
+
result.nextCursor = String(page + 1);
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Fetch the catalog of rejection-reason IDs the seller can use on
|
|
369
|
+
* `claims.createIssue()`. Cache the result — it changes rarely.
|
|
370
|
+
*
|
|
371
|
+
* Note: this endpoint is **not seller-scoped** (no `sellerId` in path).
|
|
372
|
+
*/
|
|
373
|
+
async getIssueReasons() {
|
|
374
|
+
const data = await this.transport.request({
|
|
375
|
+
method: "GET",
|
|
376
|
+
path: `/integration/order/claim-issue-reasons`,
|
|
377
|
+
rateLimiter: this.limiter
|
|
378
|
+
});
|
|
379
|
+
return (data ?? []).map((r) => ({
|
|
380
|
+
id: typeof r.id === "number" ? r.id : 0,
|
|
381
|
+
name: r.name ?? ""
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Fetch the audit log for a single claim item (state transitions,
|
|
386
|
+
* actor, timestamp). Trendyol's response shape varies — the SDK
|
|
387
|
+
* surfaces each row as `{ raw }` and leaves field extraction to the
|
|
388
|
+
* caller until we observe a stable shape on the wire.
|
|
389
|
+
*/
|
|
390
|
+
async getItemAudits(claimItemId) {
|
|
391
|
+
const data = await this.transport.request({
|
|
392
|
+
method: "GET",
|
|
393
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/claims/items/${encodeURIComponent(claimItemId)}/audit`,
|
|
394
|
+
rateLimiter: this.limiter
|
|
395
|
+
});
|
|
396
|
+
const rows = Array.isArray(data) ? data : Array.isArray(data?.content) ? data.content : [];
|
|
397
|
+
return rows.map((row) => ({ raw: row }));
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var MAX_BATCH_ITEMS = 5e3;
|
|
401
|
+
var ExportCenterResource = class {
|
|
402
|
+
constructor(transport, limiter) {
|
|
403
|
+
this.transport = transport;
|
|
404
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
405
|
+
}
|
|
406
|
+
transport;
|
|
407
|
+
limiter;
|
|
408
|
+
// ─── Products ────────────────────────────────────────────────────────────
|
|
409
|
+
/**
|
|
410
|
+
* List Export Center-approved products. Uses Trendyol's `pageKey`
|
|
411
|
+
* pagination — the first call leaves `pageKey` empty; subsequent
|
|
412
|
+
* calls pass the `x-paging-key` value from the previous response.
|
|
413
|
+
*/
|
|
414
|
+
async listProducts(params = {}) {
|
|
415
|
+
const query = {};
|
|
416
|
+
if (params.barcodes && params.barcodes.length > 0) query.barcodes = params.barcodes.join(",");
|
|
417
|
+
if (params.pageKey) query.pageKey = params.pageKey;
|
|
418
|
+
if (params.size !== void 0) query.size = params.size;
|
|
419
|
+
const data = await this.transport.request({
|
|
420
|
+
method: "GET",
|
|
421
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/products`,
|
|
422
|
+
query: Object.keys(query).length ? query : void 0,
|
|
423
|
+
rateLimiter: this.limiter
|
|
424
|
+
});
|
|
425
|
+
const rows = pickArray(data);
|
|
426
|
+
return rows.map((r) => ({ raw: r ?? {} }));
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Create Export Center products. Maximum 5000 per call. Returns a
|
|
430
|
+
* `batchId` you can poll via `getBatchStatus(batchId)`.
|
|
431
|
+
*
|
|
432
|
+
* @throws {ValidationError} when `products` is empty / oversized.
|
|
433
|
+
*/
|
|
434
|
+
async createProducts(products) {
|
|
435
|
+
this.assertBatch(products, "createProducts");
|
|
436
|
+
const data = await this.transport.request({
|
|
437
|
+
method: "POST",
|
|
438
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/products`,
|
|
439
|
+
body: { products },
|
|
440
|
+
rateLimiter: this.limiter
|
|
441
|
+
});
|
|
442
|
+
return normalizeBatchResponse(data);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Update Export Center prices. **Trendyol allows one price update per
|
|
446
|
+
* barcode per day.** Returns a `batchId`.
|
|
447
|
+
*
|
|
448
|
+
* @throws {ValidationError} when `priceInfos` is empty / oversized.
|
|
449
|
+
*/
|
|
450
|
+
async updatePrices(priceInfos) {
|
|
451
|
+
this.assertBatch(priceInfos, "updatePrices");
|
|
452
|
+
const data = await this.transport.request({
|
|
453
|
+
method: "POST",
|
|
454
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/prices`,
|
|
455
|
+
body: { priceInfos },
|
|
456
|
+
rateLimiter: this.limiter
|
|
457
|
+
});
|
|
458
|
+
return normalizeBatchResponse(data);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Update Export Center stocks. Returns a `batchId`. Sellers using
|
|
462
|
+
* Trendyol's shared inventory cannot use this endpoint (per portal docs).
|
|
463
|
+
*
|
|
464
|
+
* @throws {ValidationError} when `items` is empty / oversized.
|
|
465
|
+
*/
|
|
466
|
+
async updateStocks(items) {
|
|
467
|
+
this.assertBatch(items, "updateStocks");
|
|
468
|
+
const data = await this.transport.request({
|
|
469
|
+
method: "POST",
|
|
470
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/stocks`,
|
|
471
|
+
body: { items },
|
|
472
|
+
rateLimiter: this.limiter
|
|
473
|
+
});
|
|
474
|
+
return normalizeBatchResponse(data);
|
|
475
|
+
}
|
|
476
|
+
// ─── Batch status ────────────────────────────────────────────────────────
|
|
477
|
+
/**
|
|
478
|
+
* Look up the status of a previously-submitted batch. Trendyol retains
|
|
479
|
+
* batch records for **24 hours** only — older `batchId`s return `404`.
|
|
480
|
+
*/
|
|
481
|
+
async getBatchStatus(batchId) {
|
|
482
|
+
if (!batchId) {
|
|
483
|
+
throw new core.ValidationError({ message: "exportCenter.getBatchStatus: batchId is required" });
|
|
484
|
+
}
|
|
485
|
+
const data = await this.transport.request({
|
|
486
|
+
method: "GET",
|
|
487
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/check-status`,
|
|
488
|
+
query: { batchId },
|
|
489
|
+
rateLimiter: this.limiter
|
|
490
|
+
});
|
|
491
|
+
return normalizeBatchStatus(data);
|
|
492
|
+
}
|
|
493
|
+
// ─── Packages ────────────────────────────────────────────────────────────
|
|
494
|
+
/** List daily Export Center packages (V2 — query-based filters). */
|
|
495
|
+
async listPackagesV2(params = {}) {
|
|
496
|
+
const data = await this.transport.request({
|
|
497
|
+
method: "GET",
|
|
498
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/packages`,
|
|
499
|
+
query: {
|
|
500
|
+
trackingNumber: params.trackingNumber,
|
|
501
|
+
status: params.status,
|
|
502
|
+
creationStartDate: params.creationStartDate,
|
|
503
|
+
creationEndDate: params.creationEndDate,
|
|
504
|
+
size: params.size,
|
|
505
|
+
boutiqueId: params.boutiqueId
|
|
506
|
+
},
|
|
507
|
+
rateLimiter: this.limiter
|
|
508
|
+
});
|
|
509
|
+
return pickArray(data).map(normalizePackage);
|
|
510
|
+
}
|
|
511
|
+
/** List Export Center packages (V3 — consolidated, page-based). */
|
|
512
|
+
async listPackagesV3(params = {}) {
|
|
513
|
+
const data = await this.transport.request({
|
|
514
|
+
method: "GET",
|
|
515
|
+
path: `/integration/ecgw/v3/${this.transport.sellerId}/packages`,
|
|
516
|
+
query: {
|
|
517
|
+
status: params.status,
|
|
518
|
+
page: params.offset !== void 0 ? params.offset : void 0,
|
|
519
|
+
size: params.limit,
|
|
520
|
+
creationStartDate: params.creationStartDate,
|
|
521
|
+
creationEndDate: params.creationEndDate
|
|
522
|
+
},
|
|
523
|
+
rateLimiter: this.limiter
|
|
524
|
+
});
|
|
525
|
+
return pickArray(data).map(normalizePackage);
|
|
526
|
+
}
|
|
527
|
+
/** Get the line items inside an Export Center package. */
|
|
528
|
+
async getPackageItems(params) {
|
|
529
|
+
if (!params || !params.packageId) {
|
|
530
|
+
throw new core.ValidationError({
|
|
531
|
+
message: "exportCenter.getPackageItems: packageId is required"
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const data = await this.transport.request({
|
|
535
|
+
method: "GET",
|
|
536
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/packages/items`,
|
|
537
|
+
query: {
|
|
538
|
+
packageId: params.packageId,
|
|
539
|
+
status: params.status,
|
|
540
|
+
page: params.offset,
|
|
541
|
+
size: params.limit
|
|
542
|
+
},
|
|
543
|
+
rateLimiter: this.limiter
|
|
544
|
+
});
|
|
545
|
+
return pickArray(data).map((r) => ({ raw: r ?? {} }));
|
|
546
|
+
}
|
|
547
|
+
// ─── Lookup ──────────────────────────────────────────────────────────────
|
|
548
|
+
/** Get the required attributes for an Export Center category. */
|
|
549
|
+
async getCategoryAttributes(categoryId) {
|
|
550
|
+
if (categoryId === void 0 || categoryId === null || categoryId === "") {
|
|
551
|
+
throw new core.ValidationError({
|
|
552
|
+
message: "exportCenter.getCategoryAttributes: categoryId is required"
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
const data = await this.transport.request({
|
|
556
|
+
method: "GET",
|
|
557
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/product-categories/${encodeURIComponent(String(categoryId))}/attributes`,
|
|
558
|
+
rateLimiter: this.limiter
|
|
559
|
+
});
|
|
560
|
+
return pickArray(data).map((r) => {
|
|
561
|
+
const row = r ?? {};
|
|
562
|
+
const out = { raw: row };
|
|
563
|
+
if (typeof row.attributeId === "number" || typeof row.attributeId === "string") {
|
|
564
|
+
out.attributeId = row.attributeId;
|
|
565
|
+
}
|
|
566
|
+
if (typeof row.attributeName === "string") out.attributeName = row.attributeName;
|
|
567
|
+
if (typeof row.required === "boolean") out.required = row.required;
|
|
568
|
+
if (Array.isArray(row.values)) out.values = row.values;
|
|
569
|
+
return out;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
/** Get the care-instruction lookup values used by `createProducts`. */
|
|
573
|
+
async getCareInstructions() {
|
|
574
|
+
const data = await this.transport.request({
|
|
575
|
+
method: "GET",
|
|
576
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/care-instructions`,
|
|
577
|
+
rateLimiter: this.limiter
|
|
578
|
+
});
|
|
579
|
+
return pickArray(data).map(normalizeNamedRef);
|
|
580
|
+
}
|
|
581
|
+
/** Get the material-composition lookup values used by `createProducts`. */
|
|
582
|
+
async getCompositions() {
|
|
583
|
+
const data = await this.transport.request({
|
|
584
|
+
method: "GET",
|
|
585
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/compositions`,
|
|
586
|
+
rateLimiter: this.limiter
|
|
587
|
+
});
|
|
588
|
+
return pickArray(data).map(normalizeNamedRef);
|
|
589
|
+
}
|
|
590
|
+
/** Get the country-of-origin lookup values used by `createProducts`. */
|
|
591
|
+
async getOrigins() {
|
|
592
|
+
const data = await this.transport.request({
|
|
593
|
+
method: "GET",
|
|
594
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/origins`,
|
|
595
|
+
rateLimiter: this.limiter
|
|
596
|
+
});
|
|
597
|
+
return pickArray(data).map((r) => {
|
|
598
|
+
const row = r ?? {};
|
|
599
|
+
const out = { raw: row };
|
|
600
|
+
if (typeof row.id === "number" || typeof row.id === "string") out.id = row.id;
|
|
601
|
+
if (typeof row.name === "string") out.name = row.name;
|
|
602
|
+
if (typeof row.countryCode === "string") out.countryCode = row.countryCode;
|
|
603
|
+
return out;
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
607
|
+
assertBatch(items, methodLabel) {
|
|
608
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
609
|
+
throw new core.ValidationError({
|
|
610
|
+
message: `exportCenter.${methodLabel}: items must not be empty`
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
if (items.length > MAX_BATCH_ITEMS) {
|
|
614
|
+
throw new core.ValidationError({
|
|
615
|
+
message: `exportCenter.${methodLabel}: max ${MAX_BATCH_ITEMS} items per call (got ${items.length})`
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
function pickArray(data) {
|
|
621
|
+
if (Array.isArray(data)) return data;
|
|
622
|
+
if (data && typeof data === "object") {
|
|
623
|
+
const obj = data;
|
|
624
|
+
if (Array.isArray(obj.content)) return obj.content;
|
|
625
|
+
if (Array.isArray(obj.items)) return obj.items;
|
|
626
|
+
if (Array.isArray(obj.data)) return obj.data;
|
|
627
|
+
if (Array.isArray(obj.products)) return obj.products;
|
|
628
|
+
if (Array.isArray(obj.packages)) return obj.packages;
|
|
629
|
+
}
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
function normalizeBatchResponse(data) {
|
|
633
|
+
const obj = data && typeof data === "object" ? data : {};
|
|
634
|
+
return {
|
|
635
|
+
batchId: String(obj.batchId ?? obj.batchRequestId ?? obj.id ?? ""),
|
|
636
|
+
raw: obj
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function normalizeBatchStatus(data) {
|
|
640
|
+
const obj = data && typeof data === "object" ? data : {};
|
|
641
|
+
const out = { raw: obj };
|
|
642
|
+
if (typeof obj.batchId === "string") out.batchId = obj.batchId;
|
|
643
|
+
if (typeof obj.status === "string") out.status = obj.status;
|
|
644
|
+
if (typeof obj.itemCount === "number") out.itemCount = obj.itemCount;
|
|
645
|
+
if (typeof obj.failedItemCount === "number") out.failedItemCount = obj.failedItemCount;
|
|
646
|
+
if (Array.isArray(obj.items)) out.items = obj.items;
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
function normalizePackage(row) {
|
|
650
|
+
const r = row ?? {};
|
|
651
|
+
const out = { raw: r };
|
|
652
|
+
if (typeof r.packageNumber === "string") out.packageNumber = r.packageNumber;
|
|
653
|
+
if (typeof r.status === "string") out.status = r.status;
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
function normalizeNamedRef(row) {
|
|
657
|
+
const r = row ?? {};
|
|
658
|
+
const out = { raw: r };
|
|
659
|
+
if (typeof r.id === "number" || typeof r.id === "string") out.id = r.id;
|
|
660
|
+
if (typeof r.name === "string") out.name = r.name;
|
|
661
|
+
return out;
|
|
662
|
+
}
|
|
663
|
+
function toIso2(epochMs) {
|
|
664
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
665
|
+
return void 0;
|
|
666
|
+
}
|
|
667
|
+
return new Date(epochMs).toISOString();
|
|
668
|
+
}
|
|
669
|
+
function normalizeRow(row) {
|
|
670
|
+
const out = {
|
|
671
|
+
id: row.id !== void 0 ? String(row.id) : "",
|
|
672
|
+
raw: row
|
|
673
|
+
};
|
|
674
|
+
const tx = toIso2(row.transactionDate);
|
|
675
|
+
if (tx) out.transactionDate = tx;
|
|
676
|
+
if (row.barcode !== void 0) out.barcode = row.barcode;
|
|
677
|
+
if (row.transactionType !== void 0) out.transactionType = row.transactionType;
|
|
678
|
+
if (row.receiptId !== void 0) out.receiptId = row.receiptId;
|
|
679
|
+
if (row.description !== void 0) out.description = row.description;
|
|
680
|
+
if (typeof row.debt === "number") out.debt = row.debt;
|
|
681
|
+
if (typeof row.credit === "number") out.credit = row.credit;
|
|
682
|
+
if (row.paymentPeriod !== void 0) out.paymentPeriod = row.paymentPeriod;
|
|
683
|
+
if (row.commissionRate !== void 0) out.commissionRate = row.commissionRate;
|
|
684
|
+
if (row.commissionAmount !== void 0) out.commissionAmount = row.commissionAmount;
|
|
685
|
+
if (row.commissionInvoiceSerialNumber !== void 0) {
|
|
686
|
+
out.commissionInvoiceSerialNumber = row.commissionInvoiceSerialNumber;
|
|
687
|
+
}
|
|
688
|
+
if (row.sellerRevenue !== void 0) out.sellerRevenue = row.sellerRevenue;
|
|
689
|
+
if (row.orderNumber !== void 0) out.orderNumber = row.orderNumber;
|
|
690
|
+
if (row.paymentOrderId !== void 0) out.paymentOrderId = row.paymentOrderId;
|
|
691
|
+
const pay = toIso2(row.paymentDate);
|
|
692
|
+
if (pay) out.paymentDate = pay;
|
|
693
|
+
if (typeof row.sellerId === "number") out.sellerId = row.sellerId;
|
|
694
|
+
if (row.storeId !== void 0) out.storeId = row.storeId;
|
|
695
|
+
if (row.storeName !== void 0) out.storeName = row.storeName;
|
|
696
|
+
if (row.storeAddress !== void 0) out.storeAddress = row.storeAddress;
|
|
697
|
+
if (row.country !== void 0) out.country = row.country;
|
|
698
|
+
return out;
|
|
699
|
+
}
|
|
700
|
+
var FinanceResource = class {
|
|
701
|
+
constructor(transport, limiter) {
|
|
702
|
+
this.transport = transport;
|
|
703
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 100, intervalMs: 6e4 });
|
|
704
|
+
}
|
|
705
|
+
transport;
|
|
706
|
+
limiter;
|
|
707
|
+
async getSettlements(params = {}) {
|
|
708
|
+
return this.queryPage(
|
|
709
|
+
`/integration/finance/che/sellers/${this.transport.sellerId}/settlements`,
|
|
710
|
+
params
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
async getOtherFinancials(params = {}) {
|
|
714
|
+
return this.queryPage(
|
|
715
|
+
`/integration/finance/che/sellers/${this.transport.sellerId}/otherfinancials`,
|
|
716
|
+
params
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
async queryPage(path, params) {
|
|
720
|
+
const size = Math.min(params.limit ?? 50, 200);
|
|
721
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
722
|
+
const query = { page, size };
|
|
723
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
724
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
725
|
+
if (params.transactionType) query.transactionType = params.transactionType;
|
|
726
|
+
const data = await this.transport.request({
|
|
727
|
+
method: "GET",
|
|
728
|
+
path,
|
|
729
|
+
query,
|
|
730
|
+
rateLimiter: this.limiter
|
|
731
|
+
});
|
|
732
|
+
const items = (data.content ?? []).map(normalizeRow);
|
|
733
|
+
const result = { items };
|
|
734
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
735
|
+
if (page + 1 < totalPages) {
|
|
736
|
+
result.nextCursor = String(page + 1);
|
|
737
|
+
}
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
var MAX_ITEMS_PER_REQUEST = 1e3;
|
|
742
|
+
async function pollBatchStatus(getStatus, batchRequestId, opts = {}) {
|
|
743
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
|
|
744
|
+
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
745
|
+
const deadline = Date.now() + timeoutMs;
|
|
746
|
+
for (; ; ) {
|
|
747
|
+
opts.signal?.throwIfAborted();
|
|
748
|
+
const result = await getStatus(batchRequestId);
|
|
749
|
+
if (result.status === "COMPLETED" || result.status === "FAILED") {
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
if (Date.now() >= deadline) {
|
|
753
|
+
throw new core.TimeoutError({
|
|
754
|
+
message: `Trendyol batch ${batchRequestId} did not settle within ${timeoutMs}ms (last status: ${result.status})`,
|
|
755
|
+
data: { batchRequestId, lastStatus: result.status, lastResult: result }
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
await delay(pollIntervalMs, opts.signal);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
var InventoryResource = class {
|
|
762
|
+
/**
|
|
763
|
+
* @param transport Trendyol transport.
|
|
764
|
+
* @param getBatchStatus Batch-status poller (wired by `createTrendyolClient`
|
|
765
|
+
* to `products.getBatchStatus`). Required only for `updateAndWait`.
|
|
766
|
+
*/
|
|
767
|
+
constructor(transport, getBatchStatus) {
|
|
768
|
+
this.transport = transport;
|
|
769
|
+
this.getBatchStatus = getBatchStatus;
|
|
770
|
+
}
|
|
771
|
+
transport;
|
|
772
|
+
getBatchStatus;
|
|
773
|
+
/**
|
|
774
|
+
* Update price and/or stock for one or more SKUs (by barcode).
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* ```ts
|
|
778
|
+
* const { batchRequestId } = await client.inventory.update([
|
|
779
|
+
* { barcode: 'ABC123', quantity: 42, salePrice: 199.9, listPrice: 249.9 },
|
|
780
|
+
* { barcode: 'XYZ789', quantity: 0 },
|
|
781
|
+
* ]);
|
|
782
|
+
* const status = await client.products.getBatchStatus(batchRequestId);
|
|
783
|
+
* ```
|
|
784
|
+
*
|
|
785
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
786
|
+
* @throws {ServerError} when Trendyol accepts the request but returns no
|
|
787
|
+
* `batchRequestId` (an unpollable response — surfaced loudly instead of
|
|
788
|
+
* handing back an empty id).
|
|
789
|
+
*/
|
|
790
|
+
async update(items) {
|
|
791
|
+
if (items.length === 0) {
|
|
792
|
+
throw new core.ValidationError({ message: "updatePriceAndInventory: items must not be empty" });
|
|
793
|
+
}
|
|
794
|
+
if (items.length > MAX_ITEMS_PER_REQUEST) {
|
|
795
|
+
throw new core.ValidationError({
|
|
796
|
+
message: `updatePriceAndInventory: max ${MAX_ITEMS_PER_REQUEST} items per request, got ${items.length}`
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
const data = await this.transport.request({
|
|
800
|
+
method: "POST",
|
|
801
|
+
path: `/integration/inventory/sellers/${this.transport.sellerId}/products/price-and-inventory`,
|
|
802
|
+
body: { items }
|
|
803
|
+
});
|
|
804
|
+
if (!data.batchRequestId) {
|
|
805
|
+
throw new core.ServerError({
|
|
806
|
+
message: "Trendyol updatePriceAndInventory returned no batchRequestId",
|
|
807
|
+
data: { response: data }
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return { batchRequestId: data.batchRequestId };
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Submit price/stock updates and wait for them to settle.
|
|
814
|
+
*
|
|
815
|
+
* Splits `items` into chunks of ≤1000, submits each via {@link update}, and
|
|
816
|
+
* polls each `batchRequestId` to a terminal state. Returns one
|
|
817
|
+
* `BatchRequestResult` per chunk (read `failedItemCount` / `items[]` for
|
|
818
|
+
* per-barcode outcomes). Compose `@lonca/core`'s `retry` around this for
|
|
819
|
+
* transient-error resilience.
|
|
820
|
+
*
|
|
821
|
+
* @throws {ValidationError} when `items` is empty.
|
|
822
|
+
* @throws {TimeoutError} when any chunk does not settle within `timeoutMs`;
|
|
823
|
+
* `error.data.batchRequestId` identifies the stuck chunk.
|
|
824
|
+
*/
|
|
825
|
+
async updateAndWait(items, opts = {}) {
|
|
826
|
+
if (items.length === 0) {
|
|
827
|
+
throw new core.ValidationError({ message: "updateAndWait: items must not be empty" });
|
|
828
|
+
}
|
|
829
|
+
const getStatus = this.getBatchStatus;
|
|
830
|
+
if (!getStatus) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
"updateAndWait requires a batch-status poller; obtain the client via createTrendyolClient()"
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const results = [];
|
|
836
|
+
for (const batch of chunk(items, MAX_ITEMS_PER_REQUEST)) {
|
|
837
|
+
const { batchRequestId } = await this.update(batch);
|
|
838
|
+
results.push(await pollBatchStatus(getStatus, batchRequestId, opts));
|
|
839
|
+
}
|
|
840
|
+
return results;
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
function chunk(items, size) {
|
|
844
|
+
if (items.length <= size) return [items];
|
|
845
|
+
const chunks = [];
|
|
846
|
+
for (let i = 0; i < items.length; i += size) {
|
|
847
|
+
chunks.push(items.slice(i, i + size));
|
|
848
|
+
}
|
|
849
|
+
return chunks;
|
|
850
|
+
}
|
|
851
|
+
function delay(ms, signal) {
|
|
852
|
+
return new Promise((resolve, reject) => {
|
|
853
|
+
if (signal?.aborted) {
|
|
854
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
const onAbort = () => {
|
|
858
|
+
clearTimeout(timer);
|
|
859
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
860
|
+
};
|
|
861
|
+
const timer = setTimeout(() => {
|
|
862
|
+
signal?.removeEventListener("abort", onAbort);
|
|
863
|
+
resolve();
|
|
864
|
+
}, ms);
|
|
865
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
var InvoicesResource = class {
|
|
869
|
+
constructor(transport, limiter) {
|
|
870
|
+
this.transport = transport;
|
|
871
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 500, intervalMs: 6e4 });
|
|
872
|
+
}
|
|
873
|
+
transport;
|
|
874
|
+
limiter;
|
|
875
|
+
/**
|
|
876
|
+
* Upload an invoice file for a shipment package. **Multipart**: the
|
|
877
|
+
* SDK builds the FormData internally from the typed input.
|
|
878
|
+
*
|
|
879
|
+
* Max 10 MB. Accepted formats: PDF, JPEG, PNG.
|
|
880
|
+
*/
|
|
881
|
+
async uploadFile(input) {
|
|
882
|
+
if (!input?.file) {
|
|
883
|
+
throw new core.ValidationError({ message: "invoices.uploadFile: file is required" });
|
|
884
|
+
}
|
|
885
|
+
if (!input.shipmentPackageId) {
|
|
886
|
+
throw new core.ValidationError({ message: "invoices.uploadFile: shipmentPackageId is required" });
|
|
887
|
+
}
|
|
888
|
+
const form = new FormData();
|
|
889
|
+
form.append("shipmentPackageId", String(input.shipmentPackageId));
|
|
890
|
+
form.append("file", input.file);
|
|
891
|
+
if (input.invoiceDateTime !== void 0) {
|
|
892
|
+
form.append("invoiceDateTime", String(input.invoiceDateTime));
|
|
893
|
+
}
|
|
894
|
+
if (input.invoiceNumber !== void 0) {
|
|
895
|
+
form.append("invoiceNumber", input.invoiceNumber);
|
|
896
|
+
}
|
|
897
|
+
return this.transport.request({
|
|
898
|
+
method: "POST",
|
|
899
|
+
path: `/integration/sellers/${this.transport.sellerId}/seller-invoice-file`,
|
|
900
|
+
body: form,
|
|
901
|
+
rateLimiter: this.limiter
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
/** Register an invoice URL with Trendyol (alternative to uploading the file). */
|
|
905
|
+
async sendLink(input) {
|
|
906
|
+
if (!input?.invoiceLink) {
|
|
907
|
+
throw new core.ValidationError({ message: "invoices.sendLink: invoiceLink is required" });
|
|
908
|
+
}
|
|
909
|
+
return this.transport.request({
|
|
910
|
+
method: "POST",
|
|
911
|
+
path: `/integration/sellers/${this.transport.sellerId}/seller-invoice-links`,
|
|
912
|
+
body: input,
|
|
913
|
+
rateLimiter: this.limiter
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/** Remove a previously-registered invoice link. */
|
|
917
|
+
async deleteLink(input) {
|
|
918
|
+
return this.transport.request({
|
|
919
|
+
method: "POST",
|
|
920
|
+
path: `/integration/sellers/${this.transport.sellerId}/seller-invoice-links/delete`,
|
|
921
|
+
body: input,
|
|
922
|
+
rateLimiter: this.limiter
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
var LabelsResource = class {
|
|
927
|
+
constructor(transport, limiter) {
|
|
928
|
+
this.transport = transport;
|
|
929
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 100, intervalMs: 6e4 });
|
|
930
|
+
}
|
|
931
|
+
transport;
|
|
932
|
+
limiter;
|
|
933
|
+
/**
|
|
934
|
+
* Request a common ZPL label for a cargo tracking number. After this
|
|
935
|
+
* returns, call `getCommon()` with the same `cargoTrackingNumber` to
|
|
936
|
+
* retrieve the generated label.
|
|
937
|
+
*
|
|
938
|
+
* @throws {ValidationError} when `format` is missing.
|
|
939
|
+
*/
|
|
940
|
+
async createCommon(cargoTrackingNumber, input) {
|
|
941
|
+
if (!input?.format) {
|
|
942
|
+
throw new core.ValidationError({ message: "labels.createCommon: format is required" });
|
|
943
|
+
}
|
|
944
|
+
return this.transport.request({
|
|
945
|
+
method: "POST",
|
|
946
|
+
path: `/integration/sellers/${this.transport.sellerId}/common-label/${encodeURIComponent(String(cargoTrackingNumber))}`,
|
|
947
|
+
body: input,
|
|
948
|
+
rateLimiter: this.limiter
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Retrieve the previously-created common label. Trendyol returns
|
|
953
|
+
* `{ data: [{ label, format }] }`; the SDK surfaces the array as
|
|
954
|
+
* `labels[]` for ergonomic access.
|
|
955
|
+
*
|
|
956
|
+
* Typically `labels.length === 1` per tracking number, but kept as an
|
|
957
|
+
* array to match the wire shape.
|
|
958
|
+
*/
|
|
959
|
+
async getCommon(cargoTrackingNumber) {
|
|
960
|
+
const raw = await this.transport.request({
|
|
961
|
+
method: "GET",
|
|
962
|
+
path: `/integration/sellers/${this.transport.sellerId}/common-label/${encodeURIComponent(String(cargoTrackingNumber))}`,
|
|
963
|
+
rateLimiter: this.limiter
|
|
964
|
+
});
|
|
965
|
+
const labels = (raw?.data ?? []).map((row) => ({
|
|
966
|
+
label: row.label ?? "",
|
|
967
|
+
format: row.format ?? "ZPL"
|
|
968
|
+
}));
|
|
969
|
+
return { labels, raw: raw ?? {} };
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
function n(rows, extract) {
|
|
973
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
974
|
+
return list.map((r) => {
|
|
975
|
+
const node = r;
|
|
976
|
+
return { ...extract(node), raw: node };
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
var LocationsResource = class {
|
|
980
|
+
constructor(transport, limiter) {
|
|
981
|
+
this.transport = transport;
|
|
982
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 60, intervalMs: 6e4 });
|
|
983
|
+
}
|
|
984
|
+
transport;
|
|
985
|
+
limiter;
|
|
986
|
+
/** List all supported countries (Türkiye + AZ + GULF + CEE). */
|
|
987
|
+
async getCountries() {
|
|
988
|
+
const data = await this.transport.request({
|
|
989
|
+
method: "GET",
|
|
990
|
+
path: `/integration/member/countries`,
|
|
991
|
+
rateLimiter: this.limiter
|
|
992
|
+
});
|
|
993
|
+
return n(data, (node) => ({
|
|
994
|
+
code: String(node.code ?? node.id ?? ""),
|
|
995
|
+
name: node.name
|
|
996
|
+
}));
|
|
997
|
+
}
|
|
998
|
+
// ─── Domestic (TR / AZ) ───────────────────────────────────────────────
|
|
999
|
+
async getTurkeyCities() {
|
|
1000
|
+
return this.cities(`/integration/member/countries/domestic/TR/cities`);
|
|
1001
|
+
}
|
|
1002
|
+
async getTurkeyDistricts(cityCode) {
|
|
1003
|
+
return this.districts(
|
|
1004
|
+
`/integration/member/countries/domestic/TR/cities/${encodeURIComponent(String(cityCode))}/districts`
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
async getTurkeyNeighborhoods(cityCode, districtCode) {
|
|
1008
|
+
return this.neighborhoods(
|
|
1009
|
+
`/integration/member/countries/domestic/TR/cities/${encodeURIComponent(String(cityCode))}/districts/${encodeURIComponent(String(districtCode))}/neighborhoods`
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
async getAzerbaijanCities() {
|
|
1013
|
+
return this.cities(`/integration/member/countries/domestic/AZ/cities`);
|
|
1014
|
+
}
|
|
1015
|
+
async getAzerbaijanDistricts(cityCode) {
|
|
1016
|
+
return this.districts(
|
|
1017
|
+
`/integration/member/countries/domestic/AZ/cities/${encodeURIComponent(String(cityCode))}/districts`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
// ─── International (GULF / CEE) ───────────────────────────────────────
|
|
1021
|
+
async getCitiesByCountry(countryCode) {
|
|
1022
|
+
return this.cities(`/integration/member/countries/${encodeURIComponent(countryCode)}/cities`);
|
|
1023
|
+
}
|
|
1024
|
+
async getDistrictsByCity(countryCode, cityId) {
|
|
1025
|
+
return this.districts(
|
|
1026
|
+
`/integration/member/countries/${encodeURIComponent(countryCode)}/cities/${encodeURIComponent(String(cityId))}/districts`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
// ─── Shared paginators ────────────────────────────────────────────────
|
|
1030
|
+
async cities(path) {
|
|
1031
|
+
const data = await this.transport.request({
|
|
1032
|
+
method: "GET",
|
|
1033
|
+
path,
|
|
1034
|
+
rateLimiter: this.limiter
|
|
1035
|
+
});
|
|
1036
|
+
return n(data, (node) => ({
|
|
1037
|
+
code: String(node.code ?? node.id ?? ""),
|
|
1038
|
+
name: node.name,
|
|
1039
|
+
countryCode: node.countryCode
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1042
|
+
async districts(path) {
|
|
1043
|
+
const data = await this.transport.request({
|
|
1044
|
+
method: "GET",
|
|
1045
|
+
path,
|
|
1046
|
+
rateLimiter: this.limiter
|
|
1047
|
+
});
|
|
1048
|
+
return n(data, (node) => ({
|
|
1049
|
+
code: String(node.code ?? node.id ?? ""),
|
|
1050
|
+
name: node.name,
|
|
1051
|
+
cityCode: node.cityCode !== void 0 ? String(node.cityCode) : void 0
|
|
1052
|
+
}));
|
|
1053
|
+
}
|
|
1054
|
+
async neighborhoods(path) {
|
|
1055
|
+
const data = await this.transport.request({
|
|
1056
|
+
method: "GET",
|
|
1057
|
+
path,
|
|
1058
|
+
rateLimiter: this.limiter
|
|
1059
|
+
});
|
|
1060
|
+
return n(data, (node) => ({
|
|
1061
|
+
code: String(node.code ?? node.id ?? ""),
|
|
1062
|
+
name: node.name,
|
|
1063
|
+
districtCode: node.districtCode !== void 0 ? String(node.districtCode) : void 0
|
|
1064
|
+
}));
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
var MAX_PAGE_SIZE = 200;
|
|
1068
|
+
var DEFAULT_PAGE_SIZE2 = 50;
|
|
1069
|
+
function toIso3(epochMs) {
|
|
1070
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
1071
|
+
return void 0;
|
|
1072
|
+
}
|
|
1073
|
+
return new Date(epochMs).toISOString();
|
|
1074
|
+
}
|
|
1075
|
+
function normalizeAddress(node) {
|
|
1076
|
+
if (!node) return void 0;
|
|
1077
|
+
const out = {};
|
|
1078
|
+
if (node.id !== void 0) out.id = String(node.id);
|
|
1079
|
+
if (node.firstName !== void 0) out.firstName = node.firstName;
|
|
1080
|
+
if (node.lastName !== void 0) out.lastName = node.lastName;
|
|
1081
|
+
if (node.fullName !== void 0) out.fullName = node.fullName;
|
|
1082
|
+
if (node.company !== void 0) out.company = node.company;
|
|
1083
|
+
if (node.address1 !== void 0) out.address1 = node.address1;
|
|
1084
|
+
if (node.address2 !== void 0) out.address2 = node.address2;
|
|
1085
|
+
if (node.fullAddress !== void 0) out.fullAddress = node.fullAddress;
|
|
1086
|
+
if (node.shortAddress !== void 0) out.shortAddress = node.shortAddress;
|
|
1087
|
+
if (node.city !== void 0) out.city = node.city;
|
|
1088
|
+
if (node.cityCode !== void 0) out.cityCode = node.cityCode;
|
|
1089
|
+
if (node.district !== void 0) out.district = node.district;
|
|
1090
|
+
if (node.districtId !== void 0) out.districtId = node.districtId;
|
|
1091
|
+
if (node.neighborhoodId !== void 0) out.neighborhoodId = node.neighborhoodId;
|
|
1092
|
+
if (node.countyId !== void 0) out.countyId = node.countyId;
|
|
1093
|
+
if (node.countyName !== void 0) out.countyName = node.countyName;
|
|
1094
|
+
if (node.stateName !== void 0) out.stateName = node.stateName;
|
|
1095
|
+
if (node.postalCode !== void 0) out.postalCode = node.postalCode;
|
|
1096
|
+
if (node.countryCode !== void 0) out.countryCode = node.countryCode;
|
|
1097
|
+
if (node.phone !== void 0) out.phone = node.phone;
|
|
1098
|
+
if (node.addressLines !== void 0) out.addressLines = node.addressLines;
|
|
1099
|
+
return out;
|
|
1100
|
+
}
|
|
1101
|
+
function normalizeLine(node) {
|
|
1102
|
+
const out = {
|
|
1103
|
+
id: node.lineId !== void 0 ? String(node.lineId) : "",
|
|
1104
|
+
quantity: typeof node.quantity === "number" ? node.quantity : 0,
|
|
1105
|
+
productName: node.productName ?? "",
|
|
1106
|
+
barcode: node.barcode ?? "",
|
|
1107
|
+
lineUnitPrice: typeof node.lineUnitPrice === "number" ? node.lineUnitPrice : 0,
|
|
1108
|
+
lineGrossAmount: typeof node.lineGrossAmount === "number" ? node.lineGrossAmount : 0,
|
|
1109
|
+
raw: node
|
|
1110
|
+
};
|
|
1111
|
+
if (node.productSize !== void 0) out.productSize = node.productSize;
|
|
1112
|
+
if (node.productColor !== void 0) out.productColor = node.productColor;
|
|
1113
|
+
if (node.stockCode !== void 0) out.stockCode = node.stockCode;
|
|
1114
|
+
if (node.contentId !== void 0) out.contentId = String(node.contentId);
|
|
1115
|
+
if (node.sellerId !== void 0) out.sellerId = String(node.sellerId);
|
|
1116
|
+
if (node.productCategoryId !== void 0) out.productCategoryId = String(node.productCategoryId);
|
|
1117
|
+
if (node.salesCampaignId !== void 0) out.salesCampaignId = String(node.salesCampaignId);
|
|
1118
|
+
if (node.currencyCode !== void 0) out.currencyCode = node.currencyCode;
|
|
1119
|
+
if (node.lineSellerDiscount !== void 0) out.lineSellerDiscount = node.lineSellerDiscount;
|
|
1120
|
+
if (node.lineTyDiscount !== void 0) out.lineTyDiscount = node.lineTyDiscount;
|
|
1121
|
+
if (node.lineTotalDiscount !== void 0) out.lineTotalDiscount = node.lineTotalDiscount;
|
|
1122
|
+
if (node.vatRate !== void 0) out.vatRate = node.vatRate;
|
|
1123
|
+
if (node.commission !== void 0) out.commission = node.commission;
|
|
1124
|
+
if (node.orderLineItemStatusName !== void 0) {
|
|
1125
|
+
out.orderLineItemStatusName = node.orderLineItemStatusName;
|
|
1126
|
+
}
|
|
1127
|
+
if (node.businessUnit !== void 0) out.businessUnit = node.businessUnit;
|
|
1128
|
+
if (Array.isArray(node.fastDeliveryOptions)) out.fastDeliveryOptions = node.fastDeliveryOptions;
|
|
1129
|
+
if (Array.isArray(node.discountDetails)) {
|
|
1130
|
+
out.discountDetails = node.discountDetails;
|
|
1131
|
+
}
|
|
1132
|
+
return out;
|
|
1133
|
+
}
|
|
1134
|
+
function normalizeHistory(node) {
|
|
1135
|
+
const out = { raw: node };
|
|
1136
|
+
if (node.status !== void 0) out.status = node.status;
|
|
1137
|
+
const createdAt = toIso3(node.createdDate);
|
|
1138
|
+
if (createdAt) out.createdAt = createdAt;
|
|
1139
|
+
return out;
|
|
1140
|
+
}
|
|
1141
|
+
function normalizeCustomer(node) {
|
|
1142
|
+
const out = {
|
|
1143
|
+
firstName: node.customerFirstName ?? "",
|
|
1144
|
+
lastName: node.customerLastName ?? ""
|
|
1145
|
+
};
|
|
1146
|
+
if (node.customerId !== void 0) out.id = String(node.customerId);
|
|
1147
|
+
if (node.customerEmail !== void 0) out.email = node.customerEmail;
|
|
1148
|
+
if (node.taxNumber !== void 0 && node.taxNumber !== null) out.taxNumber = node.taxNumber;
|
|
1149
|
+
if (node.identityNumber !== void 0) out.identityNumber = node.identityNumber;
|
|
1150
|
+
return out;
|
|
1151
|
+
}
|
|
1152
|
+
function normalizePackage2(node) {
|
|
1153
|
+
const pkgId = node.shipmentPackageId ?? node.id;
|
|
1154
|
+
const out = {
|
|
1155
|
+
id: pkgId !== void 0 ? String(pkgId) : "",
|
|
1156
|
+
orderNumber: node.orderNumber ?? "",
|
|
1157
|
+
status: node.status ?? "",
|
|
1158
|
+
customer: normalizeCustomer(node),
|
|
1159
|
+
orderDate: toIso3(node.orderDate) ?? "",
|
|
1160
|
+
lastModifiedDate: toIso3(node.lastModifiedDate) ?? "",
|
|
1161
|
+
currencyCode: node.currencyCode ?? "",
|
|
1162
|
+
packageTotalPrice: typeof node.packageTotalPrice === "number" ? node.packageTotalPrice : 0,
|
|
1163
|
+
packageGrossAmount: typeof node.packageGrossAmount === "number" ? node.packageGrossAmount : 0,
|
|
1164
|
+
packageSellerDiscount: typeof node.packageSellerDiscount === "number" ? node.packageSellerDiscount : 0,
|
|
1165
|
+
packageTyDiscount: typeof node.packageTyDiscount === "number" ? node.packageTyDiscount : 0,
|
|
1166
|
+
packageTotalDiscount: typeof node.packageTotalDiscount === "number" ? node.packageTotalDiscount : 0,
|
|
1167
|
+
lines: (node.lines ?? []).map(normalizeLine),
|
|
1168
|
+
packageHistories: (node.packageHistories ?? []).map(normalizeHistory),
|
|
1169
|
+
raw: node
|
|
1170
|
+
};
|
|
1171
|
+
if (node.shipmentNumber !== void 0) out.shipmentNumber = String(node.shipmentNumber);
|
|
1172
|
+
if (node.originPackageIds !== void 0 && node.originPackageIds !== null) {
|
|
1173
|
+
out.originPackageIds = node.originPackageIds.map((id) => String(id));
|
|
1174
|
+
}
|
|
1175
|
+
if (node.warehouseId !== void 0) out.warehouseId = String(node.warehouseId);
|
|
1176
|
+
if (node.supplierId !== void 0) out.supplierId = String(node.supplierId);
|
|
1177
|
+
if (node.shipmentPackageStatus !== void 0) {
|
|
1178
|
+
out.shipmentPackageStatus = node.shipmentPackageStatus;
|
|
1179
|
+
}
|
|
1180
|
+
const agreed = toIso3(node.agreedDeliveryDate);
|
|
1181
|
+
if (agreed) out.agreedDeliveryDate = agreed;
|
|
1182
|
+
const estStart = toIso3(node.estimatedDeliveryStartDate);
|
|
1183
|
+
if (estStart) out.estimatedDeliveryStartDate = estStart;
|
|
1184
|
+
const estEnd = toIso3(node.estimatedDeliveryEndDate);
|
|
1185
|
+
if (estEnd) out.estimatedDeliveryEndDate = estEnd;
|
|
1186
|
+
const origin = toIso3(node.originShipmentDate);
|
|
1187
|
+
if (origin) out.originShipmentDate = origin;
|
|
1188
|
+
const invoice = normalizeAddress(node.invoiceAddress);
|
|
1189
|
+
if (invoice) out.invoiceAddress = invoice;
|
|
1190
|
+
const shipment = normalizeAddress(node.shipmentAddress);
|
|
1191
|
+
if (shipment) out.shipmentAddress = shipment;
|
|
1192
|
+
if (node.deliveryAddressType !== void 0) out.deliveryAddressType = node.deliveryAddressType;
|
|
1193
|
+
if (node.cargoTrackingNumber !== void 0) {
|
|
1194
|
+
out.cargoTrackingNumber = String(node.cargoTrackingNumber);
|
|
1195
|
+
}
|
|
1196
|
+
if (node.cargoProviderName !== void 0) out.cargoProviderName = node.cargoProviderName;
|
|
1197
|
+
if (node.cargoProviderId !== void 0) out.cargoProviderId = String(node.cargoProviderId);
|
|
1198
|
+
if (node.cargoSenderNumber !== void 0) out.cargoSenderNumber = node.cargoSenderNumber;
|
|
1199
|
+
if (node.deliveryType !== void 0) out.deliveryType = node.deliveryType;
|
|
1200
|
+
if (node.whoPays !== void 0) out.whoPays = node.whoPays;
|
|
1201
|
+
if (node.timeSlotId !== void 0) out.timeSlotId = node.timeSlotId;
|
|
1202
|
+
if (typeof node.fastDelivery === "boolean") out.fastDelivery = node.fastDelivery;
|
|
1203
|
+
if (node.fastDeliveryType !== void 0) out.fastDeliveryType = node.fastDeliveryType;
|
|
1204
|
+
if (typeof node.deliveredByService === "boolean")
|
|
1205
|
+
out.deliveredByService = node.deliveredByService;
|
|
1206
|
+
if (typeof node.commercial === "boolean") out.commercial = node.commercial;
|
|
1207
|
+
if (typeof node.micro === "boolean") out.micro = node.micro;
|
|
1208
|
+
if (typeof node.giftBoxRequested === "boolean") out.giftBoxRequested = node.giftBoxRequested;
|
|
1209
|
+
if (typeof node["3pByTrendyol"] === "boolean") {
|
|
1210
|
+
out.threePByTrendyol = node["3pByTrendyol"];
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof node.containsDangerousProduct === "boolean") {
|
|
1213
|
+
out.containsDangerousProduct = node.containsDangerousProduct;
|
|
1214
|
+
}
|
|
1215
|
+
if (typeof node.isCod === "boolean") out.isCod = node.isCod;
|
|
1216
|
+
if (typeof node.is4P === "boolean") out.is4P = node.is4P;
|
|
1217
|
+
if (node.invoiceLink !== void 0) out.invoiceLink = node.invoiceLink;
|
|
1218
|
+
if (node.createdBy !== void 0) out.createdBy = node.createdBy;
|
|
1219
|
+
return out;
|
|
1220
|
+
}
|
|
1221
|
+
var OrdersResource = class {
|
|
1222
|
+
constructor(transport, limiter) {
|
|
1223
|
+
this.transport = transport;
|
|
1224
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
1225
|
+
}
|
|
1226
|
+
transport;
|
|
1227
|
+
limiter;
|
|
1228
|
+
/**
|
|
1229
|
+
* List shipment packages for the seller.
|
|
1230
|
+
*
|
|
1231
|
+
* @example
|
|
1232
|
+
* ```ts
|
|
1233
|
+
* import { paginate } from '@lonca/core';
|
|
1234
|
+
* for await (const pkg of paginate((p) => client.orders.list({ ...p, status: 'Created' }))) {
|
|
1235
|
+
* console.log(pkg.id, pkg.status, pkg.customer.firstName);
|
|
1236
|
+
* }
|
|
1237
|
+
* ```
|
|
1238
|
+
*/
|
|
1239
|
+
async list(params = {}) {
|
|
1240
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
|
|
1241
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1242
|
+
const query = { page, size };
|
|
1243
|
+
if (params.status) query.status = params.status;
|
|
1244
|
+
if (params.orderNumber) query.orderNumber = params.orderNumber;
|
|
1245
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
1246
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
1247
|
+
const data = await this.transport.request({
|
|
1248
|
+
method: "GET",
|
|
1249
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/orders`,
|
|
1250
|
+
query,
|
|
1251
|
+
rateLimiter: this.limiter
|
|
1252
|
+
});
|
|
1253
|
+
const items = (data.content ?? []).map(normalizePackage2);
|
|
1254
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
1255
|
+
const result = { items };
|
|
1256
|
+
if (page + 1 < totalPages) {
|
|
1257
|
+
result.nextCursor = String(page + 1);
|
|
1258
|
+
}
|
|
1259
|
+
return result;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Push a shipment-package status update.
|
|
1263
|
+
*
|
|
1264
|
+
* Trendyol restricts the seller-side push to two transitions:
|
|
1265
|
+
* - `Picking` — order picked up from the shelf / being prepared
|
|
1266
|
+
* - `Invoiced` — invoice issued, ready for cargo handoff
|
|
1267
|
+
*
|
|
1268
|
+
* Other transitions (`Shipped`, `Delivered`, etc.) are driven by Trendyol
|
|
1269
|
+
* or the cargo provider — call `processAlternativeDelivery` or
|
|
1270
|
+
* `manualDeliverByPackageId` if you ship outside Trendyol's cargo
|
|
1271
|
+
* network.
|
|
1272
|
+
*
|
|
1273
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
1274
|
+
*/
|
|
1275
|
+
async updatePackageStatus(packageId, input) {
|
|
1276
|
+
await this.transport.request({
|
|
1277
|
+
method: "PUT",
|
|
1278
|
+
path: this.packagePath(packageId),
|
|
1279
|
+
body: input,
|
|
1280
|
+
rateLimiter: this.limiter
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Notify Trendyol that one or more line items cannot be supplied
|
|
1285
|
+
* ("Tedarik Edememe Bildirimi"). Marks the listed line IDs as
|
|
1286
|
+
* `UnSupplied`. Trendyol cancels those quantities and notifies the
|
|
1287
|
+
* customer.
|
|
1288
|
+
*
|
|
1289
|
+
* `reasonId` is a numeric code Trendyol publishes separately — consult
|
|
1290
|
+
* the seller panel or the "Tedarik Edememe" docs for current values.
|
|
1291
|
+
*
|
|
1292
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
1293
|
+
*/
|
|
1294
|
+
async cancelPackageItem(packageId, input) {
|
|
1295
|
+
if (!Array.isArray(input?.lines) || input.lines.length === 0) {
|
|
1296
|
+
throw new core.ValidationError({ message: "cancelPackageItem: lines must not be empty" });
|
|
1297
|
+
}
|
|
1298
|
+
await this.transport.request({
|
|
1299
|
+
method: "PUT",
|
|
1300
|
+
path: `${this.packagePath(packageId)}/items/unsupplied`,
|
|
1301
|
+
body: input,
|
|
1302
|
+
rateLimiter: this.limiter
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Extend the agreed delivery date for a shipment package by 1, 2, or 3 days.
|
|
1307
|
+
* Trendyol enforces the [1, 3] range server-side; the SDK validates client-side
|
|
1308
|
+
* to fail fast.
|
|
1309
|
+
*
|
|
1310
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
1311
|
+
*/
|
|
1312
|
+
async extendDeliveryDate(packageId, extendedDayCount) {
|
|
1313
|
+
if (![1, 2, 3].includes(extendedDayCount)) {
|
|
1314
|
+
throw new core.ValidationError({
|
|
1315
|
+
message: `extendDeliveryDate: extendedDayCount must be 1, 2, or 3 (got ${extendedDayCount})`
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
await this.transport.request({
|
|
1319
|
+
method: "PUT",
|
|
1320
|
+
path: `${this.packagePath(packageId)}/extended-agreed-delivery-date`,
|
|
1321
|
+
body: { extendedDayCount },
|
|
1322
|
+
rateLimiter: this.limiter
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Notify Trendyol of an alternative delivery channel — used when the
|
|
1327
|
+
* seller is shipping via a non-Trendyol cargo provider. Trendyol then
|
|
1328
|
+
* either SMSes the customer the tracking link (when `isPhoneNumber` is
|
|
1329
|
+
* `true`) or stores the tracking URL on the package directly.
|
|
1330
|
+
*
|
|
1331
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
1332
|
+
*/
|
|
1333
|
+
async processAlternativeDelivery(packageId, input) {
|
|
1334
|
+
await this.transport.request({
|
|
1335
|
+
method: "PUT",
|
|
1336
|
+
path: `${this.packagePath(packageId)}/alternative-delivery`,
|
|
1337
|
+
body: input,
|
|
1338
|
+
rateLimiter: this.limiter
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Split a shipment package by moving a set of line IDs into a new
|
|
1343
|
+
* package. The original package keeps the remaining lines.
|
|
1344
|
+
*
|
|
1345
|
+
* @param packageId The package to split.
|
|
1346
|
+
* @param orderLineIds Line IDs to move into the new package (1+).
|
|
1347
|
+
* @throws {ValidationError} when `orderLineIds` is empty.
|
|
1348
|
+
*/
|
|
1349
|
+
async splitPackage(packageId, orderLineIds) {
|
|
1350
|
+
if (!Array.isArray(orderLineIds) || orderLineIds.length === 0) {
|
|
1351
|
+
throw new core.ValidationError({ message: "splitPackage: orderLineIds must not be empty" });
|
|
1352
|
+
}
|
|
1353
|
+
await this.transport.request({
|
|
1354
|
+
method: "POST",
|
|
1355
|
+
path: `${this.packagePath(packageId)}/split`,
|
|
1356
|
+
body: { orderLineIds },
|
|
1357
|
+
rateLimiter: this.limiter
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Split a shipment package by quantity. Each `QuantitySplit` entry
|
|
1362
|
+
* carves a single line into multiple packages — e.g. `{ orderLineId: 100,
|
|
1363
|
+
* quantities: [2, 2, 1] }` splits 5 units of line 100 into three packages
|
|
1364
|
+
* of 2 + 2 + 1.
|
|
1365
|
+
*
|
|
1366
|
+
* @throws {ValidationError} when `quantitySplit` is empty.
|
|
1367
|
+
*/
|
|
1368
|
+
async splitPackageByQuantity(packageId, quantitySplit) {
|
|
1369
|
+
if (!Array.isArray(quantitySplit) || quantitySplit.length === 0) {
|
|
1370
|
+
throw new core.ValidationError({
|
|
1371
|
+
message: "splitPackageByQuantity: quantitySplit must not be empty"
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
await this.transport.request({
|
|
1375
|
+
method: "POST",
|
|
1376
|
+
path: `${this.packagePath(packageId)}/quantity-split`,
|
|
1377
|
+
body: { quantitySplit },
|
|
1378
|
+
rateLimiter: this.limiter
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Split a shipment package into multiple new packages by grouping line
|
|
1383
|
+
* IDs. Each `SplitGroup` becomes one new package containing the listed
|
|
1384
|
+
* line IDs.
|
|
1385
|
+
*
|
|
1386
|
+
* @throws {ValidationError} when `splitGroups` is empty.
|
|
1387
|
+
*/
|
|
1388
|
+
async multiSplitPackage(packageId, splitGroups) {
|
|
1389
|
+
if (!Array.isArray(splitGroups) || splitGroups.length === 0) {
|
|
1390
|
+
throw new core.ValidationError({ message: "multiSplitPackage: splitGroups must not be empty" });
|
|
1391
|
+
}
|
|
1392
|
+
await this.transport.request({
|
|
1393
|
+
method: "POST",
|
|
1394
|
+
path: `${this.packagePath(packageId)}/multi-split`,
|
|
1395
|
+
body: { splitGroups },
|
|
1396
|
+
rateLimiter: this.limiter
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Split a shipment package into multiple new packages, each containing a
|
|
1401
|
+
* mix of line items at specific quantities. This is the most expressive
|
|
1402
|
+
* split — use it when you need fine-grained control over which line IDs
|
|
1403
|
+
* and how many of each end up in each new package.
|
|
1404
|
+
*
|
|
1405
|
+
* @throws {ValidationError} when `splitPackages` is empty.
|
|
1406
|
+
*/
|
|
1407
|
+
async splitMultiPackagesByQuantity(packageId, splitPackages) {
|
|
1408
|
+
if (!Array.isArray(splitPackages) || splitPackages.length === 0) {
|
|
1409
|
+
throw new core.ValidationError({
|
|
1410
|
+
message: "splitMultiPackagesByQuantity: splitPackages must not be empty"
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
await this.transport.request({
|
|
1414
|
+
method: "POST",
|
|
1415
|
+
path: `${this.packagePath(packageId)}/split-packages`,
|
|
1416
|
+
body: { splitPackages },
|
|
1417
|
+
rateLimiter: this.limiter
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Change the cargo provider on an existing shipment package. Use one of
|
|
1422
|
+
* Trendyol's documented marketplace cargo codes (`'YKMP'`, `'ARASMP'`,
|
|
1423
|
+
* `'SURATMP'`, etc.) — see `TrendyolCargoProvider` for the full list.
|
|
1424
|
+
*/
|
|
1425
|
+
async changeCargoProvider(packageId, cargoProvider) {
|
|
1426
|
+
await this.transport.request({
|
|
1427
|
+
method: "PUT",
|
|
1428
|
+
path: `${this.packagePath(packageId)}/cargo-providers`,
|
|
1429
|
+
body: { cargoProvider },
|
|
1430
|
+
rateLimiter: this.limiter
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Mark a shipment package as manually delivered via its package ID.
|
|
1435
|
+
* Used when the seller delivered the order outside Trendyol's cargo
|
|
1436
|
+
* network and needs to flip the package to `Delivered` after handover.
|
|
1437
|
+
*
|
|
1438
|
+
* No request body; Trendyol responds with 200 + empty body on success.
|
|
1439
|
+
*/
|
|
1440
|
+
async manualDeliverByPackageId(packageId) {
|
|
1441
|
+
await this.transport.request({
|
|
1442
|
+
method: "PUT",
|
|
1443
|
+
path: `${this.packagePath(packageId)}/manual-invoice-delivery`,
|
|
1444
|
+
rateLimiter: this.limiter
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Manual-deliver variant that takes the cargo tracking number instead
|
|
1449
|
+
* of the package ID. Useful when you only have the tracking number on
|
|
1450
|
+
* hand (e.g., from a cargo provider webhook).
|
|
1451
|
+
*
|
|
1452
|
+
* Note the path structure: tracking number sits at a sibling location,
|
|
1453
|
+
* not under `/shipment-packages/{id}/...`.
|
|
1454
|
+
*/
|
|
1455
|
+
async manualDeliverByTrackingNumber(cargoTrackingNumber) {
|
|
1456
|
+
await this.transport.request({
|
|
1457
|
+
method: "PUT",
|
|
1458
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/shipment-packages/manual-invoice-delivery-by-tracking-number/${encodeURIComponent(String(cargoTrackingNumber))}`,
|
|
1459
|
+
rateLimiter: this.limiter
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Mark a package as delivered through an authorized service ("yetkili
|
|
1464
|
+
* servis"). For appliance / installation-required products that are
|
|
1465
|
+
* delivered + installed by a third-party service partner.
|
|
1466
|
+
*/
|
|
1467
|
+
async markDeliveredByService(packageId) {
|
|
1468
|
+
await this.transport.request({
|
|
1469
|
+
method: "PUT",
|
|
1470
|
+
path: `${this.packagePath(packageId)}/delivered-by-service`,
|
|
1471
|
+
rateLimiter: this.limiter
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Update box / packaging metadata on a shipment package (desi value
|
|
1476
|
+
* and/or number of boxes). Either field can be sent alone.
|
|
1477
|
+
*/
|
|
1478
|
+
async updateBoxInfo(packageId, input) {
|
|
1479
|
+
await this.transport.request({
|
|
1480
|
+
method: "PUT",
|
|
1481
|
+
path: `${this.packagePath(packageId)}/box-info`,
|
|
1482
|
+
body: input,
|
|
1483
|
+
rateLimiter: this.limiter
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Update labor costs for one or more order lines.
|
|
1488
|
+
*
|
|
1489
|
+
* **Wire note:** Trendyol's request body is a raw array (no envelope),
|
|
1490
|
+
* not `{ items: [...] }`. The SDK forwards `items` verbatim.
|
|
1491
|
+
*
|
|
1492
|
+
* @throws {ValidationError} when `items` is empty.
|
|
1493
|
+
*/
|
|
1494
|
+
async updateLaborCosts(packageId, items) {
|
|
1495
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
1496
|
+
throw new core.ValidationError({ message: "updateLaborCosts: items must not be empty" });
|
|
1497
|
+
}
|
|
1498
|
+
await this.transport.request({
|
|
1499
|
+
method: "PUT",
|
|
1500
|
+
path: `${this.packagePath(packageId)}/labor-costs`,
|
|
1501
|
+
body: items,
|
|
1502
|
+
rateLimiter: this.limiter
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Reassign a shipment package to a different warehouse. `warehouseId`
|
|
1507
|
+
* comes from `client.suppliers.getAddresses()` (filter by `isShipmentAddress`).
|
|
1508
|
+
*/
|
|
1509
|
+
async updateWarehouse(packageId, warehouseId) {
|
|
1510
|
+
await this.transport.request({
|
|
1511
|
+
method: "PUT",
|
|
1512
|
+
path: `${this.packagePath(packageId)}/warehouse`,
|
|
1513
|
+
body: { warehouseId },
|
|
1514
|
+
rateLimiter: this.limiter
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Stream variant of `list()`. Trendyol's `getShipmentPackagesStream`
|
|
1519
|
+
* returns the same `ShipmentPackage` shape but paginates with an opaque
|
|
1520
|
+
* cursor — useful when the dataset is large and page-based pagination
|
|
1521
|
+
* would hit the 10 000-record cap.
|
|
1522
|
+
*
|
|
1523
|
+
* @example
|
|
1524
|
+
* ```ts
|
|
1525
|
+
* import { paginate } from '@lonca/core';
|
|
1526
|
+
* for await (const pkg of paginate((p) =>
|
|
1527
|
+
* client.orders.listStream({ ...p, packageItemStatuses: 'Created,Picking' }),
|
|
1528
|
+
* )) {
|
|
1529
|
+
* console.log(pkg.id, pkg.status);
|
|
1530
|
+
* }
|
|
1531
|
+
* ```
|
|
1532
|
+
*/
|
|
1533
|
+
async listStream(params = {}) {
|
|
1534
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
|
|
1535
|
+
const query = { size };
|
|
1536
|
+
if (params.cursor) query.nextCursor = params.cursor;
|
|
1537
|
+
if (params.packageItemStatuses) query.packageItemStatuses = params.packageItemStatuses;
|
|
1538
|
+
if (params.lastModifiedStartDate) {
|
|
1539
|
+
query.lastModifiedStartDate = params.lastModifiedStartDate.getTime();
|
|
1540
|
+
}
|
|
1541
|
+
if (params.lastModifiedEndDate) {
|
|
1542
|
+
query.lastModifiedEndDate = params.lastModifiedEndDate.getTime();
|
|
1543
|
+
}
|
|
1544
|
+
const data = await this.transport.request({
|
|
1545
|
+
method: "GET",
|
|
1546
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/orders/stream`,
|
|
1547
|
+
query,
|
|
1548
|
+
rateLimiter: this.limiter
|
|
1549
|
+
});
|
|
1550
|
+
const items = (data.content ?? []).map(normalizePackage2);
|
|
1551
|
+
const result = { items };
|
|
1552
|
+
if (data.hasMore && data.nextCursor) {
|
|
1553
|
+
result.nextCursor = data.nextCursor;
|
|
1554
|
+
}
|
|
1555
|
+
return result;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Fetch the per-parcel cargo-fee breakdown for a single cargo invoice
|
|
1559
|
+
* (Trendyol's `getCargoInvoiceItems`). Useful for reconciling Trendyol's
|
|
1560
|
+
* cargo deductions against your shipped packages.
|
|
1561
|
+
*
|
|
1562
|
+
* `invoiceSerialNumber` is sourced from the Current Account Statement
|
|
1563
|
+
* ("Cari Hesap Ekstresi") with `transactionType=DeductionInvoices`.
|
|
1564
|
+
*
|
|
1565
|
+
* Page-based pagination internally (cursor encodes the page index).
|
|
1566
|
+
*/
|
|
1567
|
+
async getCargoInvoiceItems(invoiceSerialNumber, params = {}) {
|
|
1568
|
+
const size = Math.min(params.limit ?? 500, 500);
|
|
1569
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1570
|
+
const data = await this.transport.request({
|
|
1571
|
+
method: "GET",
|
|
1572
|
+
path: `/integration/finance/che/sellers/${this.transport.sellerId}/cargo-invoice/${encodeURIComponent(invoiceSerialNumber)}/items`,
|
|
1573
|
+
query: { page, size },
|
|
1574
|
+
rateLimiter: this.limiter
|
|
1575
|
+
});
|
|
1576
|
+
const items = (data.content ?? []).map((row) => {
|
|
1577
|
+
const out = { raw: row };
|
|
1578
|
+
if (row.shipmentPackageType !== void 0) out.shipmentPackageType = row.shipmentPackageType;
|
|
1579
|
+
if (row.parcelUniqueId !== void 0) {
|
|
1580
|
+
out.parcelUniqueId = typeof row.parcelUniqueId === "string" ? row.parcelUniqueId : String(row.parcelUniqueId);
|
|
1581
|
+
}
|
|
1582
|
+
if (row.orderNumber !== void 0) out.orderNumber = row.orderNumber;
|
|
1583
|
+
if (typeof row.amount === "number") out.amount = row.amount;
|
|
1584
|
+
if (typeof row.desi === "number") out.desi = row.desi;
|
|
1585
|
+
return out;
|
|
1586
|
+
});
|
|
1587
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
1588
|
+
const result = { items };
|
|
1589
|
+
if (page + 1 < totalPages) {
|
|
1590
|
+
result.nextCursor = String(page + 1);
|
|
1591
|
+
}
|
|
1592
|
+
return result;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Notify Trendyol that a shipped package was returned to you (manual
|
|
1596
|
+
* return flow, e.g. customer dropped it at your address or you got the
|
|
1597
|
+
* package back without going through Trendyol's return cargo).
|
|
1598
|
+
*
|
|
1599
|
+
* No body; Trendyol responds with 200 + empty body on success.
|
|
1600
|
+
*/
|
|
1601
|
+
async manualReturnByPackageId(packageId) {
|
|
1602
|
+
await this.transport.request({
|
|
1603
|
+
method: "PUT",
|
|
1604
|
+
path: `${this.packagePath(packageId)}/manual-return`,
|
|
1605
|
+
rateLimiter: this.limiter
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Manual-return variant that takes the cargo tracking number instead of
|
|
1610
|
+
* the package ID. Useful when the cargo provider's webhook only carries
|
|
1611
|
+
* the tracking number.
|
|
1612
|
+
*
|
|
1613
|
+
* Sibling path (not under `/{packageId}/...`).
|
|
1614
|
+
*/
|
|
1615
|
+
async manualReturnByTrackingNumber(cargoTrackingNumber) {
|
|
1616
|
+
await this.transport.request({
|
|
1617
|
+
method: "PUT",
|
|
1618
|
+
path: `/integration/order/sellers/${this.transport.sellerId}/shipment-packages/manual-return-by-tracking-number/${encodeURIComponent(String(cargoTrackingNumber))}`,
|
|
1619
|
+
rateLimiter: this.limiter
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Fetch Trendyol Express compensation tickets (claims filed when a
|
|
1624
|
+
* shipment is lost or damaged in transit). Page-based pagination
|
|
1625
|
+
* internally; the SDK exposes the cursor convention.
|
|
1626
|
+
*
|
|
1627
|
+
* Note the different base path — `/integration/tex/compensation/...`,
|
|
1628
|
+
* not the regular `/integration/order/...`.
|
|
1629
|
+
*
|
|
1630
|
+
* @example
|
|
1631
|
+
* ```ts
|
|
1632
|
+
* import { paginate } from '@lonca/core';
|
|
1633
|
+
* const tickets = await client.orders.getCompensationTickets({
|
|
1634
|
+
* startDate: new Date('2026-01-01'),
|
|
1635
|
+
* endDate: new Date('2026-02-01'),
|
|
1636
|
+
* });
|
|
1637
|
+
* for (const t of tickets.items) {
|
|
1638
|
+
* console.log(t.orderNumber, t.currentState, t.stateMessage);
|
|
1639
|
+
* }
|
|
1640
|
+
* ```
|
|
1641
|
+
*/
|
|
1642
|
+
async getCompensationTickets(params = {}) {
|
|
1643
|
+
const size = Math.min(params.limit ?? 200, 200);
|
|
1644
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1645
|
+
const query = { page, size };
|
|
1646
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
1647
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
1648
|
+
const res = await this.transport.request({
|
|
1649
|
+
method: "GET",
|
|
1650
|
+
path: `/integration/tex/compensation/sellers/${this.transport.sellerId}/tickets`,
|
|
1651
|
+
query,
|
|
1652
|
+
rateLimiter: this.limiter
|
|
1653
|
+
});
|
|
1654
|
+
const tickets = Array.isArray(res.data) ? res.data : res.data?.items ?? res.data?.content ?? res.content ?? [];
|
|
1655
|
+
const items = tickets.map((t) => {
|
|
1656
|
+
const itemDetails = (t.itemDetails ?? []).map((d) => {
|
|
1657
|
+
const out2 = {};
|
|
1658
|
+
if (typeof d.itemAmount === "number") out2.itemAmount = d.itemAmount;
|
|
1659
|
+
if (d.itemCode !== void 0) out2.itemCode = d.itemCode;
|
|
1660
|
+
if (typeof d.itemCount === "number") out2.itemCount = d.itemCount;
|
|
1661
|
+
if (d.itemName !== void 0) out2.itemName = d.itemName;
|
|
1662
|
+
return out2;
|
|
1663
|
+
});
|
|
1664
|
+
const out = {
|
|
1665
|
+
itemDetails,
|
|
1666
|
+
raw: t
|
|
1667
|
+
};
|
|
1668
|
+
if (t.cargoProvider !== void 0) out.cargoProvider = t.cargoProvider;
|
|
1669
|
+
if (t.compensateReason !== void 0) out.compensateReason = t.compensateReason;
|
|
1670
|
+
const createdAt = toIso3(t.createDate);
|
|
1671
|
+
if (createdAt) out.createdAt = createdAt;
|
|
1672
|
+
if (t.currentState !== void 0) {
|
|
1673
|
+
out.currentState = t.currentState;
|
|
1674
|
+
}
|
|
1675
|
+
if (t.deliveryNumber !== void 0) out.deliveryNumber = t.deliveryNumber;
|
|
1676
|
+
if (t.orderNumber !== void 0) out.orderNumber = t.orderNumber;
|
|
1677
|
+
if (t.requestedBy !== void 0) out.requestedBy = t.requestedBy;
|
|
1678
|
+
if (t.stateMessage !== void 0) out.stateMessage = t.stateMessage;
|
|
1679
|
+
if (t.totalItemsAmount !== void 0) out.totalItemsAmount = t.totalItemsAmount;
|
|
1680
|
+
return out;
|
|
1681
|
+
});
|
|
1682
|
+
const result = { items };
|
|
1683
|
+
const total = typeof res.totalCount === "number" ? res.totalCount : 0;
|
|
1684
|
+
if ((page + 1) * size < total) {
|
|
1685
|
+
result.nextCursor = String(page + 1);
|
|
1686
|
+
}
|
|
1687
|
+
return result;
|
|
1688
|
+
}
|
|
1689
|
+
packagePath(packageId) {
|
|
1690
|
+
return `/integration/order/sellers/${this.transport.sellerId}/shipment-packages/${encodeURIComponent(String(packageId))}`;
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
var DEFAULT_PAGE_SIZE3 = 50;
|
|
1694
|
+
var MAX_PAGE_SIZE2 = 1e3;
|
|
1695
|
+
var MAX_BUYBOX_BARCODES = 10;
|
|
1696
|
+
function normalizeRef(ref) {
|
|
1697
|
+
return {
|
|
1698
|
+
id: ref?.id !== void 0 ? String(ref.id) : "",
|
|
1699
|
+
name: ref?.name ?? ""
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function normalizeAttribute2(attr) {
|
|
1703
|
+
const out = {
|
|
1704
|
+
attributeId: attr.attributeId !== void 0 ? String(attr.attributeId) : ""
|
|
1705
|
+
};
|
|
1706
|
+
if (attr.attributeName !== void 0) out.attributeName = attr.attributeName;
|
|
1707
|
+
if (attr.attributeValueId !== void 0) {
|
|
1708
|
+
out.attributeValueId = String(attr.attributeValueId);
|
|
1709
|
+
}
|
|
1710
|
+
if (attr.attributeValue !== void 0) out.attributeValue = attr.attributeValue;
|
|
1711
|
+
return out;
|
|
1712
|
+
}
|
|
1713
|
+
function normalizeImages(images) {
|
|
1714
|
+
if (!images) return [];
|
|
1715
|
+
return images.map((img) => typeof img === "string" ? img : img?.url).filter((u) => typeof u === "string" && u.length > 0);
|
|
1716
|
+
}
|
|
1717
|
+
function normalizeStock(stock) {
|
|
1718
|
+
if (typeof stock === "number") return stock;
|
|
1719
|
+
if (stock && typeof stock === "object" && typeof stock.quantity === "number") {
|
|
1720
|
+
return stock.quantity;
|
|
1721
|
+
}
|
|
1722
|
+
return void 0;
|
|
1723
|
+
}
|
|
1724
|
+
function normalizeVariant(variant) {
|
|
1725
|
+
const out = {
|
|
1726
|
+
variantId: variant.variantId !== void 0 ? String(variant.variantId) : "",
|
|
1727
|
+
barcode: variant.barcode ?? "",
|
|
1728
|
+
attributes: (variant.attributes ?? []).map(normalizeAttribute2),
|
|
1729
|
+
raw: variant
|
|
1730
|
+
};
|
|
1731
|
+
if (typeof variant.commission === "number") out.commission = variant.commission;
|
|
1732
|
+
if (variant.productUrl !== void 0) out.productUrl = variant.productUrl;
|
|
1733
|
+
if (typeof variant.onSale === "boolean") out.onSale = variant.onSale;
|
|
1734
|
+
const stock = normalizeStock(variant.stock);
|
|
1735
|
+
if (stock !== void 0) out.stock = stock;
|
|
1736
|
+
return out;
|
|
1737
|
+
}
|
|
1738
|
+
function toIso4(epochMs) {
|
|
1739
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs)) return void 0;
|
|
1740
|
+
return new Date(epochMs).toISOString();
|
|
1741
|
+
}
|
|
1742
|
+
function normalizeProduct(node) {
|
|
1743
|
+
const out = {
|
|
1744
|
+
contentId: node.contentId !== void 0 ? String(node.contentId) : "",
|
|
1745
|
+
productMainId: node.productMainId ?? "",
|
|
1746
|
+
title: node.title ?? "",
|
|
1747
|
+
brand: normalizeRef(node.brand),
|
|
1748
|
+
category: normalizeRef(node.category),
|
|
1749
|
+
images: normalizeImages(node.images),
|
|
1750
|
+
attributes: (node.attributes ?? []).map(normalizeAttribute2),
|
|
1751
|
+
variants: (node.variants ?? []).map(normalizeVariant),
|
|
1752
|
+
createdAt: toIso4(node.creationDate) ?? "",
|
|
1753
|
+
updatedAt: toIso4(node.lastModifiedDate) ?? "",
|
|
1754
|
+
raw: node
|
|
1755
|
+
};
|
|
1756
|
+
if (node.description !== void 0) out.description = node.description;
|
|
1757
|
+
if (node.lastModifiedBy !== void 0) out.lastModifiedBy = node.lastModifiedBy;
|
|
1758
|
+
return out;
|
|
1759
|
+
}
|
|
1760
|
+
function normalizeUnapprovedRejectReason(r) {
|
|
1761
|
+
const out = {};
|
|
1762
|
+
if (r.rejectReason !== void 0) out.rejectReason = r.rejectReason;
|
|
1763
|
+
if (r.rejectReasonDetail !== void 0) out.rejectReasonDetail = r.rejectReasonDetail;
|
|
1764
|
+
return out;
|
|
1765
|
+
}
|
|
1766
|
+
function normalizeUnapproved(node) {
|
|
1767
|
+
const imagesSource = node.images ?? node.media;
|
|
1768
|
+
const out = {
|
|
1769
|
+
productMainId: node.productMainId ?? "",
|
|
1770
|
+
brand: normalizeRef(node.brand),
|
|
1771
|
+
category: normalizeRef(node.category),
|
|
1772
|
+
barcode: node.barcode ?? "",
|
|
1773
|
+
title: node.title ?? "",
|
|
1774
|
+
images: normalizeImages(imagesSource),
|
|
1775
|
+
attributes: (node.attributes ?? []).map(normalizeAttribute2),
|
|
1776
|
+
rejectReasonDetails: (node.rejectReasonDetails ?? []).map(normalizeUnapprovedRejectReason),
|
|
1777
|
+
raw: node
|
|
1778
|
+
};
|
|
1779
|
+
if (node.supplierId !== void 0) out.supplierId = String(node.supplierId);
|
|
1780
|
+
if (node.status !== void 0) out.status = node.status;
|
|
1781
|
+
if (node.description !== void 0) out.description = node.description;
|
|
1782
|
+
if (typeof node.quantity === "number") out.quantity = node.quantity;
|
|
1783
|
+
if (typeof node.listPrice === "number") out.listPrice = node.listPrice;
|
|
1784
|
+
if (typeof node.salePrice === "number") out.salePrice = node.salePrice;
|
|
1785
|
+
if (typeof node.vatRate === "number") out.vatRate = node.vatRate;
|
|
1786
|
+
if (node.dimensionalWeight !== void 0 && node.dimensionalWeight !== null) {
|
|
1787
|
+
out.dimensionalWeight = node.dimensionalWeight;
|
|
1788
|
+
}
|
|
1789
|
+
if (node.stockCode !== void 0) out.stockCode = node.stockCode;
|
|
1790
|
+
if (node.origin !== void 0) out.origin = node.origin;
|
|
1791
|
+
if (node.locationBasedDelivery !== void 0) {
|
|
1792
|
+
out.locationBasedDelivery = node.locationBasedDelivery;
|
|
1793
|
+
}
|
|
1794
|
+
if (node.lotNumber !== void 0) out.lotNumber = node.lotNumber;
|
|
1795
|
+
if (node.specialConsumptionTax !== void 0) {
|
|
1796
|
+
out.specialConsumptionTax = node.specialConsumptionTax;
|
|
1797
|
+
}
|
|
1798
|
+
if (node.sgrPrice !== void 0) out.sgrPrice = node.sgrPrice;
|
|
1799
|
+
const createdAt = toIso4(node.createDateTime);
|
|
1800
|
+
if (createdAt) out.createdAt = createdAt;
|
|
1801
|
+
const updatedAt = toIso4(node.lastUpdateDate);
|
|
1802
|
+
if (updatedAt) out.updatedAt = updatedAt;
|
|
1803
|
+
const priceChanged = toIso4(node.lastPriceChangeDate);
|
|
1804
|
+
if (priceChanged) out.lastPriceChangedAt = priceChanged;
|
|
1805
|
+
const stockChanged = toIso4(node.lastStockChangeDate);
|
|
1806
|
+
if (stockChanged) out.lastStockChangedAt = stockChanged;
|
|
1807
|
+
return out;
|
|
1808
|
+
}
|
|
1809
|
+
function normalizeBuyboxInfo(node) {
|
|
1810
|
+
const out = {
|
|
1811
|
+
barcode: node.barcode ?? "",
|
|
1812
|
+
raw: node
|
|
1813
|
+
};
|
|
1814
|
+
if (typeof node.buyboxOrder === "number") out.buyboxOrder = node.buyboxOrder;
|
|
1815
|
+
if (typeof node.buyboxPrice === "number") out.buyboxPrice = node.buyboxPrice;
|
|
1816
|
+
if (typeof node.hasMultipleSeller === "boolean") {
|
|
1817
|
+
out.hasMultipleSeller = node.hasMultipleSeller;
|
|
1818
|
+
}
|
|
1819
|
+
if (node.secondBuyboxPrice !== void 0) out.secondBuyboxPrice = node.secondBuyboxPrice;
|
|
1820
|
+
if (node.thirdBuyboxPrice !== void 0) out.thirdBuyboxPrice = node.thirdBuyboxPrice;
|
|
1821
|
+
return out;
|
|
1822
|
+
}
|
|
1823
|
+
function normalizeProductBase(data) {
|
|
1824
|
+
const out = {
|
|
1825
|
+
barcode: data.barcode ?? "",
|
|
1826
|
+
approved: !!data.approved,
|
|
1827
|
+
archived: !!data.archived,
|
|
1828
|
+
raw: data
|
|
1829
|
+
};
|
|
1830
|
+
const approvedAt = toIso4(data.approvedDate);
|
|
1831
|
+
if (approvedAt) out.approvedAt = approvedAt;
|
|
1832
|
+
if (data.listingId !== void 0) out.listingId = data.listingId;
|
|
1833
|
+
if (data.contentId !== void 0) out.contentId = String(data.contentId);
|
|
1834
|
+
return out;
|
|
1835
|
+
}
|
|
1836
|
+
function normalizeBatchResult(data) {
|
|
1837
|
+
const items = (data.items ?? []).map((item) => {
|
|
1838
|
+
const result = {};
|
|
1839
|
+
if (item.requestItem !== void 0) result.requestItem = item.requestItem;
|
|
1840
|
+
if (item.status !== void 0) result.status = item.status;
|
|
1841
|
+
if (item.failureReasons !== void 0) result.failureReasons = item.failureReasons;
|
|
1842
|
+
return result;
|
|
1843
|
+
});
|
|
1844
|
+
const out = {
|
|
1845
|
+
batchRequestId: typeof data.batchRequestId === "string" ? data.batchRequestId : "",
|
|
1846
|
+
status: data.status ?? "PROCESSING",
|
|
1847
|
+
items,
|
|
1848
|
+
raw: data
|
|
1849
|
+
};
|
|
1850
|
+
if (typeof data.itemCount === "number") out.itemCount = data.itemCount;
|
|
1851
|
+
if (typeof data.failedItemCount === "number") out.failedItemCount = data.failedItemCount;
|
|
1852
|
+
const createdAt = toIso4(data.creationDate);
|
|
1853
|
+
if (createdAt) out.createdAt = createdAt;
|
|
1854
|
+
const lastModifiedAt = toIso4(data.lastModification);
|
|
1855
|
+
if (lastModifiedAt) out.lastModifiedAt = lastModifiedAt;
|
|
1856
|
+
if (data.sourceType !== void 0) out.sourceType = data.sourceType;
|
|
1857
|
+
if (data.batchRequestType !== void 0) out.batchRequestType = data.batchRequestType;
|
|
1858
|
+
if (data.notes !== void 0) out.notes = data.notes;
|
|
1859
|
+
if (data.objectKey !== void 0) out.objectKey = data.objectKey;
|
|
1860
|
+
if (data.storeFrontCode !== void 0) out.storeFrontCode = data.storeFrontCode;
|
|
1861
|
+
return out;
|
|
1862
|
+
}
|
|
1863
|
+
var MAX_WRITE_ITEMS = 1e3;
|
|
1864
|
+
var DELETE_RATE_PER_MIN = 100;
|
|
1865
|
+
var ProductsResource = class {
|
|
1866
|
+
constructor(transport, options = {}) {
|
|
1867
|
+
this.transport = transport;
|
|
1868
|
+
this.filterLimiter = options.filterLimiter ?? new core.TokenBucketRateLimiter({ capacity: 2e3, intervalMs: 6e4 });
|
|
1869
|
+
this.batchLimiter = options.batchLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
1870
|
+
this.buyboxLimiter = options.buyboxLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
1871
|
+
this.writeLimiter = options.writeLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
1872
|
+
this.deleteLimiter = options.deleteLimiter ?? new core.TokenBucketRateLimiter({ capacity: DELETE_RATE_PER_MIN, intervalMs: 6e4 });
|
|
1873
|
+
}
|
|
1874
|
+
transport;
|
|
1875
|
+
filterLimiter;
|
|
1876
|
+
batchLimiter;
|
|
1877
|
+
buyboxLimiter;
|
|
1878
|
+
writeLimiter;
|
|
1879
|
+
deleteLimiter;
|
|
1880
|
+
validateBarcodes(barcodes, methodLabel) {
|
|
1881
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
1882
|
+
throw new core.ValidationError({ message: `${methodLabel}: barcodes must not be empty` });
|
|
1883
|
+
}
|
|
1884
|
+
if (barcodes.length > MAX_WRITE_ITEMS) {
|
|
1885
|
+
throw new core.ValidationError({
|
|
1886
|
+
message: `${methodLabel}: max ${MAX_WRITE_ITEMS} barcodes per call (got ${barcodes.length})`
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
async submitWrite(endpoint, items, methodLabel) {
|
|
1891
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
1892
|
+
throw new core.ValidationError({ message: `${methodLabel}: items must not be empty` });
|
|
1893
|
+
}
|
|
1894
|
+
if (items.length > MAX_WRITE_ITEMS) {
|
|
1895
|
+
throw new core.ValidationError({
|
|
1896
|
+
message: `${methodLabel}: max ${MAX_WRITE_ITEMS} items per call (got ${items.length})`
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
const data = await this.transport.request({
|
|
1900
|
+
method: "POST",
|
|
1901
|
+
path: `/integration/product/sellers/${this.transport.sellerId}${endpoint}`,
|
|
1902
|
+
body: { items },
|
|
1903
|
+
rateLimiter: this.writeLimiter
|
|
1904
|
+
});
|
|
1905
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* List approved products. Use `paginate()` from `@lonca/core` to iterate
|
|
1909
|
+
* lazily across pages.
|
|
1910
|
+
*
|
|
1911
|
+
* Trendyol exposes both page-based and `nextPageToken`-based pagination
|
|
1912
|
+
* (the latter required when the dataset exceeds 10,000 items). The SDK
|
|
1913
|
+
* picks the right strategy automatically — pass our opaque `cursor` from
|
|
1914
|
+
* the previous response and we forward it as `nextPageToken`.
|
|
1915
|
+
*
|
|
1916
|
+
* @example
|
|
1917
|
+
* ```ts
|
|
1918
|
+
* import { paginate } from '@lonca/core';
|
|
1919
|
+
* for await (const product of paginate((p) => client.products.list(p))) {
|
|
1920
|
+
* for (const variant of product.variants) {
|
|
1921
|
+
* console.log(variant.barcode, product.title);
|
|
1922
|
+
* }
|
|
1923
|
+
* }
|
|
1924
|
+
* ```
|
|
1925
|
+
*/
|
|
1926
|
+
async list(params = {}) {
|
|
1927
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE3, MAX_PAGE_SIZE2);
|
|
1928
|
+
const query = { size };
|
|
1929
|
+
if (params.cursor) {
|
|
1930
|
+
query.nextPageToken = params.cursor;
|
|
1931
|
+
} else {
|
|
1932
|
+
query.page = 0;
|
|
1933
|
+
}
|
|
1934
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
1935
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
1936
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
1937
|
+
const data = await this.transport.request({
|
|
1938
|
+
method: "GET",
|
|
1939
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/approved`,
|
|
1940
|
+
query,
|
|
1941
|
+
rateLimiter: this.filterLimiter
|
|
1942
|
+
});
|
|
1943
|
+
const items = (data.content ?? []).map(normalizeProduct);
|
|
1944
|
+
const page = { items };
|
|
1945
|
+
if (data.nextPageToken) {
|
|
1946
|
+
page.nextCursor = data.nextPageToken;
|
|
1947
|
+
}
|
|
1948
|
+
return page;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Poll a batch request returned by an async write (e.g. `createProducts`,
|
|
1952
|
+
* `updatePriceAndInventory`).
|
|
1953
|
+
*
|
|
1954
|
+
* Trendyol retains batch results for **4 hours** after the originating
|
|
1955
|
+
* request — poll within that window.
|
|
1956
|
+
*
|
|
1957
|
+
* @param batchRequestId The opaque ID returned by the originating call.
|
|
1958
|
+
*/
|
|
1959
|
+
async getBatchStatus(batchRequestId) {
|
|
1960
|
+
const id = encodeURIComponent(batchRequestId);
|
|
1961
|
+
const data = await this.transport.request({
|
|
1962
|
+
method: "GET",
|
|
1963
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/batch-requests/${id}`,
|
|
1964
|
+
rateLimiter: this.batchLimiter
|
|
1965
|
+
});
|
|
1966
|
+
return normalizeBatchResult(data);
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* List **unapproved** (draft / rejected / pending-review) products.
|
|
1970
|
+
*
|
|
1971
|
+
* Wire shape is intentionally flatter than the approved-product shape:
|
|
1972
|
+
* each barcode is one top-level item with `barcode`, `quantity`, `salePrice`
|
|
1973
|
+
* etc. at the root. Rejected drafts carry `rejectReasonDetails` so you can
|
|
1974
|
+
* surface why Trendyol's content team turned them down.
|
|
1975
|
+
*
|
|
1976
|
+
* Pagination follows the same convention as `list()`: `cursor` from the
|
|
1977
|
+
* previous response forwards as `nextPageToken`.
|
|
1978
|
+
*
|
|
1979
|
+
* @example
|
|
1980
|
+
* ```ts
|
|
1981
|
+
* const page = await client.products.listUnapproved({ limit: 50 });
|
|
1982
|
+
* for (const draft of page.items) {
|
|
1983
|
+
* if (draft.status === 'rejected') {
|
|
1984
|
+
* console.warn(draft.barcode, draft.rejectReasonDetails);
|
|
1985
|
+
* }
|
|
1986
|
+
* }
|
|
1987
|
+
* ```
|
|
1988
|
+
*/
|
|
1989
|
+
async listUnapproved(params = {}) {
|
|
1990
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE3, MAX_PAGE_SIZE2);
|
|
1991
|
+
const query = { size };
|
|
1992
|
+
if (params.cursor) {
|
|
1993
|
+
query.nextPageToken = params.cursor;
|
|
1994
|
+
} else {
|
|
1995
|
+
query.page = 0;
|
|
1996
|
+
}
|
|
1997
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
1998
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
1999
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
2000
|
+
if (params.dateQueryType) query.dateQueryType = params.dateQueryType;
|
|
2001
|
+
if (params.supplierId !== void 0) query.supplierId = params.supplierId;
|
|
2002
|
+
const data = await this.transport.request({
|
|
2003
|
+
method: "GET",
|
|
2004
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/unapproved`,
|
|
2005
|
+
query,
|
|
2006
|
+
rateLimiter: this.filterLimiter
|
|
2007
|
+
});
|
|
2008
|
+
const items = (data.content ?? []).map(normalizeUnapproved);
|
|
2009
|
+
const page = { items };
|
|
2010
|
+
if (data.nextPageToken) {
|
|
2011
|
+
page.nextCursor = data.nextPageToken;
|
|
2012
|
+
}
|
|
2013
|
+
return page;
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Fetch the basic lifecycle status of a single product by barcode.
|
|
2017
|
+
*
|
|
2018
|
+
* Cheap and useful as a polling primitive after `createProducts`: poll
|
|
2019
|
+
* this endpoint until `approved` flips to `true` (or use
|
|
2020
|
+
* `client.products.getBatchStatus()` to track the originating batch).
|
|
2021
|
+
*
|
|
2022
|
+
* @param barcode The product barcode to look up.
|
|
2023
|
+
*/
|
|
2024
|
+
async getBase(barcode) {
|
|
2025
|
+
const code = encodeURIComponent(barcode);
|
|
2026
|
+
const data = await this.transport.request({
|
|
2027
|
+
method: "GET",
|
|
2028
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/product/${code}`,
|
|
2029
|
+
rateLimiter: this.filterLimiter
|
|
2030
|
+
});
|
|
2031
|
+
return normalizeProductBase(data);
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Fetch buybox information for up to 10 barcodes in one call.
|
|
2035
|
+
*
|
|
2036
|
+
* Returns rank (`buyboxOrder === 1` means you hold the buybox), the
|
|
2037
|
+
* current buybox price, and — beyond the spec — the second and third
|
|
2038
|
+
* competing prices when other sellers are present.
|
|
2039
|
+
*
|
|
2040
|
+
* @param barcodes 1–10 product barcodes.
|
|
2041
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 10.
|
|
2042
|
+
*/
|
|
2043
|
+
async getBuyboxInfo(barcodes) {
|
|
2044
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
2045
|
+
throw new core.ValidationError({
|
|
2046
|
+
message: "getBuyboxInfo: barcodes must be a non-empty array"
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
if (barcodes.length > MAX_BUYBOX_BARCODES) {
|
|
2050
|
+
throw new core.ValidationError({
|
|
2051
|
+
message: `getBuyboxInfo: max ${MAX_BUYBOX_BARCODES} barcodes per call (got ${barcodes.length})`
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
const data = await this.transport.request({
|
|
2055
|
+
method: "POST",
|
|
2056
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/buybox-information`,
|
|
2057
|
+
body: { barcodes },
|
|
2058
|
+
rateLimiter: this.buyboxLimiter
|
|
2059
|
+
});
|
|
2060
|
+
const list = data.buyboxInfo ?? data.content ?? [];
|
|
2061
|
+
return list.map(normalizeBuyboxInfo);
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Create products (V2). Async batch — returns a `batchRequestId` you can
|
|
2065
|
+
* poll with `getBatchStatus`. Max 1000 items per call.
|
|
2066
|
+
*
|
|
2067
|
+
* Trendyol requires the full V2 attribute payload — fetch via
|
|
2068
|
+
* `categories.getAttributes` (and `categories.getAttributeValues` for
|
|
2069
|
+
* values when `allowCustom === false`). Shipment / returning warehouse
|
|
2070
|
+
* IDs come from `suppliers.getAddresses`.
|
|
2071
|
+
*
|
|
2072
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
2073
|
+
*/
|
|
2074
|
+
async create(items) {
|
|
2075
|
+
return this.submitWrite("/v2/products", items, "createProducts");
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Update **content** of approved products (title, description, images,
|
|
2079
|
+
* attributes). Identified by `contentId`. Partial update is supported
|
|
2080
|
+
* except for attributes — if you update ANY attribute, send ALL of them.
|
|
2081
|
+
*
|
|
2082
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
2083
|
+
*/
|
|
2084
|
+
async updateContent(items) {
|
|
2085
|
+
return this.submitWrite("/products/content-bulk-update", items, "updateContent");
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Update **variant** fields of approved products (stockCode, vatRate,
|
|
2089
|
+
* dimensionalWeight, warehouse IDs, location-based delivery, lot). Identified
|
|
2090
|
+
* by `barcode`. The barcode itself cannot be changed via this endpoint.
|
|
2091
|
+
*
|
|
2092
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
2093
|
+
*/
|
|
2094
|
+
async updateVariants(items) {
|
|
2095
|
+
return this.submitWrite("/products/variant-bulk-update", items, "updateVariants");
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Update **unapproved** (draft) products. Identified by `barcode`. All
|
|
2099
|
+
* other fields are optional partial updates. Use this to fix drafts that
|
|
2100
|
+
* Trendyol rejected — `client.products.listUnapproved` surfaces the
|
|
2101
|
+
* `rejectReasonDetails` you need to act on.
|
|
2102
|
+
*
|
|
2103
|
+
* **Gotcha (verified live STAGE 2026-05-25):** Trendyol's V2 spec claims
|
|
2104
|
+
* only `barcode` is required, but the endpoint returns HTTP 500
|
|
2105
|
+
* (`TrendyolSystemException` / `TypeError`) when too many optional fields
|
|
2106
|
+
* are omitted. In practice, send at least `title`, `description`,
|
|
2107
|
+
* `productMainId`, `brandId`, `categoryId`, `stockCode`,
|
|
2108
|
+
* `dimensionalWeight`, `vatRate`, `images[]`, and `attributes[]` (an
|
|
2109
|
+
* empty array is OK for the latter). The SDK forwards your payload
|
|
2110
|
+
* as-is; trim fields only if you have verified the server accepts it.
|
|
2111
|
+
*
|
|
2112
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
2113
|
+
*/
|
|
2114
|
+
async updateUnapproved(items) {
|
|
2115
|
+
return this.submitWrite("/products/unapproved-bulk-update", items, "updateUnapproved");
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Update product **delivery information** (deliveryDuration,
|
|
2119
|
+
* fastDeliveryType). Identified by `barcode`.
|
|
2120
|
+
*
|
|
2121
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
2122
|
+
*/
|
|
2123
|
+
async updateDeliveryInfo(items) {
|
|
2124
|
+
return this.submitWrite("/products/delivery-info-bulk-update", items, "updateDeliveryInfo");
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Delete products by barcode. Trendyol allows deletion of unapproved
|
|
2128
|
+
* products and approved products that have been archived for more than a
|
|
2129
|
+
* day (and have not been sales-stopped by Trendyol).
|
|
2130
|
+
*
|
|
2131
|
+
* Async batch — returns `{ batchRequestId }` to poll via `getBatchStatus`.
|
|
2132
|
+
* Separately rate-limited at **100 req/min** (much tighter than create/update).
|
|
2133
|
+
*
|
|
2134
|
+
* @param barcodes 1–1000 barcodes.
|
|
2135
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
2136
|
+
*/
|
|
2137
|
+
async delete(barcodes) {
|
|
2138
|
+
this.validateBarcodes(barcodes, "delete");
|
|
2139
|
+
const data = await this.transport.request({
|
|
2140
|
+
method: "DELETE",
|
|
2141
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products`,
|
|
2142
|
+
body: { items: barcodes.map((barcode) => ({ barcode })) },
|
|
2143
|
+
rateLimiter: this.deleteLimiter
|
|
2144
|
+
});
|
|
2145
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Archive products by barcode (Trendyol's `archived=true` state).
|
|
2149
|
+
* Archived products are not visible to customers; pair with `delete`
|
|
2150
|
+
* after the 24-hour archive cool-down to remove them entirely.
|
|
2151
|
+
*
|
|
2152
|
+
* Async batch — returns `{ batchRequestId }`.
|
|
2153
|
+
*
|
|
2154
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
2155
|
+
*/
|
|
2156
|
+
async archive(barcodes) {
|
|
2157
|
+
return this.setArchivedState(barcodes, true, "archive");
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Unarchive products by barcode (Trendyol's `archived=false` state).
|
|
2161
|
+
* Restores visibility for previously-archived products.
|
|
2162
|
+
*
|
|
2163
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
2164
|
+
*/
|
|
2165
|
+
async unarchive(barcodes) {
|
|
2166
|
+
return this.setArchivedState(barcodes, false, "unarchive");
|
|
2167
|
+
}
|
|
2168
|
+
async setArchivedState(barcodes, archived, methodLabel) {
|
|
2169
|
+
this.validateBarcodes(barcodes, methodLabel);
|
|
2170
|
+
const data = await this.transport.request({
|
|
2171
|
+
method: "PUT",
|
|
2172
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/archive-state`,
|
|
2173
|
+
body: { items: barcodes.map((barcode) => ({ barcode, archived })) },
|
|
2174
|
+
rateLimiter: this.writeLimiter
|
|
2175
|
+
});
|
|
2176
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Unlock products whose sale was paused by Trendyol due to pricing
|
|
2180
|
+
* issues (under/over-pricing, critical price error, supplier issues).
|
|
2181
|
+
* Restores selling status for the listed barcodes.
|
|
2182
|
+
*
|
|
2183
|
+
* Async batch — returns `{ batchRequestId }`.
|
|
2184
|
+
*
|
|
2185
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
2186
|
+
*/
|
|
2187
|
+
async unlock(barcodes) {
|
|
2188
|
+
this.validateBarcodes(barcodes, "unlock");
|
|
2189
|
+
const data = await this.transport.request({
|
|
2190
|
+
method: "PUT",
|
|
2191
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/unlock`,
|
|
2192
|
+
body: { items: barcodes.map((barcode) => ({ barcode })) },
|
|
2193
|
+
rateLimiter: this.writeLimiter
|
|
2194
|
+
});
|
|
2195
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
function toIso5(epochMs) {
|
|
2199
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
2200
|
+
return void 0;
|
|
2201
|
+
}
|
|
2202
|
+
return new Date(epochMs).toISOString();
|
|
2203
|
+
}
|
|
2204
|
+
function normalizeAnswer(a) {
|
|
2205
|
+
if (!a) return void 0;
|
|
2206
|
+
const out = {};
|
|
2207
|
+
if (a.text !== void 0) out.text = a.text;
|
|
2208
|
+
const createdAt = toIso5(a.creationDate);
|
|
2209
|
+
if (createdAt) out.createdAt = createdAt;
|
|
2210
|
+
if (a.status !== void 0) out.status = a.status;
|
|
2211
|
+
return out;
|
|
2212
|
+
}
|
|
2213
|
+
function normalizeQuestion(node) {
|
|
2214
|
+
const out = {
|
|
2215
|
+
id: node.id !== void 0 ? String(node.id) : "",
|
|
2216
|
+
raw: node
|
|
2217
|
+
};
|
|
2218
|
+
if (node.text !== void 0) out.text = node.text;
|
|
2219
|
+
if (node.customerId !== void 0) out.customerId = String(node.customerId);
|
|
2220
|
+
if (node.userName !== void 0) out.userName = node.userName;
|
|
2221
|
+
if (typeof node.showUserName === "boolean") out.showUserName = node.showUserName;
|
|
2222
|
+
if (node.status !== void 0) out.status = node.status;
|
|
2223
|
+
if (typeof node.public === "boolean") out.public = node.public;
|
|
2224
|
+
if (node.productMainId !== void 0) out.productMainId = node.productMainId;
|
|
2225
|
+
if (node.productName !== void 0) out.productName = node.productName;
|
|
2226
|
+
if (node.imageUrl !== void 0) out.imageUrl = node.imageUrl;
|
|
2227
|
+
if (node.webUrl !== void 0) out.webUrl = node.webUrl;
|
|
2228
|
+
const createdAt = toIso5(node.creationDate);
|
|
2229
|
+
if (createdAt) out.createdAt = createdAt;
|
|
2230
|
+
if (node.answeredDateMessage !== void 0) out.answeredDateMessage = node.answeredDateMessage;
|
|
2231
|
+
const answer = normalizeAnswer(node.answer);
|
|
2232
|
+
if (answer) out.answer = answer;
|
|
2233
|
+
const rejectedAnswer = normalizeAnswer(node.rejectedAnswer);
|
|
2234
|
+
if (rejectedAnswer) out.rejectedAnswer = rejectedAnswer;
|
|
2235
|
+
const rejectedAt = toIso5(node.rejectedDate);
|
|
2236
|
+
if (rejectedAt) out.rejectedAt = rejectedAt;
|
|
2237
|
+
if (node.reason !== void 0) out.reason = node.reason;
|
|
2238
|
+
if (node.reportReason !== void 0) out.reportReason = node.reportReason;
|
|
2239
|
+
const reportedAt = toIso5(node.reportedDate);
|
|
2240
|
+
if (reportedAt) out.reportedAt = reportedAt;
|
|
2241
|
+
return out;
|
|
2242
|
+
}
|
|
2243
|
+
var QuestionsResource = class {
|
|
2244
|
+
constructor(transport, limiter) {
|
|
2245
|
+
this.transport = transport;
|
|
2246
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 500, intervalMs: 6e4 });
|
|
2247
|
+
}
|
|
2248
|
+
transport;
|
|
2249
|
+
limiter;
|
|
2250
|
+
/** Fetch a single question by its numeric ID. */
|
|
2251
|
+
async get(questionId) {
|
|
2252
|
+
const data = await this.transport.request({
|
|
2253
|
+
method: "GET",
|
|
2254
|
+
path: `/integration/qna/sellers/${this.transport.sellerId}/questions/${encodeURIComponent(String(questionId))}`,
|
|
2255
|
+
rateLimiter: this.limiter
|
|
2256
|
+
});
|
|
2257
|
+
return normalizeQuestion(data);
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Filter questions by barcode / date range / status. Page-based
|
|
2261
|
+
* pagination internally; SDK exposes the opaque-cursor convention.
|
|
2262
|
+
*/
|
|
2263
|
+
async list(params = {}) {
|
|
2264
|
+
const size = Math.min(params.limit ?? 50, 200);
|
|
2265
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
2266
|
+
const query = { page, size };
|
|
2267
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
2268
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
2269
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
2270
|
+
if (params.status) query.status = params.status;
|
|
2271
|
+
const data = await this.transport.request({
|
|
2272
|
+
method: "GET",
|
|
2273
|
+
path: `/integration/qna/sellers/${this.transport.sellerId}/questions/filter`,
|
|
2274
|
+
query,
|
|
2275
|
+
rateLimiter: this.limiter
|
|
2276
|
+
});
|
|
2277
|
+
const items = (data.content ?? []).map(normalizeQuestion);
|
|
2278
|
+
const result = { items };
|
|
2279
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
2280
|
+
if (page + 1 < totalPages) {
|
|
2281
|
+
result.nextCursor = String(page + 1);
|
|
2282
|
+
}
|
|
2283
|
+
return result;
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Reply to a question. Trendyol enforces 10–2000 characters on the
|
|
2287
|
+
* answer text; the SDK pre-validates client-side.
|
|
2288
|
+
*
|
|
2289
|
+
* @throws {ValidationError} when `text` is outside the 10–2000 char range.
|
|
2290
|
+
*/
|
|
2291
|
+
async answer(questionId, text) {
|
|
2292
|
+
if (typeof text !== "string" || text.length < 10) {
|
|
2293
|
+
throw new core.ValidationError({
|
|
2294
|
+
message: `questions.answer: text must be at least 10 chars (got ${text?.length ?? 0})`
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
if (text.length > 2e3) {
|
|
2298
|
+
throw new core.ValidationError({
|
|
2299
|
+
message: `questions.answer: text must be at most 2000 chars (got ${text.length})`
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
return this.transport.request({
|
|
2303
|
+
method: "POST",
|
|
2304
|
+
path: `/integration/qna/sellers/${this.transport.sellerId}/questions/${encodeURIComponent(String(questionId))}/answers`,
|
|
2305
|
+
body: { text },
|
|
2306
|
+
rateLimiter: this.limiter
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
2311
|
+
"SHIPMENT",
|
|
2312
|
+
"RETURNING",
|
|
2313
|
+
"INVOICE",
|
|
2314
|
+
"WAREHOUSE"
|
|
2315
|
+
]);
|
|
2316
|
+
function normalizeAddressType(raw) {
|
|
2317
|
+
if (raw && VALID_TYPES.has(raw)) {
|
|
2318
|
+
return raw;
|
|
2319
|
+
}
|
|
2320
|
+
const stripped = raw?.replace(/_ADDRESS$/, "");
|
|
2321
|
+
if (stripped && VALID_TYPES.has(stripped)) {
|
|
2322
|
+
return stripped;
|
|
2323
|
+
}
|
|
2324
|
+
return "SHIPMENT";
|
|
2325
|
+
}
|
|
2326
|
+
function normalizeAddress2(node) {
|
|
2327
|
+
return {
|
|
2328
|
+
id: String(node.id),
|
|
2329
|
+
name: node.name,
|
|
2330
|
+
addressType: normalizeAddressType(node.addressType),
|
|
2331
|
+
isShipmentAddress: node.isShipmentAddress ?? false,
|
|
2332
|
+
isReturningAddress: node.isReturningAddress ?? false,
|
|
2333
|
+
isInvoiceAddress: node.isInvoiceAddress ?? false,
|
|
2334
|
+
isDefault: node.isDefault ?? false,
|
|
2335
|
+
address: node.address,
|
|
2336
|
+
city: node.city,
|
|
2337
|
+
district: node.district,
|
|
2338
|
+
postCode: node.postCode,
|
|
2339
|
+
fullName: node.fullName
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
2343
|
+
var SuppliersResource = class {
|
|
2344
|
+
constructor(transport, options = {}) {
|
|
2345
|
+
this.transport = transport;
|
|
2346
|
+
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
2347
|
+
this.limiter = new core.TokenBucketRateLimiter({
|
|
2348
|
+
capacity: 1,
|
|
2349
|
+
intervalMs: DEFAULT_CACHE_TTL_MS
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
transport;
|
|
2353
|
+
cache = null;
|
|
2354
|
+
cacheTtlMs;
|
|
2355
|
+
limiter;
|
|
2356
|
+
inflight = null;
|
|
2357
|
+
/**
|
|
2358
|
+
* List the seller's registered addresses (shipment, returning, invoice, warehouse).
|
|
2359
|
+
*
|
|
2360
|
+
* Returns the cached value if it is still fresh. Concurrent calls share a
|
|
2361
|
+
* single in-flight request.
|
|
2362
|
+
*/
|
|
2363
|
+
async getAddresses(options = {}) {
|
|
2364
|
+
if (!options.forceRefresh && this.cache && this.cache.expiresAt > Date.now()) {
|
|
2365
|
+
return this.cache.value;
|
|
2366
|
+
}
|
|
2367
|
+
if (this.inflight) return this.inflight;
|
|
2368
|
+
this.inflight = this.fetchFresh().finally(() => {
|
|
2369
|
+
this.inflight = null;
|
|
2370
|
+
});
|
|
2371
|
+
return this.inflight;
|
|
2372
|
+
}
|
|
2373
|
+
/** Drop the cache; the next `getAddresses()` call hits the API. */
|
|
2374
|
+
invalidateCache() {
|
|
2375
|
+
this.cache = null;
|
|
2376
|
+
}
|
|
2377
|
+
async fetchFresh() {
|
|
2378
|
+
const data = await this.transport.request({
|
|
2379
|
+
method: "GET",
|
|
2380
|
+
path: `/integration/sellers/${this.transport.sellerId}/addresses`,
|
|
2381
|
+
rateLimiter: this.limiter
|
|
2382
|
+
});
|
|
2383
|
+
const items = data.supplierAddresses.map(normalizeAddress2);
|
|
2384
|
+
this.cache = { value: items, expiresAt: Date.now() + this.cacheTtlMs };
|
|
2385
|
+
return items;
|
|
2386
|
+
}
|
|
2387
|
+
};
|
|
2388
|
+
var TestOrdersResource = class {
|
|
2389
|
+
constructor(transport, limiter) {
|
|
2390
|
+
this.transport = transport;
|
|
2391
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 60, intervalMs: 6e4 });
|
|
2392
|
+
}
|
|
2393
|
+
transport;
|
|
2394
|
+
limiter;
|
|
2395
|
+
/**
|
|
2396
|
+
* Create a test order with the given customer / addresses / lines. The
|
|
2397
|
+
* SDK forwards the typed payload verbatim — drill into Trendyol's
|
|
2398
|
+
* `createTestOrder` doc for inner field rules.
|
|
2399
|
+
*
|
|
2400
|
+
* @throws {ValidationError} when required top-level fields are missing.
|
|
2401
|
+
*/
|
|
2402
|
+
async create(input) {
|
|
2403
|
+
for (const k of ["customer", "invoiceAddress", "shippingAddress", "seller", "lines"]) {
|
|
2404
|
+
if (!input?.[k]) {
|
|
2405
|
+
throw new core.ValidationError({ message: `testOrders.create: ${k} is required` });
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
return this.transport.request({
|
|
2409
|
+
method: "POST",
|
|
2410
|
+
path: `/integration/test/order/orders/core`,
|
|
2411
|
+
body: input,
|
|
2412
|
+
rateLimiter: this.limiter
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
/** Push a test shipment package to the given status. */
|
|
2416
|
+
async updateStatus(packageId, status) {
|
|
2417
|
+
return this.transport.request({
|
|
2418
|
+
method: "PUT",
|
|
2419
|
+
path: `/integration/test/order/sellers/${this.transport.sellerId}/shipment-packages/${encodeURIComponent(String(packageId))}/status`,
|
|
2420
|
+
body: { status },
|
|
2421
|
+
rateLimiter: this.limiter
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
/** Move test claims to the `WaitingInAction` state. */
|
|
2425
|
+
async setClaimsWaitingInAction() {
|
|
2426
|
+
return this.transport.request({
|
|
2427
|
+
method: "PUT",
|
|
2428
|
+
path: `/integration/test/order/sellers/${this.transport.sellerId}/claims/waiting-in-action`,
|
|
2429
|
+
rateLimiter: this.limiter
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
var VideosResource = class {
|
|
2434
|
+
constructor(transport, options = {}) {
|
|
2435
|
+
this.transport = transport;
|
|
2436
|
+
this.createLimiter = options.createLimiter ?? new core.TokenBucketRateLimiter({ capacity: 200, intervalMs: 6e4 });
|
|
2437
|
+
this.listLimiter = options.listLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
2438
|
+
}
|
|
2439
|
+
transport;
|
|
2440
|
+
createLimiter;
|
|
2441
|
+
listLimiter;
|
|
2442
|
+
/**
|
|
2443
|
+
* Queue a video for upload. Trendyol downloads from the URL in the
|
|
2444
|
+
* body asynchronously; poll `list()` (filtered by id) for status.
|
|
2445
|
+
*
|
|
2446
|
+
* @throws {ValidationError} when `input` is empty / not an object.
|
|
2447
|
+
*/
|
|
2448
|
+
async create(input) {
|
|
2449
|
+
if (!input || typeof input !== "object") {
|
|
2450
|
+
throw new core.ValidationError({ message: "videos.create: input is required" });
|
|
2451
|
+
}
|
|
2452
|
+
return this.transport.request({
|
|
2453
|
+
method: "POST",
|
|
2454
|
+
path: `/integration/video/sellers/${this.transport.sellerId}/videos`,
|
|
2455
|
+
body: input,
|
|
2456
|
+
rateLimiter: this.createLimiter
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
/** List the seller's integration videos (optionally filtered by id / status). */
|
|
2460
|
+
async list(params = {}) {
|
|
2461
|
+
const data = await this.transport.request({
|
|
2462
|
+
method: "GET",
|
|
2463
|
+
path: `/integration/video/sellers/${this.transport.sellerId}/videos`,
|
|
2464
|
+
query: {
|
|
2465
|
+
id: params.id,
|
|
2466
|
+
sellerIntegrationStatus: params.sellerIntegrationStatus,
|
|
2467
|
+
page: params.offset,
|
|
2468
|
+
size: params.limit
|
|
2469
|
+
},
|
|
2470
|
+
rateLimiter: this.listLimiter
|
|
2471
|
+
});
|
|
2472
|
+
const rows = Array.isArray(data) ? data : Array.isArray(data?.content) ? data.content : Array.isArray(data?.items) ? data.items : [];
|
|
2473
|
+
return rows.map((r) => {
|
|
2474
|
+
const row = r ?? {};
|
|
2475
|
+
const out = { raw: row };
|
|
2476
|
+
if (typeof row.id === "string") out.id = row.id;
|
|
2477
|
+
if (typeof row.status === "string") out.status = row.status;
|
|
2478
|
+
return out;
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
};
|
|
2482
|
+
function normalizeWebhook(node) {
|
|
2483
|
+
const out = {
|
|
2484
|
+
id: node.id !== void 0 ? String(node.id) : "",
|
|
2485
|
+
raw: node
|
|
2486
|
+
};
|
|
2487
|
+
if (node.url !== void 0) out.url = node.url;
|
|
2488
|
+
if (node.authenticationType !== void 0) out.authenticationType = node.authenticationType;
|
|
2489
|
+
if (node.username !== void 0) out.username = node.username;
|
|
2490
|
+
if (node.apiKey !== void 0) out.apiKey = node.apiKey;
|
|
2491
|
+
if (node.subscribedStatuses !== void 0) out.subscribedStatuses = node.subscribedStatuses;
|
|
2492
|
+
if (typeof node.active === "boolean") out.active = node.active;
|
|
2493
|
+
else if (typeof node.isActive === "boolean") out.active = node.isActive;
|
|
2494
|
+
else if (typeof node.status === "string") out.active = node.status.toUpperCase() === "ACTIVE";
|
|
2495
|
+
return out;
|
|
2496
|
+
}
|
|
2497
|
+
var WebhooksResource = class {
|
|
2498
|
+
constructor(transport, limiter) {
|
|
2499
|
+
this.transport = transport;
|
|
2500
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 50, intervalMs: 6e4 });
|
|
2501
|
+
}
|
|
2502
|
+
transport;
|
|
2503
|
+
limiter;
|
|
2504
|
+
/**
|
|
2505
|
+
* Create a new webhook subscription. Trendyol caps subscriptions at 15
|
|
2506
|
+
* per seller — the SDK does NOT pre-check (you'd need to call `list()`
|
|
2507
|
+
* first), but Trendyol returns 400 when the cap is exceeded.
|
|
2508
|
+
*
|
|
2509
|
+
* @throws {ValidationError} when `url` or `authenticationType` is missing.
|
|
2510
|
+
*/
|
|
2511
|
+
async create(input) {
|
|
2512
|
+
this.validateInput(input, "create");
|
|
2513
|
+
return this.transport.request({
|
|
2514
|
+
method: "POST",
|
|
2515
|
+
path: `/integration/sellers/${this.transport.sellerId}/webhooks`,
|
|
2516
|
+
body: input,
|
|
2517
|
+
rateLimiter: this.limiter
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
/** List all registered webhook subscriptions. */
|
|
2521
|
+
async list() {
|
|
2522
|
+
const data = await this.transport.request({
|
|
2523
|
+
method: "GET",
|
|
2524
|
+
path: `/integration/sellers/${this.transport.sellerId}/webhooks`,
|
|
2525
|
+
rateLimiter: this.limiter
|
|
2526
|
+
});
|
|
2527
|
+
const rows = Array.isArray(data) ? data : Array.isArray(data?.webhooks) ? data.webhooks : Array.isArray(data?.content) ? data.content : [];
|
|
2528
|
+
return rows.map((r) => normalizeWebhook(r));
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Update a webhook subscription. Same input shape as `create`; replaces
|
|
2532
|
+
* the whole subscription (Trendyol does NOT partially update).
|
|
2533
|
+
*/
|
|
2534
|
+
async update(webhookId, input) {
|
|
2535
|
+
this.validateInput(input, "update");
|
|
2536
|
+
return this.transport.request({
|
|
2537
|
+
method: "PUT",
|
|
2538
|
+
path: this.webhookPath(webhookId),
|
|
2539
|
+
body: input,
|
|
2540
|
+
rateLimiter: this.limiter
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
/** Permanently delete a webhook subscription. */
|
|
2544
|
+
async delete(webhookId) {
|
|
2545
|
+
return this.transport.request({
|
|
2546
|
+
method: "DELETE",
|
|
2547
|
+
path: this.webhookPath(webhookId),
|
|
2548
|
+
rateLimiter: this.limiter
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
/** Re-activate a previously-deactivated webhook subscription. */
|
|
2552
|
+
async activate(webhookId) {
|
|
2553
|
+
return this.transport.request({
|
|
2554
|
+
method: "PUT",
|
|
2555
|
+
path: `${this.webhookPath(webhookId)}/activate`,
|
|
2556
|
+
rateLimiter: this.limiter
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* Deactivate a webhook subscription. Trendyol automatically deactivates
|
|
2561
|
+
* a subscription after persistent delivery failures (and sends 2 emails);
|
|
2562
|
+
* use `activate()` to bring it back online once your endpoint is healthy.
|
|
2563
|
+
*/
|
|
2564
|
+
async deactivate(webhookId) {
|
|
2565
|
+
return this.transport.request({
|
|
2566
|
+
method: "PUT",
|
|
2567
|
+
path: `${this.webhookPath(webhookId)}/deactivate`,
|
|
2568
|
+
rateLimiter: this.limiter
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
validateInput(input, method) {
|
|
2572
|
+
if (!input?.url || typeof input.url !== "string") {
|
|
2573
|
+
throw new core.ValidationError({ message: `webhooks.${method}: url is required` });
|
|
2574
|
+
}
|
|
2575
|
+
if (input.authenticationType !== "BASIC_AUTHENTICATION" && input.authenticationType !== "API_KEY") {
|
|
2576
|
+
throw new core.ValidationError({
|
|
2577
|
+
message: `webhooks.${method}: authenticationType must be 'BASIC_AUTHENTICATION' or 'API_KEY'`
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
if (input.authenticationType === "BASIC_AUTHENTICATION") {
|
|
2581
|
+
if (!input.username || !input.password) {
|
|
2582
|
+
throw new core.ValidationError({
|
|
2583
|
+
message: `webhooks.${method}: BASIC_AUTHENTICATION requires username + password`
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
} else if (!input.apiKey) {
|
|
2587
|
+
throw new core.ValidationError({
|
|
2588
|
+
message: `webhooks.${method}: API_KEY requires apiKey`
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
if (input.subscribedStatuses && input.subscribedStatuses.length === 0) ;
|
|
2592
|
+
}
|
|
2593
|
+
webhookPath(webhookId) {
|
|
2594
|
+
return `/integration/sellers/${this.transport.sellerId}/webhooks/${encodeURIComponent(String(webhookId))}`;
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
|
|
2598
|
+
// src/capabilities.ts
|
|
2599
|
+
var trendyolCapabilities = {
|
|
2600
|
+
/** Trendyol has no time-bounded / scheduled pricing (`pricings[]`). */
|
|
2601
|
+
scheduledPricing: false,
|
|
2602
|
+
/** `inventory.update` accepts stock-only items (`quantity` with no price). */
|
|
2603
|
+
stockOnlyBatch: true,
|
|
2604
|
+
/** Products expose `updatedAt`, so last-write-wins guards are supported. */
|
|
2605
|
+
listingUpdatedAt: true
|
|
2606
|
+
};
|
|
2607
|
+
|
|
2608
|
+
// src/auth.ts
|
|
2609
|
+
function buildAuthHeader(apiKey, apiSecret) {
|
|
2610
|
+
const token = Buffer.from(`${apiKey}:${apiSecret}`, "utf8").toString("base64");
|
|
2611
|
+
return `Basic ${token}`;
|
|
2612
|
+
}
|
|
2613
|
+
function buildUserAgent(sellerId, integratorName) {
|
|
2614
|
+
return `${sellerId} - ${integratorName}`;
|
|
2615
|
+
}
|
|
2616
|
+
function mapHttpError(status, body, retryAfterMs) {
|
|
2617
|
+
const data = { body };
|
|
2618
|
+
const issues = normalizeErrorIssues(body);
|
|
2619
|
+
switch (status) {
|
|
2620
|
+
case 401:
|
|
2621
|
+
return new core.AuthError({
|
|
2622
|
+
message: "Trendyol authentication failed (check apiKey / apiSecret)",
|
|
2623
|
+
status,
|
|
2624
|
+
data,
|
|
2625
|
+
issues
|
|
2626
|
+
});
|
|
2627
|
+
case 403:
|
|
2628
|
+
return new core.ValidationError({
|
|
2629
|
+
message: "Trendyol forbidden (check User-Agent header or endpoint path)",
|
|
2630
|
+
status,
|
|
2631
|
+
data,
|
|
2632
|
+
issues
|
|
2633
|
+
});
|
|
2634
|
+
case 404:
|
|
2635
|
+
return new core.NotFoundError({ message: "Trendyol resource not found", status, data, issues });
|
|
2636
|
+
case 429:
|
|
2637
|
+
return new core.RateLimitError({
|
|
2638
|
+
message: "Trendyol rate limit exceeded",
|
|
2639
|
+
status,
|
|
2640
|
+
retryAfterMs,
|
|
2641
|
+
data,
|
|
2642
|
+
issues
|
|
2643
|
+
});
|
|
2644
|
+
case 500:
|
|
2645
|
+
case 502:
|
|
2646
|
+
case 503:
|
|
2647
|
+
case 504:
|
|
2648
|
+
return new core.ServerError({
|
|
2649
|
+
message: `Trendyol server error (${status})`,
|
|
2650
|
+
status,
|
|
2651
|
+
data,
|
|
2652
|
+
issues
|
|
2653
|
+
});
|
|
2654
|
+
default:
|
|
2655
|
+
return new core.LoncaError({
|
|
2656
|
+
code: "UNKNOWN",
|
|
2657
|
+
message: `Trendyol unexpected response (${status})`,
|
|
2658
|
+
status,
|
|
2659
|
+
data,
|
|
2660
|
+
issues,
|
|
2661
|
+
retryable: false
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function normalizeErrorIssues(body) {
|
|
2666
|
+
const errors = extractErrorsArray(body);
|
|
2667
|
+
if (!errors) return [];
|
|
2668
|
+
const issues = [];
|
|
2669
|
+
for (const entry of errors) {
|
|
2670
|
+
if (typeof entry === "string") {
|
|
2671
|
+
issues.push({ message: entry });
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
if (entry && typeof entry === "object") {
|
|
2675
|
+
const e = entry;
|
|
2676
|
+
if (typeof e.message !== "string") continue;
|
|
2677
|
+
const issue = { message: e.message };
|
|
2678
|
+
if (typeof e.field === "string") issue.field = e.field;
|
|
2679
|
+
if (typeof e.code === "string") issue.code = e.code;
|
|
2680
|
+
issues.push(issue);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return issues;
|
|
2684
|
+
}
|
|
2685
|
+
function extractErrorsArray(body) {
|
|
2686
|
+
if (!body || typeof body !== "object") return void 0;
|
|
2687
|
+
const b = body;
|
|
2688
|
+
if (Array.isArray(b.errors)) return b.errors;
|
|
2689
|
+
const nested = b.body;
|
|
2690
|
+
if (nested && typeof nested === "object") {
|
|
2691
|
+
const n2 = nested;
|
|
2692
|
+
if (Array.isArray(n2.errors)) return n2.errors;
|
|
2693
|
+
}
|
|
2694
|
+
return void 0;
|
|
2695
|
+
}
|
|
2696
|
+
function parseRetryAfter(header) {
|
|
2697
|
+
if (!header) return void 0;
|
|
2698
|
+
const seconds = Number(header);
|
|
2699
|
+
if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1e3);
|
|
2700
|
+
const epoch = Date.parse(header);
|
|
2701
|
+
if (!Number.isNaN(epoch)) return Math.max(0, epoch - Date.now());
|
|
2702
|
+
return void 0;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
// src/transport.ts
|
|
2706
|
+
var BASE_URLS = {
|
|
2707
|
+
prod: "https://apigw.trendyol.com",
|
|
2708
|
+
stage: "https://stageapigw.trendyol.com"
|
|
2709
|
+
};
|
|
2710
|
+
var TrendyolTransport = class {
|
|
2711
|
+
constructor(config) {
|
|
2712
|
+
this.config = config;
|
|
2713
|
+
this.baseUrl = BASE_URLS[config.env];
|
|
2714
|
+
this.logger = config.logger ?? core.noopLogger;
|
|
2715
|
+
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
2716
|
+
this.fetchImpl = config.fetch ?? fetch;
|
|
2717
|
+
}
|
|
2718
|
+
config;
|
|
2719
|
+
baseUrl;
|
|
2720
|
+
logger;
|
|
2721
|
+
timeoutMs;
|
|
2722
|
+
fetchImpl;
|
|
2723
|
+
/** Seller ID this transport is configured with. Resources read it for path-building. */
|
|
2724
|
+
get sellerId() {
|
|
2725
|
+
return this.config.sellerId;
|
|
2726
|
+
}
|
|
2727
|
+
async request(opts) {
|
|
2728
|
+
return core.retry(
|
|
2729
|
+
async (attempt) => {
|
|
2730
|
+
if (opts.rateLimiter) await opts.rateLimiter.acquire(opts.signal);
|
|
2731
|
+
const url = this.buildUrl(opts.path, opts.query);
|
|
2732
|
+
const correlationId = crypto.randomUUID();
|
|
2733
|
+
const headers = this.buildHeaders(correlationId);
|
|
2734
|
+
const init = {
|
|
2735
|
+
method: opts.method,
|
|
2736
|
+
headers,
|
|
2737
|
+
signal: this.composeSignal(opts.signal)
|
|
2738
|
+
};
|
|
2739
|
+
if (opts.body !== void 0 && opts.method !== "GET") {
|
|
2740
|
+
if (opts.body instanceof FormData) {
|
|
2741
|
+
init.body = opts.body;
|
|
2742
|
+
delete headers["Content-Type"];
|
|
2743
|
+
} else {
|
|
2744
|
+
init.body = JSON.stringify(opts.body);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
this.logger.debug("trendyol.request", {
|
|
2748
|
+
method: opts.method,
|
|
2749
|
+
url,
|
|
2750
|
+
correlationId,
|
|
2751
|
+
attempt
|
|
2752
|
+
});
|
|
2753
|
+
let response;
|
|
2754
|
+
try {
|
|
2755
|
+
response = await this.fetchImpl(url, init);
|
|
2756
|
+
} catch (err) {
|
|
2757
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2758
|
+
throw new core.TimeoutError({
|
|
2759
|
+
message: `Trendyol request timed out after ${this.timeoutMs}ms`,
|
|
2760
|
+
cause: err
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
throw new core.NetworkError({
|
|
2764
|
+
message: "Trendyol network failure",
|
|
2765
|
+
cause: err
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
2768
|
+
if (!response.ok) {
|
|
2769
|
+
const body = await safeJson(response);
|
|
2770
|
+
const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
|
|
2771
|
+
const error = mapHttpError(response.status, body, retryAfterMs);
|
|
2772
|
+
this.logger.warn("trendyol.error", {
|
|
2773
|
+
method: opts.method,
|
|
2774
|
+
url,
|
|
2775
|
+
correlationId,
|
|
2776
|
+
status: response.status,
|
|
2777
|
+
code: error.code,
|
|
2778
|
+
retryable: error.retryable
|
|
2779
|
+
});
|
|
2780
|
+
throw error;
|
|
2781
|
+
}
|
|
2782
|
+
this.logger.debug("trendyol.response", {
|
|
2783
|
+
correlationId,
|
|
2784
|
+
status: response.status
|
|
2785
|
+
});
|
|
2786
|
+
if (response.status === 204) return void 0;
|
|
2787
|
+
return await safeJson(response);
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
signal: opts.signal,
|
|
2791
|
+
onRetry: (err, attempt, delay2) => {
|
|
2792
|
+
if (err instanceof core.LoncaError) {
|
|
2793
|
+
this.logger.warn("trendyol.retry", {
|
|
2794
|
+
attempt,
|
|
2795
|
+
delayMs: delay2,
|
|
2796
|
+
code: err.code,
|
|
2797
|
+
status: err.status
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2804
|
+
buildUrl(path, query) {
|
|
2805
|
+
const url = new URL(path, this.baseUrl);
|
|
2806
|
+
if (query) {
|
|
2807
|
+
for (const [key, value] of Object.entries(query)) {
|
|
2808
|
+
if (value === void 0) continue;
|
|
2809
|
+
url.searchParams.set(key, String(value));
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
return url.toString();
|
|
2813
|
+
}
|
|
2814
|
+
buildHeaders(correlationId) {
|
|
2815
|
+
return {
|
|
2816
|
+
Authorization: buildAuthHeader(this.config.apiKey, this.config.apiSecret),
|
|
2817
|
+
"x-clientip": this.config.clientIp ?? "127.0.0.1",
|
|
2818
|
+
"x-correlationid": correlationId,
|
|
2819
|
+
"x-agentname": this.config.integratorName,
|
|
2820
|
+
"User-Agent": buildUserAgent(this.config.sellerId, this.config.integratorName),
|
|
2821
|
+
"Content-Type": "application/json",
|
|
2822
|
+
Accept: "application/json"
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
composeSignal(external) {
|
|
2826
|
+
const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
|
|
2827
|
+
if (!external) return timeoutSignal;
|
|
2828
|
+
return AbortSignal.any([external, timeoutSignal]);
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
async function safeJson(response) {
|
|
2832
|
+
const text = await response.text();
|
|
2833
|
+
if (!text) return void 0;
|
|
2834
|
+
try {
|
|
2835
|
+
return JSON.parse(text);
|
|
2836
|
+
} catch {
|
|
2837
|
+
return text;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/client.ts
|
|
2842
|
+
function buildClient(transport) {
|
|
2843
|
+
const products = new ProductsResource(transport);
|
|
2844
|
+
return {
|
|
2845
|
+
brands: new BrandsResource(transport),
|
|
2846
|
+
categories: new CategoriesResource(transport),
|
|
2847
|
+
suppliers: new SuppliersResource(transport),
|
|
2848
|
+
products,
|
|
2849
|
+
inventory: new InventoryResource(transport, (id) => products.getBatchStatus(id)),
|
|
2850
|
+
orders: new OrdersResource(transport),
|
|
2851
|
+
claims: new ClaimsResource(transport),
|
|
2852
|
+
webhooks: new WebhooksResource(transport),
|
|
2853
|
+
questions: new QuestionsResource(transport),
|
|
2854
|
+
invoices: new InvoicesResource(transport),
|
|
2855
|
+
finance: new FinanceResource(transport),
|
|
2856
|
+
labels: new LabelsResource(transport),
|
|
2857
|
+
testOrders: new TestOrdersResource(transport),
|
|
2858
|
+
locations: new LocationsResource(transport),
|
|
2859
|
+
exportCenter: new ExportCenterResource(transport),
|
|
2860
|
+
videos: new VideosResource(transport),
|
|
2861
|
+
capabilities: trendyolCapabilities
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// src/testing.ts
|
|
2866
|
+
function createFakeTrendyolClient(seed = {}) {
|
|
2867
|
+
const batchRequestId = seed.batchRequestId ?? "fake-batch-1";
|
|
2868
|
+
const batchStatus = seed.batchStatus ?? "COMPLETED";
|
|
2869
|
+
const fakeFetch = (input, init) => {
|
|
2870
|
+
const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
2871
|
+
const url = new URL(rawUrl);
|
|
2872
|
+
const req = {
|
|
2873
|
+
method: (init?.method ?? "GET").toUpperCase(),
|
|
2874
|
+
path: url.pathname,
|
|
2875
|
+
query: url.searchParams,
|
|
2876
|
+
body: parseBody(init?.body)
|
|
2877
|
+
};
|
|
2878
|
+
const handled = seed.handler?.(req);
|
|
2879
|
+
const data = handled !== void 0 ? handled : defaultResponse(req, batchRequestId, batchStatus);
|
|
2880
|
+
return Promise.resolve(jsonResponse(data));
|
|
2881
|
+
};
|
|
2882
|
+
const transport = new TrendyolTransport({
|
|
2883
|
+
sellerId: 1,
|
|
2884
|
+
apiKey: "fake",
|
|
2885
|
+
apiSecret: "fake",
|
|
2886
|
+
env: "stage",
|
|
2887
|
+
integratorName: "fake",
|
|
2888
|
+
fetch: fakeFetch
|
|
2889
|
+
});
|
|
2890
|
+
return buildClient(transport);
|
|
2891
|
+
}
|
|
2892
|
+
function defaultResponse(req, batchRequestId, batchStatus) {
|
|
2893
|
+
if (req.method === "POST" && req.path.endsWith("/products/price-and-inventory")) {
|
|
2894
|
+
return { batchRequestId };
|
|
2895
|
+
}
|
|
2896
|
+
if (req.method === "GET" && req.path.includes("/products/batch-requests/")) {
|
|
2897
|
+
const id = decodeURIComponent(req.path.split("/").pop() ?? batchRequestId);
|
|
2898
|
+
return { batchRequestId: id, status: batchStatus, itemCount: 0, failedItemCount: 0, items: [] };
|
|
2899
|
+
}
|
|
2900
|
+
return {};
|
|
2901
|
+
}
|
|
2902
|
+
function parseBody(body) {
|
|
2903
|
+
if (typeof body !== "string") return void 0;
|
|
2904
|
+
try {
|
|
2905
|
+
return JSON.parse(body);
|
|
2906
|
+
} catch {
|
|
2907
|
+
return body;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
function jsonResponse(data) {
|
|
2911
|
+
return new Response(JSON.stringify(data), {
|
|
2912
|
+
status: 200,
|
|
2913
|
+
headers: { "content-type": "application/json" }
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
exports.createFakeTrendyolClient = createFakeTrendyolClient;
|
|
2918
|
+
//# sourceMappingURL=testing.cjs.map
|
|
2919
|
+
//# sourceMappingURL=testing.cjs.map
|