@lonca/trendyol 0.2.0 → 0.4.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 +172 -14
- package/dist/index.cjs +789 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +791 -9
- package/dist/index.d.ts +791 -9
- package/dist/index.js +789 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,6 +34,29 @@ var BrandsResource = class {
|
|
|
34
34
|
const nextCursor = page + 1 < data.totalPages ? String(page + 1) : void 0;
|
|
35
35
|
return nextCursor !== void 0 ? { items, nextCursor } : { items };
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Search brands by name. Useful when you need a brand's numeric ID for
|
|
39
|
+
* `createProducts` and don't want to page through the full `list()`
|
|
40
|
+
* (1000 brands per page).
|
|
41
|
+
*
|
|
42
|
+
* **Discovery-first wire fact (verified STAGE 2026-05-25):** Trendyol's
|
|
43
|
+
* doc claims this is a case-sensitive *exact* match, but live behaviour
|
|
44
|
+
* is **substring + case-insensitive** — `search('Trendyol')` returns
|
|
45
|
+
* 17 hits including `TRENDYOLMILLA`, `trendyol vavist`, `Trendyol Üyelik`.
|
|
46
|
+
* Plan for ranking your results client-side if you need an exact match.
|
|
47
|
+
* The endpoint returns an empty array when nothing matches (no 404).
|
|
48
|
+
*
|
|
49
|
+
* @param name The brand name to search for.
|
|
50
|
+
*/
|
|
51
|
+
async search(name) {
|
|
52
|
+
const data = await this.transport.request({
|
|
53
|
+
method: "GET",
|
|
54
|
+
path: "/integration/product/brands/by-name",
|
|
55
|
+
query: { name },
|
|
56
|
+
rateLimiter: this.limiter
|
|
57
|
+
});
|
|
58
|
+
return (data ?? []).map((b) => ({ id: String(b.id), name: b.name }));
|
|
59
|
+
}
|
|
37
60
|
};
|
|
38
61
|
function normalizeCategory(node) {
|
|
39
62
|
return {
|
|
@@ -63,12 +86,16 @@ function normalizeAttribute(node) {
|
|
|
63
86
|
}
|
|
64
87
|
return out;
|
|
65
88
|
}
|
|
89
|
+
var MAX_ATTR_VALUES_PAGE_SIZE = 1e3;
|
|
90
|
+
var DEFAULT_ATTR_VALUES_PAGE_SIZE = 100;
|
|
66
91
|
var CategoriesResource = class {
|
|
67
|
-
constructor(transport, limiter) {
|
|
92
|
+
constructor(transport, limiter, sellerId) {
|
|
68
93
|
this.transport = transport;
|
|
94
|
+
this.sellerId = sellerId;
|
|
69
95
|
this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 50, intervalMs: 6e4 });
|
|
70
96
|
}
|
|
71
97
|
transport;
|
|
98
|
+
sellerId;
|
|
72
99
|
limiter;
|
|
73
100
|
/**
|
|
74
101
|
* Fetch the full Trendyol category tree.
|
|
@@ -110,6 +137,87 @@ var CategoriesResource = class {
|
|
|
110
137
|
}
|
|
111
138
|
return list.map(normalizeAttribute);
|
|
112
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
|
+
* @throws Error when the client was created without a `sellerId` and this
|
|
195
|
+
* method is called directly (use `createTrendyolClient` to wire it).
|
|
196
|
+
*/
|
|
197
|
+
async getByBarcodes(barcodes) {
|
|
198
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
199
|
+
throw new ValidationError({ message: "getByBarcodes: barcodes must not be empty" });
|
|
200
|
+
}
|
|
201
|
+
if (this.sellerId === void 0) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
"CategoriesResource.getByBarcodes requires a sellerId; instantiate via createTrendyolClient"
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const data = await this.transport.request({
|
|
207
|
+
method: "POST",
|
|
208
|
+
path: `/integration/ecgw/v1/${this.sellerId}/lookup/product-categories/by-barcodes`,
|
|
209
|
+
body: { barcodes },
|
|
210
|
+
rateLimiter: this.limiter
|
|
211
|
+
});
|
|
212
|
+
const matches = Object.entries(data.barcodeCategories ?? {}).map(([barcode, cat]) => ({
|
|
213
|
+
barcode,
|
|
214
|
+
category: {
|
|
215
|
+
id: cat.id !== void 0 ? String(cat.id) : "",
|
|
216
|
+
name: cat.displayName ?? ""
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
return { matches, notFound: data.notFound ?? [] };
|
|
220
|
+
}
|
|
113
221
|
};
|
|
114
222
|
function extractAttributeList(data) {
|
|
115
223
|
if (Array.isArray(data)) return data;
|
|
@@ -249,8 +357,9 @@ function normalizeCustomer(node) {
|
|
|
249
357
|
return out;
|
|
250
358
|
}
|
|
251
359
|
function normalizePackage(node) {
|
|
360
|
+
const pkgId = node.shipmentPackageId ?? node.id;
|
|
252
361
|
const out = {
|
|
253
|
-
id:
|
|
362
|
+
id: pkgId !== void 0 ? String(pkgId) : "",
|
|
254
363
|
orderNumber: node.orderNumber ?? "",
|
|
255
364
|
status: node.status ?? "",
|
|
256
365
|
customer: normalizeCustomer(node),
|
|
@@ -358,9 +467,346 @@ var OrdersResource = class {
|
|
|
358
467
|
}
|
|
359
468
|
return result;
|
|
360
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* Push a shipment-package status update.
|
|
472
|
+
*
|
|
473
|
+
* Trendyol restricts the seller-side push to two transitions:
|
|
474
|
+
* - `Picking` — order picked up from the shelf / being prepared
|
|
475
|
+
* - `Invoiced` — invoice issued, ready for cargo handoff
|
|
476
|
+
*
|
|
477
|
+
* Other transitions (`Shipped`, `Delivered`, etc.) are driven by Trendyol
|
|
478
|
+
* or the cargo provider — call `processAlternativeDelivery` or
|
|
479
|
+
* `manualDeliverByPackageId` if you ship outside Trendyol's cargo
|
|
480
|
+
* network.
|
|
481
|
+
*
|
|
482
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
483
|
+
*/
|
|
484
|
+
async updatePackageStatus(packageId, input) {
|
|
485
|
+
await this.transport.request({
|
|
486
|
+
method: "PUT",
|
|
487
|
+
path: this.packagePath(packageId),
|
|
488
|
+
body: input,
|
|
489
|
+
rateLimiter: this.limiter
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Notify Trendyol that one or more line items cannot be supplied
|
|
494
|
+
* ("Tedarik Edememe Bildirimi"). Marks the listed line IDs as
|
|
495
|
+
* `UnSupplied`. Trendyol cancels those quantities and notifies the
|
|
496
|
+
* customer.
|
|
497
|
+
*
|
|
498
|
+
* `reasonId` is a numeric code Trendyol publishes separately — consult
|
|
499
|
+
* the seller panel or the "Tedarik Edememe" docs for current values.
|
|
500
|
+
*
|
|
501
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
502
|
+
*/
|
|
503
|
+
async cancelPackageItem(packageId, input) {
|
|
504
|
+
if (!Array.isArray(input?.lines) || input.lines.length === 0) {
|
|
505
|
+
throw new ValidationError({ message: "cancelPackageItem: lines must not be empty" });
|
|
506
|
+
}
|
|
507
|
+
await this.transport.request({
|
|
508
|
+
method: "PUT",
|
|
509
|
+
path: `${this.packagePath(packageId)}/items/unsupplied`,
|
|
510
|
+
body: input,
|
|
511
|
+
rateLimiter: this.limiter
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Extend the agreed delivery date for a shipment package by 1, 2, or 3 days.
|
|
516
|
+
* Trendyol enforces the [1, 3] range server-side; the SDK validates client-side
|
|
517
|
+
* to fail fast.
|
|
518
|
+
*
|
|
519
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
520
|
+
*/
|
|
521
|
+
async extendDeliveryDate(packageId, extendedDayCount) {
|
|
522
|
+
if (![1, 2, 3].includes(extendedDayCount)) {
|
|
523
|
+
throw new ValidationError({
|
|
524
|
+
message: `extendDeliveryDate: extendedDayCount must be 1, 2, or 3 (got ${extendedDayCount})`
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
await this.transport.request({
|
|
528
|
+
method: "PUT",
|
|
529
|
+
path: `${this.packagePath(packageId)}/extended-agreed-delivery-date`,
|
|
530
|
+
body: { extendedDayCount },
|
|
531
|
+
rateLimiter: this.limiter
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Notify Trendyol of an alternative delivery channel — used when the
|
|
536
|
+
* seller is shipping via a non-Trendyol cargo provider. Trendyol then
|
|
537
|
+
* either SMSes the customer the tracking link (when `isPhoneNumber` is
|
|
538
|
+
* `true`) or stores the tracking URL on the package directly.
|
|
539
|
+
*
|
|
540
|
+
* Returns void; Trendyol responds with 200 + empty body on success.
|
|
541
|
+
*/
|
|
542
|
+
async processAlternativeDelivery(packageId, input) {
|
|
543
|
+
await this.transport.request({
|
|
544
|
+
method: "PUT",
|
|
545
|
+
path: `${this.packagePath(packageId)}/alternative-delivery`,
|
|
546
|
+
body: input,
|
|
547
|
+
rateLimiter: this.limiter
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Split a shipment package by moving a set of line IDs into a new
|
|
552
|
+
* package. The original package keeps the remaining lines.
|
|
553
|
+
*
|
|
554
|
+
* @param packageId The package to split.
|
|
555
|
+
* @param orderLineIds Line IDs to move into the new package (1+).
|
|
556
|
+
* @throws {ValidationError} when `orderLineIds` is empty.
|
|
557
|
+
*/
|
|
558
|
+
async splitPackage(packageId, orderLineIds) {
|
|
559
|
+
if (!Array.isArray(orderLineIds) || orderLineIds.length === 0) {
|
|
560
|
+
throw new ValidationError({ message: "splitPackage: orderLineIds must not be empty" });
|
|
561
|
+
}
|
|
562
|
+
await this.transport.request({
|
|
563
|
+
method: "POST",
|
|
564
|
+
path: `${this.packagePath(packageId)}/split`,
|
|
565
|
+
body: { orderLineIds },
|
|
566
|
+
rateLimiter: this.limiter
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Split a shipment package by quantity. Each `QuantitySplit` entry
|
|
571
|
+
* carves a single line into multiple packages — e.g. `{ orderLineId: 100,
|
|
572
|
+
* quantities: [2, 2, 1] }` splits 5 units of line 100 into three packages
|
|
573
|
+
* of 2 + 2 + 1.
|
|
574
|
+
*
|
|
575
|
+
* @throws {ValidationError} when `quantitySplit` is empty.
|
|
576
|
+
*/
|
|
577
|
+
async splitPackageByQuantity(packageId, quantitySplit) {
|
|
578
|
+
if (!Array.isArray(quantitySplit) || quantitySplit.length === 0) {
|
|
579
|
+
throw new ValidationError({
|
|
580
|
+
message: "splitPackageByQuantity: quantitySplit must not be empty"
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
await this.transport.request({
|
|
584
|
+
method: "POST",
|
|
585
|
+
path: `${this.packagePath(packageId)}/quantity-split`,
|
|
586
|
+
body: { quantitySplit },
|
|
587
|
+
rateLimiter: this.limiter
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Split a shipment package into multiple new packages by grouping line
|
|
592
|
+
* IDs. Each `SplitGroup` becomes one new package containing the listed
|
|
593
|
+
* line IDs.
|
|
594
|
+
*
|
|
595
|
+
* @throws {ValidationError} when `splitGroups` is empty.
|
|
596
|
+
*/
|
|
597
|
+
async multiSplitPackage(packageId, splitGroups) {
|
|
598
|
+
if (!Array.isArray(splitGroups) || splitGroups.length === 0) {
|
|
599
|
+
throw new ValidationError({ message: "multiSplitPackage: splitGroups must not be empty" });
|
|
600
|
+
}
|
|
601
|
+
await this.transport.request({
|
|
602
|
+
method: "POST",
|
|
603
|
+
path: `${this.packagePath(packageId)}/multi-split`,
|
|
604
|
+
body: { splitGroups },
|
|
605
|
+
rateLimiter: this.limiter
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Split a shipment package into multiple new packages, each containing a
|
|
610
|
+
* mix of line items at specific quantities. This is the most expressive
|
|
611
|
+
* split — use it when you need fine-grained control over which line IDs
|
|
612
|
+
* and how many of each end up in each new package.
|
|
613
|
+
*
|
|
614
|
+
* @throws {ValidationError} when `splitPackages` is empty.
|
|
615
|
+
*/
|
|
616
|
+
async splitMultiPackagesByQuantity(packageId, splitPackages) {
|
|
617
|
+
if (!Array.isArray(splitPackages) || splitPackages.length === 0) {
|
|
618
|
+
throw new ValidationError({
|
|
619
|
+
message: "splitMultiPackagesByQuantity: splitPackages must not be empty"
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
await this.transport.request({
|
|
623
|
+
method: "POST",
|
|
624
|
+
path: `${this.packagePath(packageId)}/split-packages`,
|
|
625
|
+
body: { splitPackages },
|
|
626
|
+
rateLimiter: this.limiter
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Change the cargo provider on an existing shipment package. Use one of
|
|
631
|
+
* Trendyol's documented marketplace cargo codes (`'YKMP'`, `'ARASMP'`,
|
|
632
|
+
* `'SURATMP'`, etc.) — see `TrendyolCargoProvider` for the full list.
|
|
633
|
+
*/
|
|
634
|
+
async changeCargoProvider(packageId, cargoProvider) {
|
|
635
|
+
await this.transport.request({
|
|
636
|
+
method: "PUT",
|
|
637
|
+
path: `${this.packagePath(packageId)}/cargo-providers`,
|
|
638
|
+
body: { cargoProvider },
|
|
639
|
+
rateLimiter: this.limiter
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Mark a shipment package as manually delivered via its package ID.
|
|
644
|
+
* Used when the seller delivered the order outside Trendyol's cargo
|
|
645
|
+
* network and needs to flip the package to `Delivered` after handover.
|
|
646
|
+
*
|
|
647
|
+
* No request body; Trendyol responds with 200 + empty body on success.
|
|
648
|
+
*/
|
|
649
|
+
async manualDeliverByPackageId(packageId) {
|
|
650
|
+
await this.transport.request({
|
|
651
|
+
method: "PUT",
|
|
652
|
+
path: `${this.packagePath(packageId)}/manual-invoice-delivery`,
|
|
653
|
+
rateLimiter: this.limiter
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Manual-deliver variant that takes the cargo tracking number instead
|
|
658
|
+
* of the package ID. Useful when you only have the tracking number on
|
|
659
|
+
* hand (e.g., from a cargo provider webhook).
|
|
660
|
+
*
|
|
661
|
+
* Note the path structure: tracking number sits at a sibling location,
|
|
662
|
+
* not under `/shipment-packages/{id}/...`.
|
|
663
|
+
*/
|
|
664
|
+
async manualDeliverByTrackingNumber(cargoTrackingNumber) {
|
|
665
|
+
await this.transport.request({
|
|
666
|
+
method: "PUT",
|
|
667
|
+
path: `/integration/order/sellers/${this.sellerId}/shipment-packages/manual-invoice-delivery-by-tracking-number/${encodeURIComponent(String(cargoTrackingNumber))}`,
|
|
668
|
+
rateLimiter: this.limiter
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Mark a package as delivered through an authorized service ("yetkili
|
|
673
|
+
* servis"). For appliance / installation-required products that are
|
|
674
|
+
* delivered + installed by a third-party service partner.
|
|
675
|
+
*/
|
|
676
|
+
async markDeliveredByService(packageId) {
|
|
677
|
+
await this.transport.request({
|
|
678
|
+
method: "PUT",
|
|
679
|
+
path: `${this.packagePath(packageId)}/delivered-by-service`,
|
|
680
|
+
rateLimiter: this.limiter
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Update box / packaging metadata on a shipment package (desi value
|
|
685
|
+
* and/or number of boxes). Either field can be sent alone.
|
|
686
|
+
*/
|
|
687
|
+
async updateBoxInfo(packageId, input) {
|
|
688
|
+
await this.transport.request({
|
|
689
|
+
method: "PUT",
|
|
690
|
+
path: `${this.packagePath(packageId)}/box-info`,
|
|
691
|
+
body: input,
|
|
692
|
+
rateLimiter: this.limiter
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Update labor costs for one or more order lines.
|
|
697
|
+
*
|
|
698
|
+
* **Wire note:** Trendyol's request body is a raw array (no envelope),
|
|
699
|
+
* not `{ items: [...] }`. The SDK forwards `items` verbatim.
|
|
700
|
+
*
|
|
701
|
+
* @throws {ValidationError} when `items` is empty.
|
|
702
|
+
*/
|
|
703
|
+
async updateLaborCosts(packageId, items) {
|
|
704
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
705
|
+
throw new ValidationError({ message: "updateLaborCosts: items must not be empty" });
|
|
706
|
+
}
|
|
707
|
+
await this.transport.request({
|
|
708
|
+
method: "PUT",
|
|
709
|
+
path: `${this.packagePath(packageId)}/labor-costs`,
|
|
710
|
+
body: items,
|
|
711
|
+
rateLimiter: this.limiter
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Reassign a shipment package to a different warehouse. `warehouseId`
|
|
716
|
+
* comes from `client.suppliers.getAddresses()` (filter by `isShipmentAddress`).
|
|
717
|
+
*/
|
|
718
|
+
async updateWarehouse(packageId, warehouseId) {
|
|
719
|
+
await this.transport.request({
|
|
720
|
+
method: "PUT",
|
|
721
|
+
path: `${this.packagePath(packageId)}/warehouse`,
|
|
722
|
+
body: { warehouseId },
|
|
723
|
+
rateLimiter: this.limiter
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Stream variant of `list()`. Trendyol's `getShipmentPackagesStream`
|
|
728
|
+
* returns the same `ShipmentPackage` shape but paginates with an opaque
|
|
729
|
+
* cursor — useful when the dataset is large and page-based pagination
|
|
730
|
+
* would hit the 10 000-record cap.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```ts
|
|
734
|
+
* import { paginate } from '@lonca/core';
|
|
735
|
+
* for await (const pkg of paginate((p) =>
|
|
736
|
+
* client.orders.listStream({ ...p, packageItemStatuses: 'Created,Picking' }),
|
|
737
|
+
* )) {
|
|
738
|
+
* console.log(pkg.id, pkg.status);
|
|
739
|
+
* }
|
|
740
|
+
* ```
|
|
741
|
+
*/
|
|
742
|
+
async listStream(params = {}) {
|
|
743
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
|
|
744
|
+
const query = { size };
|
|
745
|
+
if (params.cursor) query.nextCursor = params.cursor;
|
|
746
|
+
if (params.packageItemStatuses) query.packageItemStatuses = params.packageItemStatuses;
|
|
747
|
+
if (params.lastModifiedStartDate) {
|
|
748
|
+
query.lastModifiedStartDate = params.lastModifiedStartDate.getTime();
|
|
749
|
+
}
|
|
750
|
+
if (params.lastModifiedEndDate) {
|
|
751
|
+
query.lastModifiedEndDate = params.lastModifiedEndDate.getTime();
|
|
752
|
+
}
|
|
753
|
+
const data = await this.transport.request({
|
|
754
|
+
method: "GET",
|
|
755
|
+
path: `/integration/order/sellers/${this.sellerId}/orders/stream`,
|
|
756
|
+
query,
|
|
757
|
+
rateLimiter: this.limiter
|
|
758
|
+
});
|
|
759
|
+
const items = (data.content ?? []).map(normalizePackage);
|
|
760
|
+
const result = { items };
|
|
761
|
+
if (data.hasMore && data.nextCursor) {
|
|
762
|
+
result.nextCursor = data.nextCursor;
|
|
763
|
+
}
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Fetch the per-parcel cargo-fee breakdown for a single cargo invoice
|
|
768
|
+
* (Trendyol's `getCargoInvoiceItems`). Useful for reconciling Trendyol's
|
|
769
|
+
* cargo deductions against your shipped packages.
|
|
770
|
+
*
|
|
771
|
+
* `invoiceSerialNumber` is sourced from the Current Account Statement
|
|
772
|
+
* ("Cari Hesap Ekstresi") with `transactionType=DeductionInvoices`.
|
|
773
|
+
*
|
|
774
|
+
* Page-based pagination internally (cursor encodes the page index).
|
|
775
|
+
*/
|
|
776
|
+
async getCargoInvoiceItems(invoiceSerialNumber, params = {}) {
|
|
777
|
+
const size = Math.min(params.limit ?? 500, 500);
|
|
778
|
+
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
779
|
+
const data = await this.transport.request({
|
|
780
|
+
method: "GET",
|
|
781
|
+
path: `/integration/finance/che/sellers/${this.sellerId}/cargo-invoice/${encodeURIComponent(invoiceSerialNumber)}/items`,
|
|
782
|
+
query: { page, size },
|
|
783
|
+
rateLimiter: this.limiter
|
|
784
|
+
});
|
|
785
|
+
const items = (data.content ?? []).map((row) => {
|
|
786
|
+
const out = { raw: row };
|
|
787
|
+
if (row.shipmentPackageType !== void 0) out.shipmentPackageType = row.shipmentPackageType;
|
|
788
|
+
if (row.parcelUniqueId !== void 0) {
|
|
789
|
+
out.parcelUniqueId = typeof row.parcelUniqueId === "string" ? row.parcelUniqueId : String(row.parcelUniqueId);
|
|
790
|
+
}
|
|
791
|
+
if (row.orderNumber !== void 0) out.orderNumber = row.orderNumber;
|
|
792
|
+
if (typeof row.amount === "number") out.amount = row.amount;
|
|
793
|
+
if (typeof row.desi === "number") out.desi = row.desi;
|
|
794
|
+
return out;
|
|
795
|
+
});
|
|
796
|
+
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
797
|
+
const result = { items };
|
|
798
|
+
if (page + 1 < totalPages) {
|
|
799
|
+
result.nextCursor = String(page + 1);
|
|
800
|
+
}
|
|
801
|
+
return result;
|
|
802
|
+
}
|
|
803
|
+
packagePath(packageId) {
|
|
804
|
+
return `/integration/order/sellers/${this.sellerId}/shipment-packages/${encodeURIComponent(String(packageId))}`;
|
|
805
|
+
}
|
|
361
806
|
};
|
|
362
807
|
var DEFAULT_PAGE_SIZE3 = 50;
|
|
363
808
|
var MAX_PAGE_SIZE2 = 1e3;
|
|
809
|
+
var MAX_BUYBOX_BARCODES = 10;
|
|
364
810
|
function normalizeRef(ref) {
|
|
365
811
|
return {
|
|
366
812
|
id: ref?.id !== void 0 ? String(ref.id) : "",
|
|
@@ -425,6 +871,82 @@ function normalizeProduct(node) {
|
|
|
425
871
|
if (node.lastModifiedBy !== void 0) out.lastModifiedBy = node.lastModifiedBy;
|
|
426
872
|
return out;
|
|
427
873
|
}
|
|
874
|
+
function normalizeUnapprovedRejectReason(r) {
|
|
875
|
+
const out = {};
|
|
876
|
+
if (r.rejectReason !== void 0) out.rejectReason = r.rejectReason;
|
|
877
|
+
if (r.rejectReasonDetail !== void 0) out.rejectReasonDetail = r.rejectReasonDetail;
|
|
878
|
+
return out;
|
|
879
|
+
}
|
|
880
|
+
function normalizeUnapproved(node) {
|
|
881
|
+
const imagesSource = node.images ?? node.media;
|
|
882
|
+
const out = {
|
|
883
|
+
productMainId: node.productMainId ?? "",
|
|
884
|
+
brand: normalizeRef(node.brand),
|
|
885
|
+
category: normalizeRef(node.category),
|
|
886
|
+
barcode: node.barcode ?? "",
|
|
887
|
+
title: node.title ?? "",
|
|
888
|
+
images: normalizeImages(imagesSource),
|
|
889
|
+
attributes: (node.attributes ?? []).map(normalizeAttribute2),
|
|
890
|
+
rejectReasonDetails: (node.rejectReasonDetails ?? []).map(normalizeUnapprovedRejectReason),
|
|
891
|
+
raw: node
|
|
892
|
+
};
|
|
893
|
+
if (node.supplierId !== void 0) out.supplierId = String(node.supplierId);
|
|
894
|
+
if (node.status !== void 0) out.status = node.status;
|
|
895
|
+
if (node.description !== void 0) out.description = node.description;
|
|
896
|
+
if (typeof node.quantity === "number") out.quantity = node.quantity;
|
|
897
|
+
if (typeof node.listPrice === "number") out.listPrice = node.listPrice;
|
|
898
|
+
if (typeof node.salePrice === "number") out.salePrice = node.salePrice;
|
|
899
|
+
if (typeof node.vatRate === "number") out.vatRate = node.vatRate;
|
|
900
|
+
if (node.dimensionalWeight !== void 0 && node.dimensionalWeight !== null) {
|
|
901
|
+
out.dimensionalWeight = node.dimensionalWeight;
|
|
902
|
+
}
|
|
903
|
+
if (node.stockCode !== void 0) out.stockCode = node.stockCode;
|
|
904
|
+
if (node.origin !== void 0) out.origin = node.origin;
|
|
905
|
+
if (node.locationBasedDelivery !== void 0) {
|
|
906
|
+
out.locationBasedDelivery = node.locationBasedDelivery;
|
|
907
|
+
}
|
|
908
|
+
if (node.lotNumber !== void 0) out.lotNumber = node.lotNumber;
|
|
909
|
+
if (node.specialConsumptionTax !== void 0) {
|
|
910
|
+
out.specialConsumptionTax = node.specialConsumptionTax;
|
|
911
|
+
}
|
|
912
|
+
if (node.sgrPrice !== void 0) out.sgrPrice = node.sgrPrice;
|
|
913
|
+
const createdAt = toIso2(node.createDateTime);
|
|
914
|
+
if (createdAt) out.createdAt = createdAt;
|
|
915
|
+
const updatedAt = toIso2(node.lastUpdateDate);
|
|
916
|
+
if (updatedAt) out.updatedAt = updatedAt;
|
|
917
|
+
const priceChanged = toIso2(node.lastPriceChangeDate);
|
|
918
|
+
if (priceChanged) out.lastPriceChangedAt = priceChanged;
|
|
919
|
+
const stockChanged = toIso2(node.lastStockChangeDate);
|
|
920
|
+
if (stockChanged) out.lastStockChangedAt = stockChanged;
|
|
921
|
+
return out;
|
|
922
|
+
}
|
|
923
|
+
function normalizeBuyboxInfo(node) {
|
|
924
|
+
const out = {
|
|
925
|
+
barcode: node.barcode ?? "",
|
|
926
|
+
raw: node
|
|
927
|
+
};
|
|
928
|
+
if (typeof node.buyboxOrder === "number") out.buyboxOrder = node.buyboxOrder;
|
|
929
|
+
if (typeof node.buyboxPrice === "number") out.buyboxPrice = node.buyboxPrice;
|
|
930
|
+
if (typeof node.hasMultipleSeller === "boolean") {
|
|
931
|
+
out.hasMultipleSeller = node.hasMultipleSeller;
|
|
932
|
+
}
|
|
933
|
+
if (node.secondBuyboxPrice !== void 0) out.secondBuyboxPrice = node.secondBuyboxPrice;
|
|
934
|
+
if (node.thirdBuyboxPrice !== void 0) out.thirdBuyboxPrice = node.thirdBuyboxPrice;
|
|
935
|
+
return out;
|
|
936
|
+
}
|
|
937
|
+
function normalizeProductBase(data) {
|
|
938
|
+
const out = {
|
|
939
|
+
barcode: data.barcode ?? "",
|
|
940
|
+
approved: !!data.approved,
|
|
941
|
+
archived: !!data.archived,
|
|
942
|
+
raw: data
|
|
943
|
+
};
|
|
944
|
+
const approvedAt = toIso2(data.approvedDate);
|
|
945
|
+
if (approvedAt) out.approvedAt = approvedAt;
|
|
946
|
+
if (data.listingId !== void 0) out.listingId = data.listingId;
|
|
947
|
+
if (data.contentId !== void 0) out.contentId = String(data.contentId);
|
|
948
|
+
return out;
|
|
949
|
+
}
|
|
428
950
|
function normalizeBatchResult(data) {
|
|
429
951
|
const items = (data.items ?? []).map((item) => {
|
|
430
952
|
const result = {};
|
|
@@ -452,17 +974,52 @@ function normalizeBatchResult(data) {
|
|
|
452
974
|
if (data.storeFrontCode !== void 0) out.storeFrontCode = data.storeFrontCode;
|
|
453
975
|
return out;
|
|
454
976
|
}
|
|
977
|
+
var MAX_WRITE_ITEMS = 1e3;
|
|
978
|
+
var DELETE_RATE_PER_MIN = 100;
|
|
455
979
|
var ProductsResource = class {
|
|
456
980
|
constructor(transport, sellerId, options = {}) {
|
|
457
981
|
this.transport = transport;
|
|
458
982
|
this.sellerId = sellerId;
|
|
459
983
|
this.filterLimiter = options.filterLimiter ?? new TokenBucketRateLimiter({ capacity: 2e3, intervalMs: 6e4 });
|
|
460
984
|
this.batchLimiter = options.batchLimiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
985
|
+
this.buyboxLimiter = options.buyboxLimiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
986
|
+
this.writeLimiter = options.writeLimiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
987
|
+
this.deleteLimiter = options.deleteLimiter ?? new TokenBucketRateLimiter({ capacity: DELETE_RATE_PER_MIN, intervalMs: 6e4 });
|
|
461
988
|
}
|
|
462
989
|
transport;
|
|
463
990
|
sellerId;
|
|
464
991
|
filterLimiter;
|
|
465
992
|
batchLimiter;
|
|
993
|
+
buyboxLimiter;
|
|
994
|
+
writeLimiter;
|
|
995
|
+
deleteLimiter;
|
|
996
|
+
validateBarcodes(barcodes, methodLabel) {
|
|
997
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
998
|
+
throw new ValidationError({ message: `${methodLabel}: barcodes must not be empty` });
|
|
999
|
+
}
|
|
1000
|
+
if (barcodes.length > MAX_WRITE_ITEMS) {
|
|
1001
|
+
throw new ValidationError({
|
|
1002
|
+
message: `${methodLabel}: max ${MAX_WRITE_ITEMS} barcodes per call (got ${barcodes.length})`
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
async submitWrite(endpoint, items, methodLabel) {
|
|
1007
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
1008
|
+
throw new ValidationError({ message: `${methodLabel}: items must not be empty` });
|
|
1009
|
+
}
|
|
1010
|
+
if (items.length > MAX_WRITE_ITEMS) {
|
|
1011
|
+
throw new ValidationError({
|
|
1012
|
+
message: `${methodLabel}: max ${MAX_WRITE_ITEMS} items per call (got ${items.length})`
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
const data = await this.transport.request({
|
|
1016
|
+
method: "POST",
|
|
1017
|
+
path: `/integration/product/sellers/${this.sellerId}${endpoint}`,
|
|
1018
|
+
body: { items },
|
|
1019
|
+
rateLimiter: this.writeLimiter
|
|
1020
|
+
});
|
|
1021
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
1022
|
+
}
|
|
466
1023
|
/**
|
|
467
1024
|
* List approved products. Use `paginate()` from `@lonca/core` to iterate
|
|
468
1025
|
* lazily across pages.
|
|
@@ -524,6 +1081,235 @@ var ProductsResource = class {
|
|
|
524
1081
|
});
|
|
525
1082
|
return normalizeBatchResult(data);
|
|
526
1083
|
}
|
|
1084
|
+
/**
|
|
1085
|
+
* List **unapproved** (draft / rejected / pending-review) products.
|
|
1086
|
+
*
|
|
1087
|
+
* Wire shape is intentionally flatter than the approved-product shape:
|
|
1088
|
+
* each barcode is one top-level item with `barcode`, `quantity`, `salePrice`
|
|
1089
|
+
* etc. at the root. Rejected drafts carry `rejectReasonDetails` so you can
|
|
1090
|
+
* surface why Trendyol's content team turned them down.
|
|
1091
|
+
*
|
|
1092
|
+
* Pagination follows the same convention as `list()`: `cursor` from the
|
|
1093
|
+
* previous response forwards as `nextPageToken`.
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```ts
|
|
1097
|
+
* const page = await client.products.listUnapproved({ limit: 50 });
|
|
1098
|
+
* for (const draft of page.items) {
|
|
1099
|
+
* if (draft.status === 'rejected') {
|
|
1100
|
+
* console.warn(draft.barcode, draft.rejectReasonDetails);
|
|
1101
|
+
* }
|
|
1102
|
+
* }
|
|
1103
|
+
* ```
|
|
1104
|
+
*/
|
|
1105
|
+
async listUnapproved(params = {}) {
|
|
1106
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE3, MAX_PAGE_SIZE2);
|
|
1107
|
+
const query = { size };
|
|
1108
|
+
if (params.cursor) {
|
|
1109
|
+
query.nextPageToken = params.cursor;
|
|
1110
|
+
} else {
|
|
1111
|
+
query.page = 0;
|
|
1112
|
+
}
|
|
1113
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
1114
|
+
if (params.startDate) query.startDate = params.startDate.getTime();
|
|
1115
|
+
if (params.endDate) query.endDate = params.endDate.getTime();
|
|
1116
|
+
if (params.dateQueryType) query.dateQueryType = params.dateQueryType;
|
|
1117
|
+
if (params.supplierId !== void 0) query.supplierId = params.supplierId;
|
|
1118
|
+
const data = await this.transport.request({
|
|
1119
|
+
method: "GET",
|
|
1120
|
+
path: `/integration/product/sellers/${this.sellerId}/products/unapproved`,
|
|
1121
|
+
query,
|
|
1122
|
+
rateLimiter: this.filterLimiter
|
|
1123
|
+
});
|
|
1124
|
+
const items = (data.content ?? []).map(normalizeUnapproved);
|
|
1125
|
+
const page = { items };
|
|
1126
|
+
if (data.nextPageToken) {
|
|
1127
|
+
page.nextCursor = data.nextPageToken;
|
|
1128
|
+
}
|
|
1129
|
+
return page;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Fetch the basic lifecycle status of a single product by barcode.
|
|
1133
|
+
*
|
|
1134
|
+
* Cheap and useful as a polling primitive after `createProducts`: poll
|
|
1135
|
+
* this endpoint until `approved` flips to `true` (or use
|
|
1136
|
+
* `client.products.getBatchStatus()` to track the originating batch).
|
|
1137
|
+
*
|
|
1138
|
+
* @param barcode The product barcode to look up.
|
|
1139
|
+
*/
|
|
1140
|
+
async getBase(barcode) {
|
|
1141
|
+
const code = encodeURIComponent(barcode);
|
|
1142
|
+
const data = await this.transport.request({
|
|
1143
|
+
method: "GET",
|
|
1144
|
+
path: `/integration/product/sellers/${this.sellerId}/product/${code}`,
|
|
1145
|
+
rateLimiter: this.filterLimiter
|
|
1146
|
+
});
|
|
1147
|
+
return normalizeProductBase(data);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Fetch buybox information for up to 10 barcodes in one call.
|
|
1151
|
+
*
|
|
1152
|
+
* Returns rank (`buyboxOrder === 1` means you hold the buybox), the
|
|
1153
|
+
* current buybox price, and — beyond the spec — the second and third
|
|
1154
|
+
* competing prices when other sellers are present.
|
|
1155
|
+
*
|
|
1156
|
+
* @param barcodes 1–10 product barcodes.
|
|
1157
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 10.
|
|
1158
|
+
*/
|
|
1159
|
+
async getBuyboxInfo(barcodes) {
|
|
1160
|
+
if (!Array.isArray(barcodes) || barcodes.length === 0) {
|
|
1161
|
+
throw new ValidationError({
|
|
1162
|
+
message: "getBuyboxInfo: barcodes must be a non-empty array"
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
if (barcodes.length > MAX_BUYBOX_BARCODES) {
|
|
1166
|
+
throw new ValidationError({
|
|
1167
|
+
message: `getBuyboxInfo: max ${MAX_BUYBOX_BARCODES} barcodes per call (got ${barcodes.length})`
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
const data = await this.transport.request({
|
|
1171
|
+
method: "POST",
|
|
1172
|
+
path: `/integration/product/sellers/${this.sellerId}/products/buybox-information`,
|
|
1173
|
+
body: { barcodes },
|
|
1174
|
+
rateLimiter: this.buyboxLimiter
|
|
1175
|
+
});
|
|
1176
|
+
const list = data.buyboxInfo ?? data.content ?? [];
|
|
1177
|
+
return list.map(normalizeBuyboxInfo);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Create products (V2). Async batch — returns a `batchRequestId` you can
|
|
1181
|
+
* poll with `getBatchStatus`. Max 1000 items per call.
|
|
1182
|
+
*
|
|
1183
|
+
* Trendyol requires the full V2 attribute payload — fetch via
|
|
1184
|
+
* `categories.getAttributes` (and `categories.getAttributeValues` for
|
|
1185
|
+
* values when `allowCustom === false`). Shipment / returning warehouse
|
|
1186
|
+
* IDs come from `suppliers.getAddresses`.
|
|
1187
|
+
*
|
|
1188
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
1189
|
+
*/
|
|
1190
|
+
async create(items) {
|
|
1191
|
+
return this.submitWrite("/v2/products", items, "createProducts");
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Update **content** of approved products (title, description, images,
|
|
1195
|
+
* attributes). Identified by `contentId`. Partial update is supported
|
|
1196
|
+
* except for attributes — if you update ANY attribute, send ALL of them.
|
|
1197
|
+
*
|
|
1198
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
1199
|
+
*/
|
|
1200
|
+
async updateContent(items) {
|
|
1201
|
+
return this.submitWrite("/products/content-bulk-update", items, "updateContent");
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Update **variant** fields of approved products (stockCode, vatRate,
|
|
1205
|
+
* dimensionalWeight, warehouse IDs, location-based delivery, lot). Identified
|
|
1206
|
+
* by `barcode`. The barcode itself cannot be changed via this endpoint.
|
|
1207
|
+
*
|
|
1208
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
1209
|
+
*/
|
|
1210
|
+
async updateVariants(items) {
|
|
1211
|
+
return this.submitWrite("/products/variant-bulk-update", items, "updateVariants");
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Update **unapproved** (draft) products. Identified by `barcode`. All
|
|
1215
|
+
* other fields are optional partial updates. Use this to fix drafts that
|
|
1216
|
+
* Trendyol rejected — `client.products.listUnapproved` surfaces the
|
|
1217
|
+
* `rejectReasonDetails` you need to act on.
|
|
1218
|
+
*
|
|
1219
|
+
* **Gotcha (verified live STAGE 2026-05-25):** Trendyol's V2 spec claims
|
|
1220
|
+
* only `barcode` is required, but the endpoint returns HTTP 500
|
|
1221
|
+
* (`TrendyolSystemException` / `TypeError`) when too many optional fields
|
|
1222
|
+
* are omitted. In practice, send at least `title`, `description`,
|
|
1223
|
+
* `productMainId`, `brandId`, `categoryId`, `stockCode`,
|
|
1224
|
+
* `dimensionalWeight`, `vatRate`, `images[]`, and `attributes[]` (an
|
|
1225
|
+
* empty array is OK for the latter). The SDK forwards your payload
|
|
1226
|
+
* as-is; trim fields only if you have verified the server accepts it.
|
|
1227
|
+
*
|
|
1228
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
1229
|
+
*/
|
|
1230
|
+
async updateUnapproved(items) {
|
|
1231
|
+
return this.submitWrite("/products/unapproved-bulk-update", items, "updateUnapproved");
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Update product **delivery information** (deliveryDuration,
|
|
1235
|
+
* fastDeliveryType). Identified by `barcode`.
|
|
1236
|
+
*
|
|
1237
|
+
* @throws {ValidationError} when `items` is empty or longer than 1000.
|
|
1238
|
+
*/
|
|
1239
|
+
async updateDeliveryInfo(items) {
|
|
1240
|
+
return this.submitWrite("/products/delivery-info-bulk-update", items, "updateDeliveryInfo");
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Delete products by barcode. Trendyol allows deletion of unapproved
|
|
1244
|
+
* products and approved products that have been archived for more than a
|
|
1245
|
+
* day (and have not been sales-stopped by Trendyol).
|
|
1246
|
+
*
|
|
1247
|
+
* Async batch — returns `{ batchRequestId }` to poll via `getBatchStatus`.
|
|
1248
|
+
* Separately rate-limited at **100 req/min** (much tighter than create/update).
|
|
1249
|
+
*
|
|
1250
|
+
* @param barcodes 1–1000 barcodes.
|
|
1251
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
1252
|
+
*/
|
|
1253
|
+
async delete(barcodes) {
|
|
1254
|
+
this.validateBarcodes(barcodes, "delete");
|
|
1255
|
+
const data = await this.transport.request({
|
|
1256
|
+
method: "DELETE",
|
|
1257
|
+
path: `/integration/product/sellers/${this.sellerId}/products`,
|
|
1258
|
+
body: { items: barcodes.map((barcode) => ({ barcode })) },
|
|
1259
|
+
rateLimiter: this.deleteLimiter
|
|
1260
|
+
});
|
|
1261
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Archive products by barcode (Trendyol's `archived=true` state).
|
|
1265
|
+
* Archived products are not visible to customers; pair with `delete`
|
|
1266
|
+
* after the 24-hour archive cool-down to remove them entirely.
|
|
1267
|
+
*
|
|
1268
|
+
* Async batch — returns `{ batchRequestId }`.
|
|
1269
|
+
*
|
|
1270
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
1271
|
+
*/
|
|
1272
|
+
async archive(barcodes) {
|
|
1273
|
+
return this.setArchivedState(barcodes, true, "archive");
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Unarchive products by barcode (Trendyol's `archived=false` state).
|
|
1277
|
+
* Restores visibility for previously-archived products.
|
|
1278
|
+
*
|
|
1279
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
1280
|
+
*/
|
|
1281
|
+
async unarchive(barcodes) {
|
|
1282
|
+
return this.setArchivedState(barcodes, false, "unarchive");
|
|
1283
|
+
}
|
|
1284
|
+
async setArchivedState(barcodes, archived, methodLabel) {
|
|
1285
|
+
this.validateBarcodes(barcodes, methodLabel);
|
|
1286
|
+
const data = await this.transport.request({
|
|
1287
|
+
method: "PUT",
|
|
1288
|
+
path: `/integration/product/sellers/${this.sellerId}/products/archive-state`,
|
|
1289
|
+
body: { items: barcodes.map((barcode) => ({ barcode, archived })) },
|
|
1290
|
+
rateLimiter: this.writeLimiter
|
|
1291
|
+
});
|
|
1292
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Unlock products whose sale was paused by Trendyol due to pricing
|
|
1296
|
+
* issues (under/over-pricing, critical price error, supplier issues).
|
|
1297
|
+
* Restores selling status for the listed barcodes.
|
|
1298
|
+
*
|
|
1299
|
+
* Async batch — returns `{ batchRequestId }`.
|
|
1300
|
+
*
|
|
1301
|
+
* @throws {ValidationError} when `barcodes` is empty or longer than 1000.
|
|
1302
|
+
*/
|
|
1303
|
+
async unlock(barcodes) {
|
|
1304
|
+
this.validateBarcodes(barcodes, "unlock");
|
|
1305
|
+
const data = await this.transport.request({
|
|
1306
|
+
method: "PUT",
|
|
1307
|
+
path: `/integration/product/sellers/${this.sellerId}/products/unlock`,
|
|
1308
|
+
body: { items: barcodes.map((barcode) => ({ barcode })) },
|
|
1309
|
+
rateLimiter: this.writeLimiter
|
|
1310
|
+
});
|
|
1311
|
+
return { batchRequestId: data?.batchRequestId ?? "" };
|
|
1312
|
+
}
|
|
527
1313
|
};
|
|
528
1314
|
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
529
1315
|
"SHIPMENT",
|
|
@@ -808,7 +1594,7 @@ function createTrendyolClient(opts) {
|
|
|
808
1594
|
});
|
|
809
1595
|
return {
|
|
810
1596
|
brands: new BrandsResource(transport),
|
|
811
|
-
categories: new CategoriesResource(transport),
|
|
1597
|
+
categories: new CategoriesResource(transport, void 0, opts.sellerId),
|
|
812
1598
|
suppliers: new SuppliersResource(transport, opts.sellerId),
|
|
813
1599
|
products: new ProductsResource(transport, opts.sellerId),
|
|
814
1600
|
inventory: new InventoryResource(transport, opts.sellerId),
|