@lonca/trendyol 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +1031 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1049 -1
- package/dist/index.d.ts +1049 -1
- package/dist/index.js +1028 -4
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.cjs
CHANGED
|
@@ -28,7 +28,7 @@ var BrandsResource = class {
|
|
|
28
28
|
const size = params.limit ?? DEFAULT_PAGE_SIZE;
|
|
29
29
|
const data = await this.transport.request({
|
|
30
30
|
method: "GET",
|
|
31
|
-
path: "/
|
|
31
|
+
path: "/integration/product/brands",
|
|
32
32
|
query: { page, size },
|
|
33
33
|
rateLimiter: this.limiter
|
|
34
34
|
});
|
|
@@ -36,6 +36,1025 @@ var BrandsResource = class {
|
|
|
36
36
|
const nextCursor = page + 1 < data.totalPages ? String(page + 1) : void 0;
|
|
37
37
|
return nextCursor !== void 0 ? { items, nextCursor } : { items };
|
|
38
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, sellerId) {
|
|
95
|
+
this.transport = transport;
|
|
96
|
+
this.sellerId = sellerId;
|
|
97
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 50, intervalMs: 6e4 });
|
|
98
|
+
}
|
|
99
|
+
transport;
|
|
100
|
+
sellerId;
|
|
101
|
+
limiter;
|
|
102
|
+
/**
|
|
103
|
+
* Fetch the full Trendyol category tree.
|
|
104
|
+
*
|
|
105
|
+
* Trendyol returns the entire tree in one response — there is no pagination.
|
|
106
|
+
* Cache the result aggressively in your application; the tree changes rarely.
|
|
107
|
+
*/
|
|
108
|
+
async list() {
|
|
109
|
+
const data = await this.transport.request({
|
|
110
|
+
method: "GET",
|
|
111
|
+
path: "/integration/product/product-categories",
|
|
112
|
+
rateLimiter: this.limiter
|
|
113
|
+
});
|
|
114
|
+
const list = Array.isArray(data) ? data : data.categories;
|
|
115
|
+
return list.map(normalizeCategory);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Fetch the attributes (required and optional) for a single category.
|
|
119
|
+
*
|
|
120
|
+
* Call this before `createProduct V2` so you know which attributes are
|
|
121
|
+
* mandatory — the API rejects products that omit any `required` attribute.
|
|
122
|
+
*
|
|
123
|
+
* @param categoryId Trendyol numeric category ID; accepts `string` or `number`.
|
|
124
|
+
*/
|
|
125
|
+
async getAttributes(categoryId) {
|
|
126
|
+
const id = encodeURIComponent(String(categoryId));
|
|
127
|
+
const data = await this.transport.request({
|
|
128
|
+
method: "GET",
|
|
129
|
+
path: `/integration/product/categories/${id}/attributes`,
|
|
130
|
+
rateLimiter: this.limiter
|
|
131
|
+
});
|
|
132
|
+
const list = extractAttributeList(data);
|
|
133
|
+
if (!list) {
|
|
134
|
+
console.error(
|
|
135
|
+
"[@lonca/trendyol] getCategoryAttributes: unexpected response shape:",
|
|
136
|
+
JSON.stringify(data).slice(0, 800)
|
|
137
|
+
);
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
return list.map(normalizeAttribute);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Fetch the allowed values for a single category attribute (paginated).
|
|
144
|
+
*
|
|
145
|
+
* `getCategoryAttributes` returns attribute metadata + flags but typically
|
|
146
|
+
* omits the value catalog. Use this method to fetch the catalog for an
|
|
147
|
+
* attribute when `allowCustom` is `false` and you need to map your data
|
|
148
|
+
* onto Trendyol's accepted values.
|
|
149
|
+
*
|
|
150
|
+
* @param categoryId Trendyol numeric category ID; accepts `string` or `number`.
|
|
151
|
+
* @param attributeId Attribute ID returned by `getAttributes`.
|
|
152
|
+
* @param params Cursor pagination (max page size 1000; default 100).
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* import { paginate } from '@lonca/core';
|
|
157
|
+
* for await (const value of paginate((p) =>
|
|
158
|
+
* client.categories.getAttributeValues(catId, attrId, p),
|
|
159
|
+
* )) {
|
|
160
|
+
* console.log(value.id, value.name);
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
async getAttributeValues(categoryId, attributeId, params = {}) {
|
|
165
|
+
const catId = encodeURIComponent(String(categoryId));
|
|
166
|
+
const attrId = encodeURIComponent(String(attributeId));
|
|
167
|
+
const size = Math.min(params.limit ?? DEFAULT_ATTR_VALUES_PAGE_SIZE, MAX_ATTR_VALUES_PAGE_SIZE);
|
|
168
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
169
|
+
const data = await this.transport.request({
|
|
170
|
+
method: "GET",
|
|
171
|
+
path: `/integration/product/categories/${catId}/attributes/${attrId}/values`,
|
|
172
|
+
query: { page, size },
|
|
173
|
+
rateLimiter: this.limiter
|
|
174
|
+
});
|
|
175
|
+
const items = (data.content ?? []).map((item) => ({
|
|
176
|
+
id: item.attributeValueId !== void 0 ? String(item.attributeValueId) : "",
|
|
177
|
+
name: item.attributeValue ?? item.attributeValueName ?? ""
|
|
178
|
+
}));
|
|
179
|
+
const result = { items };
|
|
180
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
181
|
+
if (page + 1 < totalPages) {
|
|
182
|
+
result.nextCursor = String(page + 1);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Look up category info for a list of barcodes (Trendyol Export Center
|
|
188
|
+
* / AutoFT endpoint).
|
|
189
|
+
*
|
|
190
|
+
* **Requires Export Center enrollment.** Sellers who have not joined
|
|
191
|
+
* Trendyol's "İhracat Merkezi" program will get an auth error on this
|
|
192
|
+
* endpoint even though their regular Marketplace credentials are valid.
|
|
193
|
+
*
|
|
194
|
+
* @param barcodes 1–N barcodes to look up.
|
|
195
|
+
* @throws {ValidationError} when `barcodes` is empty.
|
|
196
|
+
* @throws Error when the client was created without a `sellerId` and this
|
|
197
|
+
* method is called directly (use `createTrendyolClient` to wire it).
|
|
198
|
+
*/
|
|
199
|
+
async getByBarcodes(barcodes) {
|
|
200
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
201
|
+
throw new core.ValidationError({ message: "getByBarcodes: barcodes must not be empty" });
|
|
202
|
+
}
|
|
203
|
+
if (this.sellerId === void 0) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
"CategoriesResource.getByBarcodes requires a sellerId; instantiate via createTrendyolClient"
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const data = await this.transport.request({
|
|
209
|
+
method: "POST",
|
|
210
|
+
path: `/integration/ecgw/v1/${this.sellerId}/lookup/product-categories/by-barcodes`,
|
|
211
|
+
body: { barcodes },
|
|
212
|
+
rateLimiter: this.limiter
|
|
213
|
+
});
|
|
214
|
+
const matches = Object.entries(data.barcodeCategories ?? {}).map(([barcode, cat]) => ({
|
|
215
|
+
barcode,
|
|
216
|
+
category: {
|
|
217
|
+
id: cat.id !== void 0 ? String(cat.id) : "",
|
|
218
|
+
name: cat.displayName ?? ""
|
|
219
|
+
}
|
|
220
|
+
}));
|
|
221
|
+
return { matches, notFound: data.notFound ?? [] };
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function extractAttributeList(data) {
|
|
225
|
+
if (Array.isArray(data)) return data;
|
|
226
|
+
if (data && typeof data === "object") {
|
|
227
|
+
const obj = data;
|
|
228
|
+
if (Array.isArray(obj.categoryAttributes)) {
|
|
229
|
+
return obj.categoryAttributes;
|
|
230
|
+
}
|
|
231
|
+
if (Array.isArray(obj.attributes)) {
|
|
232
|
+
return obj.attributes;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
var MAX_ITEMS_PER_REQUEST = 1e3;
|
|
238
|
+
var InventoryResource = class {
|
|
239
|
+
constructor(transport, sellerId) {
|
|
240
|
+
this.transport = transport;
|
|
241
|
+
this.sellerId = sellerId;
|
|
242
|
+
}
|
|
243
|
+
transport;
|
|
244
|
+
sellerId;
|
|
245
|
+
/**
|
|
246
|
+
* Update price and/or stock for one or more SKUs (by barcode).
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* const { batchRequestId } = await client.inventory.update([
|
|
251
|
+
* { barcode: 'ABC123', quantity: 42, salePrice: 199.9, listPrice: 249.9 },
|
|
252
|
+
* { barcode: 'XYZ789', quantity: 0 },
|
|
253
|
+
* ]);
|
|
254
|
+
* const status = await client.products.getBatchStatus(batchRequestId);
|
|
255
|
+
* ```
|
|
256
|
+
*
|
|
257
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
258
|
+
*/
|
|
259
|
+
async update(items) {
|
|
260
|
+
if (items.length === 0) {
|
|
261
|
+
throw new core.ValidationError({ message: "updatePriceAndInventory: items must not be empty" });
|
|
262
|
+
}
|
|
263
|
+
if (items.length > MAX_ITEMS_PER_REQUEST) {
|
|
264
|
+
throw new core.ValidationError({
|
|
265
|
+
message: `updatePriceAndInventory: max ${MAX_ITEMS_PER_REQUEST} items per request, got ${items.length}`
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const data = await this.transport.request({
|
|
269
|
+
method: "POST",
|
|
270
|
+
path: `/integration/inventory/sellers/${this.sellerId}/products/price-and-inventory`,
|
|
271
|
+
body: { items }
|
|
272
|
+
});
|
|
273
|
+
return { batchRequestId: data.batchRequestId ?? "" };
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var MAX_PAGE_SIZE = 200;
|
|
277
|
+
var DEFAULT_PAGE_SIZE2 = 50;
|
|
278
|
+
function toIso(epochMs) {
|
|
279
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
return new Date(epochMs).toISOString();
|
|
283
|
+
}
|
|
284
|
+
function normalizeAddress(node) {
|
|
285
|
+
if (!node) return void 0;
|
|
286
|
+
const out = {};
|
|
287
|
+
if (node.id !== void 0) out.id = String(node.id);
|
|
288
|
+
if (node.firstName !== void 0) out.firstName = node.firstName;
|
|
289
|
+
if (node.lastName !== void 0) out.lastName = node.lastName;
|
|
290
|
+
if (node.fullName !== void 0) out.fullName = node.fullName;
|
|
291
|
+
if (node.company !== void 0) out.company = node.company;
|
|
292
|
+
if (node.address1 !== void 0) out.address1 = node.address1;
|
|
293
|
+
if (node.address2 !== void 0) out.address2 = node.address2;
|
|
294
|
+
if (node.fullAddress !== void 0) out.fullAddress = node.fullAddress;
|
|
295
|
+
if (node.shortAddress !== void 0) out.shortAddress = node.shortAddress;
|
|
296
|
+
if (node.city !== void 0) out.city = node.city;
|
|
297
|
+
if (node.cityCode !== void 0) out.cityCode = node.cityCode;
|
|
298
|
+
if (node.district !== void 0) out.district = node.district;
|
|
299
|
+
if (node.districtId !== void 0) out.districtId = node.districtId;
|
|
300
|
+
if (node.neighborhoodId !== void 0) out.neighborhoodId = node.neighborhoodId;
|
|
301
|
+
if (node.countyId !== void 0) out.countyId = node.countyId;
|
|
302
|
+
if (node.countyName !== void 0) out.countyName = node.countyName;
|
|
303
|
+
if (node.stateName !== void 0) out.stateName = node.stateName;
|
|
304
|
+
if (node.postalCode !== void 0) out.postalCode = node.postalCode;
|
|
305
|
+
if (node.countryCode !== void 0) out.countryCode = node.countryCode;
|
|
306
|
+
if (node.phone !== void 0) out.phone = node.phone;
|
|
307
|
+
if (node.addressLines !== void 0) out.addressLines = node.addressLines;
|
|
308
|
+
return out;
|
|
309
|
+
}
|
|
310
|
+
function normalizeLine(node) {
|
|
311
|
+
const out = {
|
|
312
|
+
id: node.lineId !== void 0 ? String(node.lineId) : "",
|
|
313
|
+
quantity: typeof node.quantity === "number" ? node.quantity : 0,
|
|
314
|
+
productName: node.productName ?? "",
|
|
315
|
+
barcode: node.barcode ?? "",
|
|
316
|
+
lineUnitPrice: typeof node.lineUnitPrice === "number" ? node.lineUnitPrice : 0,
|
|
317
|
+
lineGrossAmount: typeof node.lineGrossAmount === "number" ? node.lineGrossAmount : 0,
|
|
318
|
+
raw: node
|
|
319
|
+
};
|
|
320
|
+
if (node.productSize !== void 0) out.productSize = node.productSize;
|
|
321
|
+
if (node.productColor !== void 0) out.productColor = node.productColor;
|
|
322
|
+
if (node.stockCode !== void 0) out.stockCode = node.stockCode;
|
|
323
|
+
if (node.contentId !== void 0) out.contentId = String(node.contentId);
|
|
324
|
+
if (node.sellerId !== void 0) out.sellerId = String(node.sellerId);
|
|
325
|
+
if (node.productCategoryId !== void 0) out.productCategoryId = String(node.productCategoryId);
|
|
326
|
+
if (node.salesCampaignId !== void 0) out.salesCampaignId = String(node.salesCampaignId);
|
|
327
|
+
if (node.currencyCode !== void 0) out.currencyCode = node.currencyCode;
|
|
328
|
+
if (node.lineSellerDiscount !== void 0) out.lineSellerDiscount = node.lineSellerDiscount;
|
|
329
|
+
if (node.lineTyDiscount !== void 0) out.lineTyDiscount = node.lineTyDiscount;
|
|
330
|
+
if (node.lineTotalDiscount !== void 0) out.lineTotalDiscount = node.lineTotalDiscount;
|
|
331
|
+
if (node.vatRate !== void 0) out.vatRate = node.vatRate;
|
|
332
|
+
if (node.commission !== void 0) out.commission = node.commission;
|
|
333
|
+
if (node.orderLineItemStatusName !== void 0) {
|
|
334
|
+
out.orderLineItemStatusName = node.orderLineItemStatusName;
|
|
335
|
+
}
|
|
336
|
+
if (node.businessUnit !== void 0) out.businessUnit = node.businessUnit;
|
|
337
|
+
if (Array.isArray(node.fastDeliveryOptions)) out.fastDeliveryOptions = node.fastDeliveryOptions;
|
|
338
|
+
if (Array.isArray(node.discountDetails)) {
|
|
339
|
+
out.discountDetails = node.discountDetails;
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
function normalizeHistory(node) {
|
|
344
|
+
const out = { raw: node };
|
|
345
|
+
if (node.status !== void 0) out.status = node.status;
|
|
346
|
+
const createdAt = toIso(node.createdDate);
|
|
347
|
+
if (createdAt) out.createdAt = createdAt;
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
function normalizeCustomer(node) {
|
|
351
|
+
const out = {
|
|
352
|
+
firstName: node.customerFirstName ?? "",
|
|
353
|
+
lastName: node.customerLastName ?? ""
|
|
354
|
+
};
|
|
355
|
+
if (node.customerId !== void 0) out.id = String(node.customerId);
|
|
356
|
+
if (node.customerEmail !== void 0) out.email = node.customerEmail;
|
|
357
|
+
if (node.taxNumber !== void 0 && node.taxNumber !== null) out.taxNumber = node.taxNumber;
|
|
358
|
+
if (node.identityNumber !== void 0) out.identityNumber = node.identityNumber;
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
function normalizePackage(node) {
|
|
362
|
+
const out = {
|
|
363
|
+
id: node.shipmentPackageId !== void 0 ? String(node.shipmentPackageId) : "",
|
|
364
|
+
orderNumber: node.orderNumber ?? "",
|
|
365
|
+
status: node.status ?? "",
|
|
366
|
+
customer: normalizeCustomer(node),
|
|
367
|
+
orderDate: toIso(node.orderDate) ?? "",
|
|
368
|
+
lastModifiedDate: toIso(node.lastModifiedDate) ?? "",
|
|
369
|
+
currencyCode: node.currencyCode ?? "",
|
|
370
|
+
packageTotalPrice: typeof node.packageTotalPrice === "number" ? node.packageTotalPrice : 0,
|
|
371
|
+
packageGrossAmount: typeof node.packageGrossAmount === "number" ? node.packageGrossAmount : 0,
|
|
372
|
+
packageSellerDiscount: typeof node.packageSellerDiscount === "number" ? node.packageSellerDiscount : 0,
|
|
373
|
+
packageTyDiscount: typeof node.packageTyDiscount === "number" ? node.packageTyDiscount : 0,
|
|
374
|
+
packageTotalDiscount: typeof node.packageTotalDiscount === "number" ? node.packageTotalDiscount : 0,
|
|
375
|
+
lines: (node.lines ?? []).map(normalizeLine),
|
|
376
|
+
packageHistories: (node.packageHistories ?? []).map(normalizeHistory),
|
|
377
|
+
raw: node
|
|
378
|
+
};
|
|
379
|
+
if (node.shipmentNumber !== void 0) out.shipmentNumber = String(node.shipmentNumber);
|
|
380
|
+
if (node.originPackageIds !== void 0 && node.originPackageIds !== null) {
|
|
381
|
+
out.originPackageIds = node.originPackageIds.map((id) => String(id));
|
|
382
|
+
}
|
|
383
|
+
if (node.warehouseId !== void 0) out.warehouseId = String(node.warehouseId);
|
|
384
|
+
if (node.supplierId !== void 0) out.supplierId = String(node.supplierId);
|
|
385
|
+
if (node.shipmentPackageStatus !== void 0) {
|
|
386
|
+
out.shipmentPackageStatus = node.shipmentPackageStatus;
|
|
387
|
+
}
|
|
388
|
+
const agreed = toIso(node.agreedDeliveryDate);
|
|
389
|
+
if (agreed) out.agreedDeliveryDate = agreed;
|
|
390
|
+
const estStart = toIso(node.estimatedDeliveryStartDate);
|
|
391
|
+
if (estStart) out.estimatedDeliveryStartDate = estStart;
|
|
392
|
+
const estEnd = toIso(node.estimatedDeliveryEndDate);
|
|
393
|
+
if (estEnd) out.estimatedDeliveryEndDate = estEnd;
|
|
394
|
+
const origin = toIso(node.originShipmentDate);
|
|
395
|
+
if (origin) out.originShipmentDate = origin;
|
|
396
|
+
const invoice = normalizeAddress(node.invoiceAddress);
|
|
397
|
+
if (invoice) out.invoiceAddress = invoice;
|
|
398
|
+
const shipment = normalizeAddress(node.shipmentAddress);
|
|
399
|
+
if (shipment) out.shipmentAddress = shipment;
|
|
400
|
+
if (node.deliveryAddressType !== void 0) out.deliveryAddressType = node.deliveryAddressType;
|
|
401
|
+
if (node.cargoTrackingNumber !== void 0) {
|
|
402
|
+
out.cargoTrackingNumber = String(node.cargoTrackingNumber);
|
|
403
|
+
}
|
|
404
|
+
if (node.cargoProviderName !== void 0) out.cargoProviderName = node.cargoProviderName;
|
|
405
|
+
if (node.cargoProviderId !== void 0) out.cargoProviderId = String(node.cargoProviderId);
|
|
406
|
+
if (node.cargoSenderNumber !== void 0) out.cargoSenderNumber = node.cargoSenderNumber;
|
|
407
|
+
if (node.deliveryType !== void 0) out.deliveryType = node.deliveryType;
|
|
408
|
+
if (node.whoPays !== void 0) out.whoPays = node.whoPays;
|
|
409
|
+
if (node.timeSlotId !== void 0) out.timeSlotId = node.timeSlotId;
|
|
410
|
+
if (typeof node.fastDelivery === "boolean") out.fastDelivery = node.fastDelivery;
|
|
411
|
+
if (node.fastDeliveryType !== void 0) out.fastDeliveryType = node.fastDeliveryType;
|
|
412
|
+
if (typeof node.deliveredByService === "boolean")
|
|
413
|
+
out.deliveredByService = node.deliveredByService;
|
|
414
|
+
if (typeof node.commercial === "boolean") out.commercial = node.commercial;
|
|
415
|
+
if (typeof node.micro === "boolean") out.micro = node.micro;
|
|
416
|
+
if (typeof node.giftBoxRequested === "boolean") out.giftBoxRequested = node.giftBoxRequested;
|
|
417
|
+
if (typeof node["3pByTrendyol"] === "boolean") {
|
|
418
|
+
out.threePByTrendyol = node["3pByTrendyol"];
|
|
419
|
+
}
|
|
420
|
+
if (typeof node.containsDangerousProduct === "boolean") {
|
|
421
|
+
out.containsDangerousProduct = node.containsDangerousProduct;
|
|
422
|
+
}
|
|
423
|
+
if (typeof node.isCod === "boolean") out.isCod = node.isCod;
|
|
424
|
+
if (typeof node.is4P === "boolean") out.is4P = node.is4P;
|
|
425
|
+
if (node.invoiceLink !== void 0) out.invoiceLink = node.invoiceLink;
|
|
426
|
+
if (node.createdBy !== void 0) out.createdBy = node.createdBy;
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
var OrdersResource = class {
|
|
430
|
+
constructor(transport, sellerId, limiter) {
|
|
431
|
+
this.transport = transport;
|
|
432
|
+
this.sellerId = sellerId;
|
|
433
|
+
this.limiter = limiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
434
|
+
}
|
|
435
|
+
transport;
|
|
436
|
+
sellerId;
|
|
437
|
+
limiter;
|
|
438
|
+
/**
|
|
439
|
+
* List shipment packages for the seller.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* import { paginate } from '@lonca/core';
|
|
444
|
+
* for await (const pkg of paginate((p) => client.orders.list({ ...p, status: 'Created' }))) {
|
|
445
|
+
* console.log(pkg.id, pkg.status, pkg.customer.firstName);
|
|
446
|
+
* }
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
async list(params = {}) {
|
|
450
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
|
|
451
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
452
|
+
const query = { page, size };
|
|
453
|
+
if (params.status) query.status = params.status;
|
|
454
|
+
if (params.orderNumber) query.orderNumber = params.orderNumber;
|
|
455
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
456
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
457
|
+
const data = await this.transport.request({
|
|
458
|
+
method: "GET",
|
|
459
|
+
path: `/integration/order/sellers/${this.sellerId}/orders`,
|
|
460
|
+
query,
|
|
461
|
+
rateLimiter: this.limiter
|
|
462
|
+
});
|
|
463
|
+
const items = (data.content ?? []).map(normalizePackage);
|
|
464
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
465
|
+
const result = { items };
|
|
466
|
+
if (page + 1 < totalPages) {
|
|
467
|
+
result.nextCursor = String(page + 1);
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
var DEFAULT_PAGE_SIZE3 = 50;
|
|
473
|
+
var MAX_PAGE_SIZE2 = 1e3;
|
|
474
|
+
var MAX_BUYBOX_BARCODES = 10;
|
|
475
|
+
function normalizeRef(ref) {
|
|
476
|
+
return {
|
|
477
|
+
id: ref?.id !== void 0 ? String(ref.id) : "",
|
|
478
|
+
name: ref?.name ?? ""
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function normalizeAttribute2(attr) {
|
|
482
|
+
const out = {
|
|
483
|
+
attributeId: attr.attributeId !== void 0 ? String(attr.attributeId) : ""
|
|
484
|
+
};
|
|
485
|
+
if (attr.attributeName !== void 0) out.attributeName = attr.attributeName;
|
|
486
|
+
if (attr.attributeValueId !== void 0) {
|
|
487
|
+
out.attributeValueId = String(attr.attributeValueId);
|
|
488
|
+
}
|
|
489
|
+
if (attr.attributeValue !== void 0) out.attributeValue = attr.attributeValue;
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
function normalizeImages(images) {
|
|
493
|
+
if (!images) return [];
|
|
494
|
+
return images.map((img) => typeof img === "string" ? img : img?.url).filter((u) => typeof u === "string" && u.length > 0);
|
|
495
|
+
}
|
|
496
|
+
function normalizeStock(stock) {
|
|
497
|
+
if (typeof stock === "number") return stock;
|
|
498
|
+
if (stock && typeof stock === "object" && typeof stock.quantity === "number") {
|
|
499
|
+
return stock.quantity;
|
|
500
|
+
}
|
|
501
|
+
return void 0;
|
|
502
|
+
}
|
|
503
|
+
function normalizeVariant(variant) {
|
|
504
|
+
const out = {
|
|
505
|
+
variantId: variant.variantId !== void 0 ? String(variant.variantId) : "",
|
|
506
|
+
barcode: variant.barcode ?? "",
|
|
507
|
+
attributes: (variant.attributes ?? []).map(normalizeAttribute2),
|
|
508
|
+
raw: variant
|
|
509
|
+
};
|
|
510
|
+
if (typeof variant.commission === "number") out.commission = variant.commission;
|
|
511
|
+
if (variant.productUrl !== void 0) out.productUrl = variant.productUrl;
|
|
512
|
+
if (typeof variant.onSale === "boolean") out.onSale = variant.onSale;
|
|
513
|
+
const stock = normalizeStock(variant.stock);
|
|
514
|
+
if (stock !== void 0) out.stock = stock;
|
|
515
|
+
return out;
|
|
516
|
+
}
|
|
517
|
+
function toIso2(epochMs) {
|
|
518
|
+
if (typeof epochMs !== "number" || !Number.isFinite(epochMs)) return void 0;
|
|
519
|
+
return new Date(epochMs).toISOString();
|
|
520
|
+
}
|
|
521
|
+
function normalizeProduct(node) {
|
|
522
|
+
const out = {
|
|
523
|
+
contentId: node.contentId !== void 0 ? String(node.contentId) : "",
|
|
524
|
+
productMainId: node.productMainId ?? "",
|
|
525
|
+
title: node.title ?? "",
|
|
526
|
+
brand: normalizeRef(node.brand),
|
|
527
|
+
category: normalizeRef(node.category),
|
|
528
|
+
images: normalizeImages(node.images),
|
|
529
|
+
attributes: (node.attributes ?? []).map(normalizeAttribute2),
|
|
530
|
+
variants: (node.variants ?? []).map(normalizeVariant),
|
|
531
|
+
createdAt: toIso2(node.creationDate) ?? "",
|
|
532
|
+
updatedAt: toIso2(node.lastModifiedDate) ?? "",
|
|
533
|
+
raw: node
|
|
534
|
+
};
|
|
535
|
+
if (node.description !== void 0) out.description = node.description;
|
|
536
|
+
if (node.lastModifiedBy !== void 0) out.lastModifiedBy = node.lastModifiedBy;
|
|
537
|
+
return out;
|
|
538
|
+
}
|
|
539
|
+
function normalizeUnapprovedRejectReason(r) {
|
|
540
|
+
const out = {};
|
|
541
|
+
if (r.rejectReason !== void 0) out.rejectReason = r.rejectReason;
|
|
542
|
+
if (r.rejectReasonDetail !== void 0) out.rejectReasonDetail = r.rejectReasonDetail;
|
|
543
|
+
return out;
|
|
544
|
+
}
|
|
545
|
+
function normalizeUnapproved(node) {
|
|
546
|
+
const imagesSource = node.images ?? node.media;
|
|
547
|
+
const out = {
|
|
548
|
+
productMainId: node.productMainId ?? "",
|
|
549
|
+
brand: normalizeRef(node.brand),
|
|
550
|
+
category: normalizeRef(node.category),
|
|
551
|
+
barcode: node.barcode ?? "",
|
|
552
|
+
title: node.title ?? "",
|
|
553
|
+
images: normalizeImages(imagesSource),
|
|
554
|
+
attributes: (node.attributes ?? []).map(normalizeAttribute2),
|
|
555
|
+
rejectReasonDetails: (node.rejectReasonDetails ?? []).map(normalizeUnapprovedRejectReason),
|
|
556
|
+
raw: node
|
|
557
|
+
};
|
|
558
|
+
if (node.supplierId !== void 0) out.supplierId = String(node.supplierId);
|
|
559
|
+
if (node.status !== void 0) out.status = node.status;
|
|
560
|
+
if (node.description !== void 0) out.description = node.description;
|
|
561
|
+
if (typeof node.quantity === "number") out.quantity = node.quantity;
|
|
562
|
+
if (typeof node.listPrice === "number") out.listPrice = node.listPrice;
|
|
563
|
+
if (typeof node.salePrice === "number") out.salePrice = node.salePrice;
|
|
564
|
+
if (typeof node.vatRate === "number") out.vatRate = node.vatRate;
|
|
565
|
+
if (node.dimensionalWeight !== void 0 && node.dimensionalWeight !== null) {
|
|
566
|
+
out.dimensionalWeight = node.dimensionalWeight;
|
|
567
|
+
}
|
|
568
|
+
if (node.stockCode !== void 0) out.stockCode = node.stockCode;
|
|
569
|
+
if (node.origin !== void 0) out.origin = node.origin;
|
|
570
|
+
if (node.locationBasedDelivery !== void 0) {
|
|
571
|
+
out.locationBasedDelivery = node.locationBasedDelivery;
|
|
572
|
+
}
|
|
573
|
+
if (node.lotNumber !== void 0) out.lotNumber = node.lotNumber;
|
|
574
|
+
if (node.specialConsumptionTax !== void 0) {
|
|
575
|
+
out.specialConsumptionTax = node.specialConsumptionTax;
|
|
576
|
+
}
|
|
577
|
+
if (node.sgrPrice !== void 0) out.sgrPrice = node.sgrPrice;
|
|
578
|
+
const createdAt = toIso2(node.createDateTime);
|
|
579
|
+
if (createdAt) out.createdAt = createdAt;
|
|
580
|
+
const updatedAt = toIso2(node.lastUpdateDate);
|
|
581
|
+
if (updatedAt) out.updatedAt = updatedAt;
|
|
582
|
+
const priceChanged = toIso2(node.lastPriceChangeDate);
|
|
583
|
+
if (priceChanged) out.lastPriceChangedAt = priceChanged;
|
|
584
|
+
const stockChanged = toIso2(node.lastStockChangeDate);
|
|
585
|
+
if (stockChanged) out.lastStockChangedAt = stockChanged;
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
588
|
+
function normalizeBuyboxInfo(node) {
|
|
589
|
+
const out = {
|
|
590
|
+
barcode: node.barcode ?? "",
|
|
591
|
+
raw: node
|
|
592
|
+
};
|
|
593
|
+
if (typeof node.buyboxOrder === "number") out.buyboxOrder = node.buyboxOrder;
|
|
594
|
+
if (typeof node.buyboxPrice === "number") out.buyboxPrice = node.buyboxPrice;
|
|
595
|
+
if (typeof node.hasMultipleSeller === "boolean") {
|
|
596
|
+
out.hasMultipleSeller = node.hasMultipleSeller;
|
|
597
|
+
}
|
|
598
|
+
if (node.secondBuyboxPrice !== void 0) out.secondBuyboxPrice = node.secondBuyboxPrice;
|
|
599
|
+
if (node.thirdBuyboxPrice !== void 0) out.thirdBuyboxPrice = node.thirdBuyboxPrice;
|
|
600
|
+
return out;
|
|
601
|
+
}
|
|
602
|
+
function normalizeProductBase(data) {
|
|
603
|
+
const out = {
|
|
604
|
+
barcode: data.barcode ?? "",
|
|
605
|
+
approved: !!data.approved,
|
|
606
|
+
archived: !!data.archived,
|
|
607
|
+
raw: data
|
|
608
|
+
};
|
|
609
|
+
const approvedAt = toIso2(data.approvedDate);
|
|
610
|
+
if (approvedAt) out.approvedAt = approvedAt;
|
|
611
|
+
if (data.listingId !== void 0) out.listingId = data.listingId;
|
|
612
|
+
if (data.contentId !== void 0) out.contentId = String(data.contentId);
|
|
613
|
+
return out;
|
|
614
|
+
}
|
|
615
|
+
function normalizeBatchResult(data) {
|
|
616
|
+
const items = (data.items ?? []).map((item) => {
|
|
617
|
+
const result = {};
|
|
618
|
+
if (item.requestItem !== void 0) result.requestItem = item.requestItem;
|
|
619
|
+
if (item.status !== void 0) result.status = item.status;
|
|
620
|
+
if (item.failureReasons !== void 0) result.failureReasons = item.failureReasons;
|
|
621
|
+
return result;
|
|
622
|
+
});
|
|
623
|
+
const out = {
|
|
624
|
+
batchRequestId: typeof data.batchRequestId === "string" ? data.batchRequestId : "",
|
|
625
|
+
status: data.status ?? "PROCESSING",
|
|
626
|
+
items,
|
|
627
|
+
raw: data
|
|
628
|
+
};
|
|
629
|
+
if (typeof data.itemCount === "number") out.itemCount = data.itemCount;
|
|
630
|
+
if (typeof data.failedItemCount === "number") out.failedItemCount = data.failedItemCount;
|
|
631
|
+
const createdAt = toIso2(data.creationDate);
|
|
632
|
+
if (createdAt) out.createdAt = createdAt;
|
|
633
|
+
const lastModifiedAt = toIso2(data.lastModification);
|
|
634
|
+
if (lastModifiedAt) out.lastModifiedAt = lastModifiedAt;
|
|
635
|
+
if (data.sourceType !== void 0) out.sourceType = data.sourceType;
|
|
636
|
+
if (data.batchRequestType !== void 0) out.batchRequestType = data.batchRequestType;
|
|
637
|
+
if (data.notes !== void 0) out.notes = data.notes;
|
|
638
|
+
if (data.objectKey !== void 0) out.objectKey = data.objectKey;
|
|
639
|
+
if (data.storeFrontCode !== void 0) out.storeFrontCode = data.storeFrontCode;
|
|
640
|
+
return out;
|
|
641
|
+
}
|
|
642
|
+
var MAX_WRITE_ITEMS = 1e3;
|
|
643
|
+
var DELETE_RATE_PER_MIN = 100;
|
|
644
|
+
var ProductsResource = class {
|
|
645
|
+
constructor(transport, sellerId, options = {}) {
|
|
646
|
+
this.transport = transport;
|
|
647
|
+
this.sellerId = sellerId;
|
|
648
|
+
this.filterLimiter = options.filterLimiter ?? new core.TokenBucketRateLimiter({ capacity: 2e3, intervalMs: 6e4 });
|
|
649
|
+
this.batchLimiter = options.batchLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
650
|
+
this.buyboxLimiter = options.buyboxLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
651
|
+
this.writeLimiter = options.writeLimiter ?? new core.TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
652
|
+
this.deleteLimiter = options.deleteLimiter ?? new core.TokenBucketRateLimiter({ capacity: DELETE_RATE_PER_MIN, intervalMs: 6e4 });
|
|
653
|
+
}
|
|
654
|
+
transport;
|
|
655
|
+
sellerId;
|
|
656
|
+
filterLimiter;
|
|
657
|
+
batchLimiter;
|
|
658
|
+
buyboxLimiter;
|
|
659
|
+
writeLimiter;
|
|
660
|
+
deleteLimiter;
|
|
661
|
+
validateBarcodes(barcodes, methodLabel) {
|
|
662
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
663
|
+
throw new core.ValidationError({ message: `${methodLabel}: barcodes must not be empty` });
|
|
664
|
+
}
|
|
665
|
+
if (barcodes.length > MAX_WRITE_ITEMS) {
|
|
666
|
+
throw new core.ValidationError({
|
|
667
|
+
message: `${methodLabel}: max ${MAX_WRITE_ITEMS} barcodes per call (got ${barcodes.length})`
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async submitWrite(endpoint, items, methodLabel) {
|
|
672
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
673
|
+
throw new core.ValidationError({ message: `${methodLabel}: items must not be empty` });
|
|
674
|
+
}
|
|
675
|
+
if (items.length > MAX_WRITE_ITEMS) {
|
|
676
|
+
throw new core.ValidationError({
|
|
677
|
+
message: `${methodLabel}: max ${MAX_WRITE_ITEMS} items per call (got ${items.length})`
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const data = await this.transport.request({
|
|
681
|
+
method: "POST",
|
|
682
|
+
path: `/integration/product/sellers/${this.sellerId}${endpoint}`,
|
|
683
|
+
body: { items },
|
|
684
|
+
rateLimiter: this.writeLimiter
|
|
685
|
+
});
|
|
686
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* List approved products. Use `paginate()` from `@lonca/core` to iterate
|
|
690
|
+
* lazily across pages.
|
|
691
|
+
*
|
|
692
|
+
* Trendyol exposes both page-based and `nextPageToken`-based pagination
|
|
693
|
+
* (the latter required when the dataset exceeds 10,000 items). The SDK
|
|
694
|
+
* picks the right strategy automatically — pass our opaque `cursor` from
|
|
695
|
+
* the previous response and we forward it as `nextPageToken`.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```ts
|
|
699
|
+
* import { paginate } from '@lonca/core';
|
|
700
|
+
* for await (const product of paginate((p) => client.products.list(p))) {
|
|
701
|
+
* for (const variant of product.variants) {
|
|
702
|
+
* console.log(variant.barcode, product.title);
|
|
703
|
+
* }
|
|
704
|
+
* }
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
async list(params = {}) {
|
|
708
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE3, MAX_PAGE_SIZE2);
|
|
709
|
+
const query = { size };
|
|
710
|
+
if (params.cursor) {
|
|
711
|
+
query.nextPageToken = params.cursor;
|
|
712
|
+
} else {
|
|
713
|
+
query.page = 0;
|
|
714
|
+
}
|
|
715
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
716
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
717
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
718
|
+
const data = await this.transport.request({
|
|
719
|
+
method: "GET",
|
|
720
|
+
path: `/integration/product/sellers/${this.sellerId}/products/approved`,
|
|
721
|
+
query,
|
|
722
|
+
rateLimiter: this.filterLimiter
|
|
723
|
+
});
|
|
724
|
+
const items = (data.content ?? []).map(normalizeProduct);
|
|
725
|
+
const page = { items };
|
|
726
|
+
if (data.nextPageToken) {
|
|
727
|
+
page.nextCursor = data.nextPageToken;
|
|
728
|
+
}
|
|
729
|
+
return page;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Poll a batch request returned by an async write (e.g. `createProducts`,
|
|
733
|
+
* `updatePriceAndInventory`).
|
|
734
|
+
*
|
|
735
|
+
* Trendyol retains batch results for **4 hours** after the originating
|
|
736
|
+
* request — poll within that window.
|
|
737
|
+
*
|
|
738
|
+
* @param batchRequestId The opaque ID returned by the originating call.
|
|
739
|
+
*/
|
|
740
|
+
async getBatchStatus(batchRequestId) {
|
|
741
|
+
const id = encodeURIComponent(batchRequestId);
|
|
742
|
+
const data = await this.transport.request({
|
|
743
|
+
method: "GET",
|
|
744
|
+
path: `/integration/product/sellers/${this.sellerId}/products/batch-requests/${id}`,
|
|
745
|
+
rateLimiter: this.batchLimiter
|
|
746
|
+
});
|
|
747
|
+
return normalizeBatchResult(data);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* List **unapproved** (draft / rejected / pending-review) products.
|
|
751
|
+
*
|
|
752
|
+
* Wire shape is intentionally flatter than the approved-product shape:
|
|
753
|
+
* each barcode is one top-level item with `barcode`, `quantity`, `salePrice`
|
|
754
|
+
* etc. at the root. Rejected drafts carry `rejectReasonDetails` so you can
|
|
755
|
+
* surface why Trendyol's content team turned them down.
|
|
756
|
+
*
|
|
757
|
+
* Pagination follows the same convention as `list()`: `cursor` from the
|
|
758
|
+
* previous response forwards as `nextPageToken`.
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* ```ts
|
|
762
|
+
* const page = await client.products.listUnapproved({ limit: 50 });
|
|
763
|
+
* for (const draft of page.items) {
|
|
764
|
+
* if (draft.status === 'rejected') {
|
|
765
|
+
* console.warn(draft.barcode, draft.rejectReasonDetails);
|
|
766
|
+
* }
|
|
767
|
+
* }
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
async listUnapproved(params = {}) {
|
|
771
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE3, MAX_PAGE_SIZE2);
|
|
772
|
+
const query = { size };
|
|
773
|
+
if (params.cursor) {
|
|
774
|
+
query.nextPageToken = params.cursor;
|
|
775
|
+
} else {
|
|
776
|
+
query.page = 0;
|
|
777
|
+
}
|
|
778
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
779
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
780
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
781
|
+
if (params.dateQueryType) query.dateQueryType = params.dateQueryType;
|
|
782
|
+
if (params.supplierId !== void 0) query.supplierId = params.supplierId;
|
|
783
|
+
const data = await this.transport.request({
|
|
784
|
+
method: "GET",
|
|
785
|
+
path: `/integration/product/sellers/${this.sellerId}/products/unapproved`,
|
|
786
|
+
query,
|
|
787
|
+
rateLimiter: this.filterLimiter
|
|
788
|
+
});
|
|
789
|
+
const items = (data.content ?? []).map(normalizeUnapproved);
|
|
790
|
+
const page = { items };
|
|
791
|
+
if (data.nextPageToken) {
|
|
792
|
+
page.nextCursor = data.nextPageToken;
|
|
793
|
+
}
|
|
794
|
+
return page;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Fetch the basic lifecycle status of a single product by barcode.
|
|
798
|
+
*
|
|
799
|
+
* Cheap and useful as a polling primitive after `createProducts`: poll
|
|
800
|
+
* this endpoint until `approved` flips to `true` (or use
|
|
801
|
+
* `client.products.getBatchStatus()` to track the originating batch).
|
|
802
|
+
*
|
|
803
|
+
* @param barcode The product barcode to look up.
|
|
804
|
+
*/
|
|
805
|
+
async getBase(barcode) {
|
|
806
|
+
const code = encodeURIComponent(barcode);
|
|
807
|
+
const data = await this.transport.request({
|
|
808
|
+
method: "GET",
|
|
809
|
+
path: `/integration/product/sellers/${this.sellerId}/product/${code}`,
|
|
810
|
+
rateLimiter: this.filterLimiter
|
|
811
|
+
});
|
|
812
|
+
return normalizeProductBase(data);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Fetch buybox information for up to 10 barcodes in one call.
|
|
816
|
+
*
|
|
817
|
+
* Returns rank (`buyboxOrder === 1` means you hold the buybox), the
|
|
818
|
+
* current buybox price, and — beyond the spec — the second and third
|
|
819
|
+
* competing prices when other sellers are present.
|
|
820
|
+
*
|
|
821
|
+
* @param barcodes 1–10 product barcodes.
|
|
822
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 10.
|
|
823
|
+
*/
|
|
824
|
+
async getBuyboxInfo(barcodes) {
|
|
825
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
826
|
+
throw new core.ValidationError({
|
|
827
|
+
message: "getBuyboxInfo: barcodes must be a non-empty array"
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
if (barcodes.length > MAX_BUYBOX_BARCODES) {
|
|
831
|
+
throw new core.ValidationError({
|
|
832
|
+
message: `getBuyboxInfo: max ${MAX_BUYBOX_BARCODES} barcodes per call (got ${barcodes.length})`
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
const data = await this.transport.request({
|
|
836
|
+
method: "POST",
|
|
837
|
+
path: `/integration/product/sellers/${this.sellerId}/products/buybox-information`,
|
|
838
|
+
body: { barcodes },
|
|
839
|
+
rateLimiter: this.buyboxLimiter
|
|
840
|
+
});
|
|
841
|
+
const list = data.buyboxInfo ?? data.content ?? [];
|
|
842
|
+
return list.map(normalizeBuyboxInfo);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Create products (V2). Async batch — returns a `batchRequestId` you can
|
|
846
|
+
* poll with `getBatchStatus`. Max 1000 items per call.
|
|
847
|
+
*
|
|
848
|
+
* Trendyol requires the full V2 attribute payload — fetch via
|
|
849
|
+
* `categories.getAttributes` (and `categories.getAttributeValues` for
|
|
850
|
+
* values when `allowCustom === false`). Shipment / returning warehouse
|
|
851
|
+
* IDs come from `suppliers.getAddresses`.
|
|
852
|
+
*
|
|
853
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
854
|
+
*/
|
|
855
|
+
async create(items) {
|
|
856
|
+
return this.submitWrite("/v2/products", items, "createProducts");
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Update **content** of approved products (title, description, images,
|
|
860
|
+
* attributes). Identified by `contentId`. Partial update is supported
|
|
861
|
+
* except for attributes — if you update ANY attribute, send ALL of them.
|
|
862
|
+
*
|
|
863
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
864
|
+
*/
|
|
865
|
+
async updateContent(items) {
|
|
866
|
+
return this.submitWrite("/products/content-bulk-update", items, "updateContent");
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Update **variant** fields of approved products (stockCode, vatRate,
|
|
870
|
+
* dimensionalWeight, warehouse IDs, location-based delivery, lot). Identified
|
|
871
|
+
* by `barcode`. The barcode itself cannot be changed via this endpoint.
|
|
872
|
+
*
|
|
873
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
874
|
+
*/
|
|
875
|
+
async updateVariants(items) {
|
|
876
|
+
return this.submitWrite("/products/variant-bulk-update", items, "updateVariants");
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Update **unapproved** (draft) products. Identified by `barcode`. All
|
|
880
|
+
* other fields are optional partial updates. Use this to fix drafts that
|
|
881
|
+
* Trendyol rejected — `client.products.listUnapproved` surfaces the
|
|
882
|
+
* `rejectReasonDetails` you need to act on.
|
|
883
|
+
*
|
|
884
|
+
* **Gotcha (verified live STAGE 2026-05-25):** Trendyol's V2 spec claims
|
|
885
|
+
* only `barcode` is required, but the endpoint returns HTTP 500
|
|
886
|
+
* (`TrendyolSystemException` / `TypeError`) when too many optional fields
|
|
887
|
+
* are omitted. In practice, send at least `title`, `description`,
|
|
888
|
+
* `productMainId`, `brandId`, `categoryId`, `stockCode`,
|
|
889
|
+
* `dimensionalWeight`, `vatRate`, `images[]`, and `attributes[]` (an
|
|
890
|
+
* empty array is OK for the latter). The SDK forwards your payload
|
|
891
|
+
* as-is; trim fields only if you have verified the server accepts it.
|
|
892
|
+
*
|
|
893
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
894
|
+
*/
|
|
895
|
+
async updateUnapproved(items) {
|
|
896
|
+
return this.submitWrite("/products/unapproved-bulk-update", items, "updateUnapproved");
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Update product **delivery information** (deliveryDuration,
|
|
900
|
+
* fastDeliveryType). Identified by `barcode`.
|
|
901
|
+
*
|
|
902
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
903
|
+
*/
|
|
904
|
+
async updateDeliveryInfo(items) {
|
|
905
|
+
return this.submitWrite("/products/delivery-info-bulk-update", items, "updateDeliveryInfo");
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Delete products by barcode. Trendyol allows deletion of unapproved
|
|
909
|
+
* products and approved products that have been archived for more than a
|
|
910
|
+
* day (and have not been sales-stopped by Trendyol).
|
|
911
|
+
*
|
|
912
|
+
* Async batch — returns `{ batchRequestId }` to poll via `getBatchStatus`.
|
|
913
|
+
* Separately rate-limited at **100 req/min** (much tighter than create/update).
|
|
914
|
+
*
|
|
915
|
+
* @param barcodes 1–1000 barcodes.
|
|
916
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
917
|
+
*/
|
|
918
|
+
async delete(barcodes) {
|
|
919
|
+
this.validateBarcodes(barcodes, "delete");
|
|
920
|
+
const data = await this.transport.request({
|
|
921
|
+
method: "DELETE",
|
|
922
|
+
path: `/integration/product/sellers/${this.sellerId}/products`,
|
|
923
|
+
body: { items: barcodes.map((barcode) => ({ barcode })) },
|
|
924
|
+
rateLimiter: this.deleteLimiter
|
|
925
|
+
});
|
|
926
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Archive products by barcode (Trendyol's `archived=true` state).
|
|
930
|
+
* Archived products are not visible to customers; pair with `delete`
|
|
931
|
+
* after the 24-hour archive cool-down to remove them entirely.
|
|
932
|
+
*
|
|
933
|
+
* Async batch — returns `{ batchRequestId }`.
|
|
934
|
+
*
|
|
935
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
936
|
+
*/
|
|
937
|
+
async archive(barcodes) {
|
|
938
|
+
return this.setArchivedState(barcodes, true, "archive");
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Unarchive products by barcode (Trendyol's `archived=false` state).
|
|
942
|
+
* Restores visibility for previously-archived products.
|
|
943
|
+
*
|
|
944
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
945
|
+
*/
|
|
946
|
+
async unarchive(barcodes) {
|
|
947
|
+
return this.setArchivedState(barcodes, false, "unarchive");
|
|
948
|
+
}
|
|
949
|
+
async setArchivedState(barcodes, archived, methodLabel) {
|
|
950
|
+
this.validateBarcodes(barcodes, methodLabel);
|
|
951
|
+
const data = await this.transport.request({
|
|
952
|
+
method: "PUT",
|
|
953
|
+
path: `/integration/product/sellers/${this.sellerId}/products/archive-state`,
|
|
954
|
+
body: { items: barcodes.map((barcode) => ({ barcode, archived })) },
|
|
955
|
+
rateLimiter: this.writeLimiter
|
|
956
|
+
});
|
|
957
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Unlock products whose sale was paused by Trendyol due to pricing
|
|
961
|
+
* issues (under/over-pricing, critical price error, supplier issues).
|
|
962
|
+
* Restores selling status for the listed barcodes.
|
|
963
|
+
*
|
|
964
|
+
* Async batch — returns `{ batchRequestId }`.
|
|
965
|
+
*
|
|
966
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
967
|
+
*/
|
|
968
|
+
async unlock(barcodes) {
|
|
969
|
+
this.validateBarcodes(barcodes, "unlock");
|
|
970
|
+
const data = await this.transport.request({
|
|
971
|
+
method: "PUT",
|
|
972
|
+
path: `/integration/product/sellers/${this.sellerId}/products/unlock`,
|
|
973
|
+
body: { items: barcodes.map((barcode) => ({ barcode })) },
|
|
974
|
+
rateLimiter: this.writeLimiter
|
|
975
|
+
});
|
|
976
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
980
|
+
"SHIPMENT",
|
|
981
|
+
"RETURNING",
|
|
982
|
+
"INVOICE",
|
|
983
|
+
"WAREHOUSE"
|
|
984
|
+
]);
|
|
985
|
+
function normalizeAddressType(raw) {
|
|
986
|
+
if (raw && VALID_TYPES.has(raw)) {
|
|
987
|
+
return raw;
|
|
988
|
+
}
|
|
989
|
+
const stripped = raw?.replace(/_ADDRESS$/, "");
|
|
990
|
+
if (stripped && VALID_TYPES.has(stripped)) {
|
|
991
|
+
return stripped;
|
|
992
|
+
}
|
|
993
|
+
return "SHIPMENT";
|
|
994
|
+
}
|
|
995
|
+
function normalizeAddress2(node) {
|
|
996
|
+
return {
|
|
997
|
+
id: String(node.id),
|
|
998
|
+
name: node.name,
|
|
999
|
+
addressType: normalizeAddressType(node.addressType),
|
|
1000
|
+
isShipmentAddress: node.isShipmentAddress ?? false,
|
|
1001
|
+
isReturningAddress: node.isReturningAddress ?? false,
|
|
1002
|
+
isInvoiceAddress: node.isInvoiceAddress ?? false,
|
|
1003
|
+
isDefault: node.isDefault ?? false,
|
|
1004
|
+
address: node.address,
|
|
1005
|
+
city: node.city,
|
|
1006
|
+
district: node.district,
|
|
1007
|
+
postCode: node.postCode,
|
|
1008
|
+
fullName: node.fullName
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1012
|
+
var SuppliersResource = class {
|
|
1013
|
+
constructor(transport, sellerId, options = {}) {
|
|
1014
|
+
this.transport = transport;
|
|
1015
|
+
this.sellerId = sellerId;
|
|
1016
|
+
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1017
|
+
this.limiter = new core.TokenBucketRateLimiter({
|
|
1018
|
+
capacity: 1,
|
|
1019
|
+
intervalMs: DEFAULT_CACHE_TTL_MS
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
transport;
|
|
1023
|
+
sellerId;
|
|
1024
|
+
cache = null;
|
|
1025
|
+
cacheTtlMs;
|
|
1026
|
+
limiter;
|
|
1027
|
+
inflight = null;
|
|
1028
|
+
/**
|
|
1029
|
+
* List the seller's registered addresses (shipment, returning, invoice, warehouse).
|
|
1030
|
+
*
|
|
1031
|
+
* Returns the cached value if it is still fresh. Concurrent calls share a
|
|
1032
|
+
* single in-flight request.
|
|
1033
|
+
*/
|
|
1034
|
+
async getAddresses(options = {}) {
|
|
1035
|
+
if (!options.forceRefresh && this.cache && this.cache.expiresAt > Date.now()) {
|
|
1036
|
+
return this.cache.value;
|
|
1037
|
+
}
|
|
1038
|
+
if (this.inflight) return this.inflight;
|
|
1039
|
+
this.inflight = this.fetchFresh().finally(() => {
|
|
1040
|
+
this.inflight = null;
|
|
1041
|
+
});
|
|
1042
|
+
return this.inflight;
|
|
1043
|
+
}
|
|
1044
|
+
/** Drop the cache; the next `getAddresses()` call hits the API. */
|
|
1045
|
+
invalidateCache() {
|
|
1046
|
+
this.cache = null;
|
|
1047
|
+
}
|
|
1048
|
+
async fetchFresh() {
|
|
1049
|
+
const data = await this.transport.request({
|
|
1050
|
+
method: "GET",
|
|
1051
|
+
path: `/integration/sellers/${this.sellerId}/addresses`,
|
|
1052
|
+
rateLimiter: this.limiter
|
|
1053
|
+
});
|
|
1054
|
+
const items = data.supplierAddresses.map(normalizeAddress2);
|
|
1055
|
+
this.cache = { value: items, expiresAt: Date.now() + this.cacheTtlMs };
|
|
1056
|
+
return items;
|
|
1057
|
+
}
|
|
39
1058
|
};
|
|
40
1059
|
|
|
41
1060
|
// src/auth.ts
|
|
@@ -239,11 +1258,21 @@ function createTrendyolClient(opts) {
|
|
|
239
1258
|
timeoutMs: opts.timeoutMs
|
|
240
1259
|
});
|
|
241
1260
|
return {
|
|
242
|
-
brands: new BrandsResource(transport)
|
|
1261
|
+
brands: new BrandsResource(transport),
|
|
1262
|
+
categories: new CategoriesResource(transport, void 0, opts.sellerId),
|
|
1263
|
+
suppliers: new SuppliersResource(transport, opts.sellerId),
|
|
1264
|
+
products: new ProductsResource(transport, opts.sellerId),
|
|
1265
|
+
inventory: new InventoryResource(transport, opts.sellerId),
|
|
1266
|
+
orders: new OrdersResource(transport, opts.sellerId)
|
|
243
1267
|
};
|
|
244
1268
|
}
|
|
245
1269
|
|
|
246
1270
|
exports.BrandsResource = BrandsResource;
|
|
1271
|
+
exports.CategoriesResource = CategoriesResource;
|
|
1272
|
+
exports.InventoryResource = InventoryResource;
|
|
1273
|
+
exports.OrdersResource = OrdersResource;
|
|
1274
|
+
exports.ProductsResource = ProductsResource;
|
|
1275
|
+
exports.SuppliersResource = SuppliersResource;
|
|
247
1276
|
exports.createTrendyolClient = createTrendyolClient;
|
|
248
1277
|
//# sourceMappingURL=index.cjs.map
|
|
249
1278
|
//# sourceMappingURL=index.cjs.map
|