@lonca/trendyol 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -232,6 +232,217 @@ function extractAttributeList(data) {
232
232
  }
233
233
  return null;
234
234
  }
235
+ function toIso(epochMs) {
236
+ if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
237
+ return void 0;
238
+ }
239
+ return new Date(epochMs).toISOString();
240
+ }
241
+ function normalizeClaim(node) {
242
+ const out = {
243
+ id: node.id ?? node.claimId ?? "",
244
+ orderNumber: node.orderNumber ?? "",
245
+ raw: node
246
+ };
247
+ const orderDate = toIso(node.orderDate);
248
+ if (orderDate) out.orderDate = orderDate;
249
+ const claimDate = toIso(node.claimDate);
250
+ if (claimDate) out.claimDate = claimDate;
251
+ if (node.customerFirstName !== void 0) out.customerFirstName = node.customerFirstName;
252
+ if (node.customerLastName !== void 0) out.customerLastName = node.customerLastName;
253
+ return out;
254
+ }
255
+ var ClaimsResource = class {
256
+ constructor(transport, sellerId, limiter) {
257
+ this.transport = transport;
258
+ this.sellerId = sellerId;
259
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
260
+ }
261
+ transport;
262
+ sellerId;
263
+ limiter;
264
+ /**
265
+ * Create a return claim against an order. Use this to file a return on
266
+ * behalf of a customer (e.g. when they called your CS line). For
267
+ * customer-initiated returns coming from trendyol.com, you receive them
268
+ * via `claims.list()` — no need to call `create`.
269
+ *
270
+ * Returns whatever Trendyol returns (typically the new claim's identifier).
271
+ *
272
+ * @throws {ValidationError} when `claimItems` is empty.
273
+ */
274
+ async create(input) {
275
+ if (!Array.isArray(input?.claimItems) || input.claimItems.length === 0) {
276
+ throw new ValidationError({ message: "claims.create: claimItems must not be empty" });
277
+ }
278
+ return this.transport.request({
279
+ method: "POST",
280
+ path: `/integration/order/sellers/${this.sellerId}/claims/create`,
281
+ body: input,
282
+ rateLimiter: this.limiter
283
+ });
284
+ }
285
+ /**
286
+ * File a seller-side rejection ("ret talebi") against a customer claim.
287
+ *
288
+ * **Wire format: `multipart/form-data`** — the SDK builds the FormData
289
+ * internally from the typed input. `claimItemIdList` is joined with
290
+ * commas (Trendyol expects a single comma-separated string field).
291
+ * Attach supporting docs (PDF / JPEG) via `files: [Blob, ...]`.
292
+ */
293
+ async createIssue(claimId, input) {
294
+ if (!Array.isArray(input?.claimItemIdList) || input.claimItemIdList.length === 0) {
295
+ throw new ValidationError({
296
+ message: "claims.createIssue: claimItemIdList must not be empty"
297
+ });
298
+ }
299
+ if (typeof input.description !== "string" || input.description.length === 0) {
300
+ throw new ValidationError({ message: "claims.createIssue: description is required" });
301
+ }
302
+ if (input.description.length > 500) {
303
+ throw new ValidationError({
304
+ message: `claims.createIssue: description must be \u2264500 chars (got ${input.description.length})`
305
+ });
306
+ }
307
+ const form = new FormData();
308
+ form.append("claimIssueReasonId", String(input.claimIssueReasonId));
309
+ form.append("claimItemIdList", input.claimItemIdList.join(","));
310
+ form.append("description", input.description);
311
+ if (input.files) {
312
+ for (const file of input.files) {
313
+ form.append("files", file);
314
+ }
315
+ }
316
+ return this.transport.request({
317
+ method: "POST",
318
+ path: `/integration/order/sellers/${this.sellerId}/claims/${encodeURIComponent(claimId)}/issue`,
319
+ body: form,
320
+ rateLimiter: this.limiter
321
+ });
322
+ }
323
+ /**
324
+ * Approve specific claim line items. After approval, Trendyol moves
325
+ * those line items into the post-approval refund / return-shipping flow.
326
+ *
327
+ * @throws {ValidationError} when `claimLineItemIdList` is empty.
328
+ */
329
+ async approveLineItems(claimId, input) {
330
+ if (!Array.isArray(input?.claimLineItemIdList) || input.claimLineItemIdList.length === 0) {
331
+ throw new ValidationError({
332
+ message: "claims.approveLineItems: claimLineItemIdList must not be empty"
333
+ });
334
+ }
335
+ return this.transport.request({
336
+ method: "PUT",
337
+ path: `/integration/order/sellers/${this.sellerId}/claims/${encodeURIComponent(claimId)}/items/approve`,
338
+ body: input,
339
+ rateLimiter: this.limiter
340
+ });
341
+ }
342
+ /**
343
+ * List claims (page-based; SDK exposes opaque cursor convention).
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * import { paginate } from '@lonca/core';
348
+ * for await (const c of paginate((p) =>
349
+ * client.claims.list({ ...p, claimItemStatus: 'WaitingInAction' }),
350
+ * )) {
351
+ * console.log(c.id, c.orderNumber, c.claimDate);
352
+ * }
353
+ * ```
354
+ */
355
+ async list(params = {}) {
356
+ const size = Math.min(params.limit ?? 50, 200);
357
+ const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
358
+ const query = { page, size };
359
+ if (params.startDate) query.startDate = params.startDate.getTime();
360
+ if (params.endDate) query.endDate = params.endDate.getTime();
361
+ if (params.claimItemStatus) query.claimItemStatus = params.claimItemStatus;
362
+ const data = await this.transport.request({
363
+ method: "GET",
364
+ path: `/integration/order/sellers/${this.sellerId}/claims`,
365
+ query,
366
+ rateLimiter: this.limiter
367
+ });
368
+ const items = (data.content ?? []).map(normalizeClaim);
369
+ const result = { items };
370
+ const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
371
+ if (page + 1 < totalPages) {
372
+ result.nextCursor = String(page + 1);
373
+ }
374
+ return result;
375
+ }
376
+ /**
377
+ * Fetch the catalog of rejection-reason IDs the seller can use on
378
+ * `claims.createIssue()`. Cache the result — it changes rarely.
379
+ *
380
+ * Note: this endpoint is **not seller-scoped** (no `sellerId` in path).
381
+ */
382
+ async getIssueReasons() {
383
+ const data = await this.transport.request({
384
+ method: "GET",
385
+ path: `/integration/order/claim-issue-reasons`,
386
+ rateLimiter: this.limiter
387
+ });
388
+ return (data ?? []).map((r) => ({
389
+ id: typeof r.id === "number" ? r.id : 0,
390
+ name: r.name ?? ""
391
+ }));
392
+ }
393
+ /**
394
+ * Fetch the audit log for a single claim item (state transitions,
395
+ * actor, timestamp). Trendyol's response shape varies — the SDK
396
+ * surfaces each row as `{ raw }` and leaves field extraction to the
397
+ * caller until we observe a stable shape on the wire.
398
+ */
399
+ async getItemAudits(claimItemId) {
400
+ const data = await this.transport.request({
401
+ method: "GET",
402
+ path: `/integration/order/sellers/${this.sellerId}/claims/items/${encodeURIComponent(claimItemId)}/audit`,
403
+ rateLimiter: this.limiter
404
+ });
405
+ const rows = Array.isArray(data) ? data : Array.isArray(data?.content) ? data.content : [];
406
+ return rows.map((row) => ({ raw: row }));
407
+ }
408
+ };
409
+ var FinanceResource = class {
410
+ constructor(transport, sellerId, limiter) {
411
+ this.transport = transport;
412
+ this.sellerId = sellerId;
413
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 100, intervalMs: 6e4 });
414
+ }
415
+ transport;
416
+ sellerId;
417
+ limiter;
418
+ async getSettlements(params = {}) {
419
+ return this.queryPage(`/integration/sellers/${this.sellerId}/settlements`, params);
420
+ }
421
+ async getOtherFinancials(params = {}) {
422
+ return this.queryPage(`/integration/sellers/${this.sellerId}/otherfinancials`, params);
423
+ }
424
+ async queryPage(path, params) {
425
+ const size = Math.min(params.limit ?? 50, 200);
426
+ const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
427
+ const query = { page, size };
428
+ if (params.startDate) query.startDate = params.startDate.getTime();
429
+ if (params.endDate) query.endDate = params.endDate.getTime();
430
+ if (params.transactionType) query.transactionType = params.transactionType;
431
+ const data = await this.transport.request({
432
+ method: "GET",
433
+ path,
434
+ query,
435
+ rateLimiter: this.limiter
436
+ });
437
+ const items = (data.content ?? []).map((row) => ({ raw: row }));
438
+ const result = { items };
439
+ const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
440
+ if (page + 1 < totalPages) {
441
+ result.nextCursor = String(page + 1);
442
+ }
443
+ return result;
444
+ }
445
+ };
235
446
  var MAX_ITEMS_PER_REQUEST = 1e3;
236
447
  var InventoryResource = class {
237
448
  constructor(transport, sellerId) {
@@ -271,9 +482,201 @@ var InventoryResource = class {
271
482
  return { batchRequestId: data.batchRequestId ?? "" };
272
483
  }
273
484
  };
485
+ var InvoicesResource = class {
486
+ constructor(transport, sellerId, limiter) {
487
+ this.transport = transport;
488
+ this.sellerId = sellerId;
489
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 500, intervalMs: 6e4 });
490
+ }
491
+ transport;
492
+ sellerId;
493
+ limiter;
494
+ /**
495
+ * Upload an invoice file for a shipment package. **Multipart**: the
496
+ * SDK builds the FormData internally from the typed input.
497
+ *
498
+ * Max 10 MB. Accepted formats: PDF, JPEG, PNG.
499
+ */
500
+ async uploadFile(input) {
501
+ if (!input?.file) {
502
+ throw new ValidationError({ message: "invoices.uploadFile: file is required" });
503
+ }
504
+ if (!input.shipmentPackageId) {
505
+ throw new ValidationError({ message: "invoices.uploadFile: shipmentPackageId is required" });
506
+ }
507
+ const form = new FormData();
508
+ form.append("shipmentPackageId", String(input.shipmentPackageId));
509
+ form.append("file", input.file);
510
+ if (input.invoiceDateTime !== void 0) {
511
+ form.append("invoiceDateTime", String(input.invoiceDateTime));
512
+ }
513
+ if (input.invoiceNumber !== void 0) {
514
+ form.append("invoiceNumber", input.invoiceNumber);
515
+ }
516
+ return this.transport.request({
517
+ method: "POST",
518
+ path: `/integration/sellers/${this.sellerId}/seller-invoice-file`,
519
+ body: form,
520
+ rateLimiter: this.limiter
521
+ });
522
+ }
523
+ /** Register an invoice URL with Trendyol (alternative to uploading the file). */
524
+ async sendLink(input) {
525
+ if (!input?.invoiceLink) {
526
+ throw new ValidationError({ message: "invoices.sendLink: invoiceLink is required" });
527
+ }
528
+ return this.transport.request({
529
+ method: "POST",
530
+ path: `/integration/sellers/${this.sellerId}/seller-invoice-links`,
531
+ body: input,
532
+ rateLimiter: this.limiter
533
+ });
534
+ }
535
+ /** Remove a previously-registered invoice link. */
536
+ async deleteLink(input) {
537
+ return this.transport.request({
538
+ method: "POST",
539
+ path: `/integration/sellers/${this.sellerId}/seller-invoice-links/delete`,
540
+ body: input,
541
+ rateLimiter: this.limiter
542
+ });
543
+ }
544
+ };
545
+ var LabelsResource = class {
546
+ constructor(transport, sellerId, limiter) {
547
+ this.transport = transport;
548
+ this.sellerId = sellerId;
549
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 100, intervalMs: 6e4 });
550
+ }
551
+ transport;
552
+ sellerId;
553
+ limiter;
554
+ /**
555
+ * Request a common ZPL label for a cargo tracking number. After this
556
+ * returns, call `getCommon()` with the same `cargoTrackingNumber` to
557
+ * retrieve the generated label.
558
+ *
559
+ * @throws {ValidationError} when `format` is missing.
560
+ */
561
+ async createCommon(cargoTrackingNumber, input) {
562
+ if (!input?.format) {
563
+ throw new ValidationError({ message: "labels.createCommon: format is required" });
564
+ }
565
+ return this.transport.request({
566
+ method: "POST",
567
+ path: `/integration/sellers/${this.sellerId}/common-label/${encodeURIComponent(String(cargoTrackingNumber))}`,
568
+ body: input,
569
+ rateLimiter: this.limiter
570
+ });
571
+ }
572
+ /** Retrieve the previously-created common label. */
573
+ async getCommon(cargoTrackingNumber) {
574
+ const data = await this.transport.request({
575
+ method: "GET",
576
+ path: `/integration/sellers/${this.sellerId}/common-label/${encodeURIComponent(String(cargoTrackingNumber))}`,
577
+ rateLimiter: this.limiter
578
+ });
579
+ return { raw: data ?? {} };
580
+ }
581
+ };
582
+ function n(rows, extract) {
583
+ const list = Array.isArray(rows) ? rows : [];
584
+ return list.map((r) => {
585
+ const node = r;
586
+ return { ...extract(node), raw: node };
587
+ });
588
+ }
589
+ var LocationsResource = class {
590
+ constructor(transport, limiter) {
591
+ this.transport = transport;
592
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 60, intervalMs: 6e4 });
593
+ }
594
+ transport;
595
+ limiter;
596
+ /** List all supported countries (Türkiye + AZ + GULF + CEE). */
597
+ async getCountries() {
598
+ const data = await this.transport.request({
599
+ method: "GET",
600
+ path: `/integration/member/countries`,
601
+ rateLimiter: this.limiter
602
+ });
603
+ return n(data, (node) => ({
604
+ code: String(node.code ?? node.id ?? ""),
605
+ name: node.name
606
+ }));
607
+ }
608
+ // ─── Domestic (TR / AZ) ───────────────────────────────────────────────
609
+ async getTurkeyCities() {
610
+ return this.cities(`/integration/member/countries/domestic/TR/cities`);
611
+ }
612
+ async getTurkeyDistricts(cityCode) {
613
+ return this.districts(
614
+ `/integration/member/countries/domestic/TR/cities/${encodeURIComponent(String(cityCode))}/districts`
615
+ );
616
+ }
617
+ async getTurkeyNeighborhoods(cityCode, districtCode) {
618
+ return this.neighborhoods(
619
+ `/integration/member/countries/domestic/TR/cities/${encodeURIComponent(String(cityCode))}/districts/${encodeURIComponent(String(districtCode))}/neighborhoods`
620
+ );
621
+ }
622
+ async getAzerbaijanCities() {
623
+ return this.cities(`/integration/member/countries/domestic/AZ/cities`);
624
+ }
625
+ async getAzerbaijanDistricts(cityCode) {
626
+ return this.districts(
627
+ `/integration/member/countries/domestic/AZ/cities/${encodeURIComponent(String(cityCode))}/districts`
628
+ );
629
+ }
630
+ // ─── International (GULF / CEE) ───────────────────────────────────────
631
+ async getCitiesByCountry(countryCode) {
632
+ return this.cities(`/integration/member/countries/${encodeURIComponent(countryCode)}/cities`);
633
+ }
634
+ async getDistrictsByCity(countryCode, cityId) {
635
+ return this.districts(
636
+ `/integration/member/countries/${encodeURIComponent(countryCode)}/cities/${encodeURIComponent(String(cityId))}/districts`
637
+ );
638
+ }
639
+ // ─── Shared paginators ────────────────────────────────────────────────
640
+ async cities(path) {
641
+ const data = await this.transport.request({
642
+ method: "GET",
643
+ path,
644
+ rateLimiter: this.limiter
645
+ });
646
+ return n(data, (node) => ({
647
+ code: String(node.code ?? node.id ?? ""),
648
+ name: node.name,
649
+ countryCode: node.countryCode
650
+ }));
651
+ }
652
+ async districts(path) {
653
+ const data = await this.transport.request({
654
+ method: "GET",
655
+ path,
656
+ rateLimiter: this.limiter
657
+ });
658
+ return n(data, (node) => ({
659
+ code: String(node.code ?? node.id ?? ""),
660
+ name: node.name,
661
+ cityCode: node.cityCode !== void 0 ? String(node.cityCode) : void 0
662
+ }));
663
+ }
664
+ async neighborhoods(path) {
665
+ const data = await this.transport.request({
666
+ method: "GET",
667
+ path,
668
+ rateLimiter: this.limiter
669
+ });
670
+ return n(data, (node) => ({
671
+ code: String(node.code ?? node.id ?? ""),
672
+ name: node.name,
673
+ districtCode: node.districtCode !== void 0 ? String(node.districtCode) : void 0
674
+ }));
675
+ }
676
+ };
274
677
  var MAX_PAGE_SIZE = 200;
275
678
  var DEFAULT_PAGE_SIZE2 = 50;
276
- function toIso(epochMs) {
679
+ function toIso2(epochMs) {
277
680
  if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
278
681
  return void 0;
279
682
  }
@@ -341,7 +744,7 @@ function normalizeLine(node) {
341
744
  function normalizeHistory(node) {
342
745
  const out = { raw: node };
343
746
  if (node.status !== void 0) out.status = node.status;
344
- const createdAt = toIso(node.createdDate);
747
+ const createdAt = toIso2(node.createdDate);
345
748
  if (createdAt) out.createdAt = createdAt;
346
749
  return out;
347
750
  }
@@ -357,13 +760,14 @@ function normalizeCustomer(node) {
357
760
  return out;
358
761
  }
359
762
  function normalizePackage(node) {
763
+ const pkgId = node.shipmentPackageId ?? node.id;
360
764
  const out = {
361
- id: node.shipmentPackageId !== void 0 ? String(node.shipmentPackageId) : "",
765
+ id: pkgId !== void 0 ? String(pkgId) : "",
362
766
  orderNumber: node.orderNumber ?? "",
363
767
  status: node.status ?? "",
364
768
  customer: normalizeCustomer(node),
365
- orderDate: toIso(node.orderDate) ?? "",
366
- lastModifiedDate: toIso(node.lastModifiedDate) ?? "",
769
+ orderDate: toIso2(node.orderDate) ?? "",
770
+ lastModifiedDate: toIso2(node.lastModifiedDate) ?? "",
367
771
  currencyCode: node.currencyCode ?? "",
368
772
  packageTotalPrice: typeof node.packageTotalPrice === "number" ? node.packageTotalPrice : 0,
369
773
  packageGrossAmount: typeof node.packageGrossAmount === "number" ? node.packageGrossAmount : 0,
@@ -383,13 +787,13 @@ function normalizePackage(node) {
383
787
  if (node.shipmentPackageStatus !== void 0) {
384
788
  out.shipmentPackageStatus = node.shipmentPackageStatus;
385
789
  }
386
- const agreed = toIso(node.agreedDeliveryDate);
790
+ const agreed = toIso2(node.agreedDeliveryDate);
387
791
  if (agreed) out.agreedDeliveryDate = agreed;
388
- const estStart = toIso(node.estimatedDeliveryStartDate);
792
+ const estStart = toIso2(node.estimatedDeliveryStartDate);
389
793
  if (estStart) out.estimatedDeliveryStartDate = estStart;
390
- const estEnd = toIso(node.estimatedDeliveryEndDate);
794
+ const estEnd = toIso2(node.estimatedDeliveryEndDate);
391
795
  if (estEnd) out.estimatedDeliveryEndDate = estEnd;
392
- const origin = toIso(node.originShipmentDate);
796
+ const origin = toIso2(node.originShipmentDate);
393
797
  if (origin) out.originShipmentDate = origin;
394
798
  const invoice = normalizeAddress(node.invoiceAddress);
395
799
  if (invoice) out.invoiceAddress = invoice;
@@ -415,50 +819,383 @@ function normalizePackage(node) {
415
819
  if (typeof node["3pByTrendyol"] === "boolean") {
416
820
  out.threePByTrendyol = node["3pByTrendyol"];
417
821
  }
418
- if (typeof node.containsDangerousProduct === "boolean") {
419
- out.containsDangerousProduct = node.containsDangerousProduct;
822
+ if (typeof node.containsDangerousProduct === "boolean") {
823
+ out.containsDangerousProduct = node.containsDangerousProduct;
824
+ }
825
+ if (typeof node.isCod === "boolean") out.isCod = node.isCod;
826
+ if (typeof node.is4P === "boolean") out.is4P = node.is4P;
827
+ if (node.invoiceLink !== void 0) out.invoiceLink = node.invoiceLink;
828
+ if (node.createdBy !== void 0) out.createdBy = node.createdBy;
829
+ return out;
830
+ }
831
+ var OrdersResource = class {
832
+ constructor(transport, sellerId, limiter) {
833
+ this.transport = transport;
834
+ this.sellerId = sellerId;
835
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
836
+ }
837
+ transport;
838
+ sellerId;
839
+ limiter;
840
+ /**
841
+ * List shipment packages for the seller.
842
+ *
843
+ * @example
844
+ * ```ts
845
+ * import { paginate } from '@lonca/core';
846
+ * for await (const pkg of paginate((p) => client.orders.list({ ...p, status: 'Created' }))) {
847
+ * console.log(pkg.id, pkg.status, pkg.customer.firstName);
848
+ * }
849
+ * ```
850
+ */
851
+ async list(params = {}) {
852
+ const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
853
+ const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
854
+ const query = { page, size };
855
+ if (params.status) query.status = params.status;
856
+ if (params.orderNumber) query.orderNumber = params.orderNumber;
857
+ if (params.startDate) query.startDate = params.startDate.getTime();
858
+ if (params.endDate) query.endDate = params.endDate.getTime();
859
+ const data = await this.transport.request({
860
+ method: "GET",
861
+ path: `/integration/order/sellers/${this.sellerId}/orders`,
862
+ query,
863
+ rateLimiter: this.limiter
864
+ });
865
+ const items = (data.content ?? []).map(normalizePackage);
866
+ const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
867
+ const result = { items };
868
+ if (page + 1 < totalPages) {
869
+ result.nextCursor = String(page + 1);
870
+ }
871
+ return result;
872
+ }
873
+ /**
874
+ * Push a shipment-package status update.
875
+ *
876
+ * Trendyol restricts the seller-side push to two transitions:
877
+ * - `Picking` — order picked up from the shelf / being prepared
878
+ * - `Invoiced` — invoice issued, ready for cargo handoff
879
+ *
880
+ * Other transitions (`Shipped`, `Delivered`, etc.) are driven by Trendyol
881
+ * or the cargo provider — call `processAlternativeDelivery` or
882
+ * `manualDeliverByPackageId` if you ship outside Trendyol's cargo
883
+ * network.
884
+ *
885
+ * Returns void; Trendyol responds with 200 + empty body on success.
886
+ */
887
+ async updatePackageStatus(packageId, input) {
888
+ await this.transport.request({
889
+ method: "PUT",
890
+ path: this.packagePath(packageId),
891
+ body: input,
892
+ rateLimiter: this.limiter
893
+ });
894
+ }
895
+ /**
896
+ * Notify Trendyol that one or more line items cannot be supplied
897
+ * ("Tedarik Edememe Bildirimi"). Marks the listed line IDs as
898
+ * `UnSupplied`. Trendyol cancels those quantities and notifies the
899
+ * customer.
900
+ *
901
+ * `reasonId` is a numeric code Trendyol publishes separately — consult
902
+ * the seller panel or the "Tedarik Edememe" docs for current values.
903
+ *
904
+ * Returns void; Trendyol responds with 200 + empty body on success.
905
+ */
906
+ async cancelPackageItem(packageId, input) {
907
+ if (!Array.isArray(input?.lines) || input.lines.length === 0) {
908
+ throw new ValidationError({ message: "cancelPackageItem: lines must not be empty" });
909
+ }
910
+ await this.transport.request({
911
+ method: "PUT",
912
+ path: `${this.packagePath(packageId)}/items/unsupplied`,
913
+ body: input,
914
+ rateLimiter: this.limiter
915
+ });
916
+ }
917
+ /**
918
+ * Extend the agreed delivery date for a shipment package by 1, 2, or 3 days.
919
+ * Trendyol enforces the [1, 3] range server-side; the SDK validates client-side
920
+ * to fail fast.
921
+ *
922
+ * Returns void; Trendyol responds with 200 + empty body on success.
923
+ */
924
+ async extendDeliveryDate(packageId, extendedDayCount) {
925
+ if (![1, 2, 3].includes(extendedDayCount)) {
926
+ throw new ValidationError({
927
+ message: `extendDeliveryDate: extendedDayCount must be 1, 2, or 3 (got ${extendedDayCount})`
928
+ });
929
+ }
930
+ await this.transport.request({
931
+ method: "PUT",
932
+ path: `${this.packagePath(packageId)}/extended-agreed-delivery-date`,
933
+ body: { extendedDayCount },
934
+ rateLimiter: this.limiter
935
+ });
936
+ }
937
+ /**
938
+ * Notify Trendyol of an alternative delivery channel — used when the
939
+ * seller is shipping via a non-Trendyol cargo provider. Trendyol then
940
+ * either SMSes the customer the tracking link (when `isPhoneNumber` is
941
+ * `true`) or stores the tracking URL on the package directly.
942
+ *
943
+ * Returns void; Trendyol responds with 200 + empty body on success.
944
+ */
945
+ async processAlternativeDelivery(packageId, input) {
946
+ await this.transport.request({
947
+ method: "PUT",
948
+ path: `${this.packagePath(packageId)}/alternative-delivery`,
949
+ body: input,
950
+ rateLimiter: this.limiter
951
+ });
952
+ }
953
+ /**
954
+ * Split a shipment package by moving a set of line IDs into a new
955
+ * package. The original package keeps the remaining lines.
956
+ *
957
+ * @param packageId The package to split.
958
+ * @param orderLineIds Line IDs to move into the new package (1+).
959
+ * @throws {ValidationError} when `orderLineIds` is empty.
960
+ */
961
+ async splitPackage(packageId, orderLineIds) {
962
+ if (!Array.isArray(orderLineIds) || orderLineIds.length === 0) {
963
+ throw new ValidationError({ message: "splitPackage: orderLineIds must not be empty" });
964
+ }
965
+ await this.transport.request({
966
+ method: "POST",
967
+ path: `${this.packagePath(packageId)}/split`,
968
+ body: { orderLineIds },
969
+ rateLimiter: this.limiter
970
+ });
971
+ }
972
+ /**
973
+ * Split a shipment package by quantity. Each `QuantitySplit` entry
974
+ * carves a single line into multiple packages — e.g. `{ orderLineId: 100,
975
+ * quantities: [2, 2, 1] }` splits 5 units of line 100 into three packages
976
+ * of 2 + 2 + 1.
977
+ *
978
+ * @throws {ValidationError} when `quantitySplit` is empty.
979
+ */
980
+ async splitPackageByQuantity(packageId, quantitySplit) {
981
+ if (!Array.isArray(quantitySplit) || quantitySplit.length === 0) {
982
+ throw new ValidationError({
983
+ message: "splitPackageByQuantity: quantitySplit must not be empty"
984
+ });
985
+ }
986
+ await this.transport.request({
987
+ method: "POST",
988
+ path: `${this.packagePath(packageId)}/quantity-split`,
989
+ body: { quantitySplit },
990
+ rateLimiter: this.limiter
991
+ });
992
+ }
993
+ /**
994
+ * Split a shipment package into multiple new packages by grouping line
995
+ * IDs. Each `SplitGroup` becomes one new package containing the listed
996
+ * line IDs.
997
+ *
998
+ * @throws {ValidationError} when `splitGroups` is empty.
999
+ */
1000
+ async multiSplitPackage(packageId, splitGroups) {
1001
+ if (!Array.isArray(splitGroups) || splitGroups.length === 0) {
1002
+ throw new ValidationError({ message: "multiSplitPackage: splitGroups must not be empty" });
1003
+ }
1004
+ await this.transport.request({
1005
+ method: "POST",
1006
+ path: `${this.packagePath(packageId)}/multi-split`,
1007
+ body: { splitGroups },
1008
+ rateLimiter: this.limiter
1009
+ });
1010
+ }
1011
+ /**
1012
+ * Split a shipment package into multiple new packages, each containing a
1013
+ * mix of line items at specific quantities. This is the most expressive
1014
+ * split — use it when you need fine-grained control over which line IDs
1015
+ * and how many of each end up in each new package.
1016
+ *
1017
+ * @throws {ValidationError} when `splitPackages` is empty.
1018
+ */
1019
+ async splitMultiPackagesByQuantity(packageId, splitPackages) {
1020
+ if (!Array.isArray(splitPackages) || splitPackages.length === 0) {
1021
+ throw new ValidationError({
1022
+ message: "splitMultiPackagesByQuantity: splitPackages must not be empty"
1023
+ });
1024
+ }
1025
+ await this.transport.request({
1026
+ method: "POST",
1027
+ path: `${this.packagePath(packageId)}/split-packages`,
1028
+ body: { splitPackages },
1029
+ rateLimiter: this.limiter
1030
+ });
1031
+ }
1032
+ /**
1033
+ * Change the cargo provider on an existing shipment package. Use one of
1034
+ * Trendyol's documented marketplace cargo codes (`'YKMP'`, `'ARASMP'`,
1035
+ * `'SURATMP'`, etc.) — see `TrendyolCargoProvider` for the full list.
1036
+ */
1037
+ async changeCargoProvider(packageId, cargoProvider) {
1038
+ await this.transport.request({
1039
+ method: "PUT",
1040
+ path: `${this.packagePath(packageId)}/cargo-providers`,
1041
+ body: { cargoProvider },
1042
+ rateLimiter: this.limiter
1043
+ });
1044
+ }
1045
+ /**
1046
+ * Mark a shipment package as manually delivered via its package ID.
1047
+ * Used when the seller delivered the order outside Trendyol's cargo
1048
+ * network and needs to flip the package to `Delivered` after handover.
1049
+ *
1050
+ * No request body; Trendyol responds with 200 + empty body on success.
1051
+ */
1052
+ async manualDeliverByPackageId(packageId) {
1053
+ await this.transport.request({
1054
+ method: "PUT",
1055
+ path: `${this.packagePath(packageId)}/manual-invoice-delivery`,
1056
+ rateLimiter: this.limiter
1057
+ });
1058
+ }
1059
+ /**
1060
+ * Manual-deliver variant that takes the cargo tracking number instead
1061
+ * of the package ID. Useful when you only have the tracking number on
1062
+ * hand (e.g., from a cargo provider webhook).
1063
+ *
1064
+ * Note the path structure: tracking number sits at a sibling location,
1065
+ * not under `/shipment-packages/{id}/...`.
1066
+ */
1067
+ async manualDeliverByTrackingNumber(cargoTrackingNumber) {
1068
+ await this.transport.request({
1069
+ method: "PUT",
1070
+ path: `/integration/order/sellers/${this.sellerId}/shipment-packages/manual-invoice-delivery-by-tracking-number/${encodeURIComponent(String(cargoTrackingNumber))}`,
1071
+ rateLimiter: this.limiter
1072
+ });
1073
+ }
1074
+ /**
1075
+ * Mark a package as delivered through an authorized service ("yetkili
1076
+ * servis"). For appliance / installation-required products that are
1077
+ * delivered + installed by a third-party service partner.
1078
+ */
1079
+ async markDeliveredByService(packageId) {
1080
+ await this.transport.request({
1081
+ method: "PUT",
1082
+ path: `${this.packagePath(packageId)}/delivered-by-service`,
1083
+ rateLimiter: this.limiter
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Update box / packaging metadata on a shipment package (desi value
1088
+ * and/or number of boxes). Either field can be sent alone.
1089
+ */
1090
+ async updateBoxInfo(packageId, input) {
1091
+ await this.transport.request({
1092
+ method: "PUT",
1093
+ path: `${this.packagePath(packageId)}/box-info`,
1094
+ body: input,
1095
+ rateLimiter: this.limiter
1096
+ });
420
1097
  }
421
- if (typeof node.isCod === "boolean") out.isCod = node.isCod;
422
- if (typeof node.is4P === "boolean") out.is4P = node.is4P;
423
- if (node.invoiceLink !== void 0) out.invoiceLink = node.invoiceLink;
424
- if (node.createdBy !== void 0) out.createdBy = node.createdBy;
425
- return out;
426
- }
427
- var OrdersResource = class {
428
- constructor(transport, sellerId, limiter) {
429
- this.transport = transport;
430
- this.sellerId = sellerId;
431
- this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
1098
+ /**
1099
+ * Update labor costs for one or more order lines.
1100
+ *
1101
+ * **Wire note:** Trendyol's request body is a raw array (no envelope),
1102
+ * not `{ items: [...] }`. The SDK forwards `items` verbatim.
1103
+ *
1104
+ * @throws {ValidationError} when `items` is empty.
1105
+ */
1106
+ async updateLaborCosts(packageId, items) {
1107
+ if (!Array.isArray(items) || items.length === 0) {
1108
+ throw new ValidationError({ message: "updateLaborCosts: items must not be empty" });
1109
+ }
1110
+ await this.transport.request({
1111
+ method: "PUT",
1112
+ path: `${this.packagePath(packageId)}/labor-costs`,
1113
+ body: items,
1114
+ rateLimiter: this.limiter
1115
+ });
432
1116
  }
433
- transport;
434
- sellerId;
435
- limiter;
436
1117
  /**
437
- * List shipment packages for the seller.
1118
+ * Reassign a shipment package to a different warehouse. `warehouseId`
1119
+ * comes from `client.suppliers.getAddresses()` (filter by `isShipmentAddress`).
1120
+ */
1121
+ async updateWarehouse(packageId, warehouseId) {
1122
+ await this.transport.request({
1123
+ method: "PUT",
1124
+ path: `${this.packagePath(packageId)}/warehouse`,
1125
+ body: { warehouseId },
1126
+ rateLimiter: this.limiter
1127
+ });
1128
+ }
1129
+ /**
1130
+ * Stream variant of `list()`. Trendyol's `getShipmentPackagesStream`
1131
+ * returns the same `ShipmentPackage` shape but paginates with an opaque
1132
+ * cursor — useful when the dataset is large and page-based pagination
1133
+ * would hit the 10 000-record cap.
438
1134
  *
439
1135
  * @example
440
1136
  * ```ts
441
1137
  * import { paginate } from '@lonca/core';
442
- * for await (const pkg of paginate((p) => client.orders.list({ ...p, status: 'Created' }))) {
443
- * console.log(pkg.id, pkg.status, pkg.customer.firstName);
1138
+ * for await (const pkg of paginate((p) =>
1139
+ * client.orders.listStream({ ...p, packageItemStatuses: 'Created,Picking' }),
1140
+ * )) {
1141
+ * console.log(pkg.id, pkg.status);
444
1142
  * }
445
1143
  * ```
446
1144
  */
447
- async list(params = {}) {
1145
+ async listStream(params = {}) {
448
1146
  const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
449
- const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
450
- const query = { page, size };
451
- if (params.status) query.status = params.status;
452
- if (params.orderNumber) query.orderNumber = params.orderNumber;
453
- if (params.startDate) query.startDate = params.startDate.getTime();
454
- if (params.endDate) query.endDate = params.endDate.getTime();
1147
+ const query = { size };
1148
+ if (params.cursor) query.nextCursor = params.cursor;
1149
+ if (params.packageItemStatuses) query.packageItemStatuses = params.packageItemStatuses;
1150
+ if (params.lastModifiedStartDate) {
1151
+ query.lastModifiedStartDate = params.lastModifiedStartDate.getTime();
1152
+ }
1153
+ if (params.lastModifiedEndDate) {
1154
+ query.lastModifiedEndDate = params.lastModifiedEndDate.getTime();
1155
+ }
455
1156
  const data = await this.transport.request({
456
1157
  method: "GET",
457
- path: `/integration/order/sellers/${this.sellerId}/orders`,
1158
+ path: `/integration/order/sellers/${this.sellerId}/orders/stream`,
458
1159
  query,
459
1160
  rateLimiter: this.limiter
460
1161
  });
461
1162
  const items = (data.content ?? []).map(normalizePackage);
1163
+ const result = { items };
1164
+ if (data.hasMore && data.nextCursor) {
1165
+ result.nextCursor = data.nextCursor;
1166
+ }
1167
+ return result;
1168
+ }
1169
+ /**
1170
+ * Fetch the per-parcel cargo-fee breakdown for a single cargo invoice
1171
+ * (Trendyol's `getCargoInvoiceItems`). Useful for reconciling Trendyol's
1172
+ * cargo deductions against your shipped packages.
1173
+ *
1174
+ * `invoiceSerialNumber` is sourced from the Current Account Statement
1175
+ * ("Cari Hesap Ekstresi") with `transactionType=DeductionInvoices`.
1176
+ *
1177
+ * Page-based pagination internally (cursor encodes the page index).
1178
+ */
1179
+ async getCargoInvoiceItems(invoiceSerialNumber, params = {}) {
1180
+ const size = Math.min(params.limit ?? 500, 500);
1181
+ const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
1182
+ const data = await this.transport.request({
1183
+ method: "GET",
1184
+ path: `/integration/finance/che/sellers/${this.sellerId}/cargo-invoice/${encodeURIComponent(invoiceSerialNumber)}/items`,
1185
+ query: { page, size },
1186
+ rateLimiter: this.limiter
1187
+ });
1188
+ const items = (data.content ?? []).map((row) => {
1189
+ const out = { raw: row };
1190
+ if (row.shipmentPackageType !== void 0) out.shipmentPackageType = row.shipmentPackageType;
1191
+ if (row.parcelUniqueId !== void 0) {
1192
+ out.parcelUniqueId = typeof row.parcelUniqueId === "string" ? row.parcelUniqueId : String(row.parcelUniqueId);
1193
+ }
1194
+ if (row.orderNumber !== void 0) out.orderNumber = row.orderNumber;
1195
+ if (typeof row.amount === "number") out.amount = row.amount;
1196
+ if (typeof row.desi === "number") out.desi = row.desi;
1197
+ return out;
1198
+ });
462
1199
  const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
463
1200
  const result = { items };
464
1201
  if (page + 1 < totalPages) {
@@ -466,6 +1203,104 @@ var OrdersResource = class {
466
1203
  }
467
1204
  return result;
468
1205
  }
1206
+ /**
1207
+ * Notify Trendyol that a shipped package was returned to you (manual
1208
+ * return flow, e.g. customer dropped it at your address or you got the
1209
+ * package back without going through Trendyol's return cargo).
1210
+ *
1211
+ * No body; Trendyol responds with 200 + empty body on success.
1212
+ */
1213
+ async manualReturnByPackageId(packageId) {
1214
+ await this.transport.request({
1215
+ method: "PUT",
1216
+ path: `${this.packagePath(packageId)}/manual-return`,
1217
+ rateLimiter: this.limiter
1218
+ });
1219
+ }
1220
+ /**
1221
+ * Manual-return variant that takes the cargo tracking number instead of
1222
+ * the package ID. Useful when the cargo provider's webhook only carries
1223
+ * the tracking number.
1224
+ *
1225
+ * Sibling path (not under `/{packageId}/...`).
1226
+ */
1227
+ async manualReturnByTrackingNumber(cargoTrackingNumber) {
1228
+ await this.transport.request({
1229
+ method: "PUT",
1230
+ path: `/integration/order/sellers/${this.sellerId}/shipment-packages/manual-return-by-tracking-number/${encodeURIComponent(String(cargoTrackingNumber))}`,
1231
+ rateLimiter: this.limiter
1232
+ });
1233
+ }
1234
+ /**
1235
+ * Fetch Trendyol Express compensation tickets (claims filed when a
1236
+ * shipment is lost or damaged in transit). Page-based pagination
1237
+ * internally; the SDK exposes the cursor convention.
1238
+ *
1239
+ * Note the different base path — `/integration/tex/compensation/...`,
1240
+ * not the regular `/integration/order/...`.
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * import { paginate } from '@lonca/core';
1245
+ * const tickets = await client.orders.getCompensationTickets({
1246
+ * startDate: new Date('2026-01-01'),
1247
+ * endDate: new Date('2026-02-01'),
1248
+ * });
1249
+ * for (const t of tickets.items) {
1250
+ * console.log(t.orderNumber, t.currentState, t.stateMessage);
1251
+ * }
1252
+ * ```
1253
+ */
1254
+ async getCompensationTickets(params = {}) {
1255
+ const size = Math.min(params.limit ?? 200, 200);
1256
+ const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
1257
+ const query = { page, size };
1258
+ if (params.startDate) query.startDate = params.startDate.getTime();
1259
+ if (params.endDate) query.endDate = params.endDate.getTime();
1260
+ const res = await this.transport.request({
1261
+ method: "GET",
1262
+ path: `/integration/tex/compensation/sellers/${this.sellerId}/tickets`,
1263
+ query,
1264
+ rateLimiter: this.limiter
1265
+ });
1266
+ const tickets = Array.isArray(res.data) ? res.data : res.data?.items ?? res.data?.content ?? res.content ?? [];
1267
+ const items = tickets.map((t) => {
1268
+ const itemDetails = (t.itemDetails ?? []).map((d) => {
1269
+ const out2 = {};
1270
+ if (typeof d.itemAmount === "number") out2.itemAmount = d.itemAmount;
1271
+ if (d.itemCode !== void 0) out2.itemCode = d.itemCode;
1272
+ if (typeof d.itemCount === "number") out2.itemCount = d.itemCount;
1273
+ if (d.itemName !== void 0) out2.itemName = d.itemName;
1274
+ return out2;
1275
+ });
1276
+ const out = {
1277
+ itemDetails,
1278
+ raw: t
1279
+ };
1280
+ if (t.cargoProvider !== void 0) out.cargoProvider = t.cargoProvider;
1281
+ if (t.compensateReason !== void 0) out.compensateReason = t.compensateReason;
1282
+ const createdAt = toIso2(t.createDate);
1283
+ if (createdAt) out.createdAt = createdAt;
1284
+ if (t.currentState !== void 0) {
1285
+ out.currentState = t.currentState;
1286
+ }
1287
+ if (t.deliveryNumber !== void 0) out.deliveryNumber = t.deliveryNumber;
1288
+ if (t.orderNumber !== void 0) out.orderNumber = t.orderNumber;
1289
+ if (t.requestedBy !== void 0) out.requestedBy = t.requestedBy;
1290
+ if (t.stateMessage !== void 0) out.stateMessage = t.stateMessage;
1291
+ if (t.totalItemsAmount !== void 0) out.totalItemsAmount = t.totalItemsAmount;
1292
+ return out;
1293
+ });
1294
+ const result = { items };
1295
+ const total = typeof res.totalCount === "number" ? res.totalCount : 0;
1296
+ if ((page + 1) * size < total) {
1297
+ result.nextCursor = String(page + 1);
1298
+ }
1299
+ return result;
1300
+ }
1301
+ packagePath(packageId) {
1302
+ return `/integration/order/sellers/${this.sellerId}/shipment-packages/${encodeURIComponent(String(packageId))}`;
1303
+ }
469
1304
  };
470
1305
  var DEFAULT_PAGE_SIZE3 = 50;
471
1306
  var MAX_PAGE_SIZE2 = 1e3;
@@ -512,7 +1347,7 @@ function normalizeVariant(variant) {
512
1347
  if (stock !== void 0) out.stock = stock;
513
1348
  return out;
514
1349
  }
515
- function toIso2(epochMs) {
1350
+ function toIso3(epochMs) {
516
1351
  if (typeof epochMs !== "number" || !Number.isFinite(epochMs)) return void 0;
517
1352
  return new Date(epochMs).toISOString();
518
1353
  }
@@ -526,8 +1361,8 @@ function normalizeProduct(node) {
526
1361
  images: normalizeImages(node.images),
527
1362
  attributes: (node.attributes ?? []).map(normalizeAttribute2),
528
1363
  variants: (node.variants ?? []).map(normalizeVariant),
529
- createdAt: toIso2(node.creationDate) ?? "",
530
- updatedAt: toIso2(node.lastModifiedDate) ?? "",
1364
+ createdAt: toIso3(node.creationDate) ?? "",
1365
+ updatedAt: toIso3(node.lastModifiedDate) ?? "",
531
1366
  raw: node
532
1367
  };
533
1368
  if (node.description !== void 0) out.description = node.description;
@@ -573,13 +1408,13 @@ function normalizeUnapproved(node) {
573
1408
  out.specialConsumptionTax = node.specialConsumptionTax;
574
1409
  }
575
1410
  if (node.sgrPrice !== void 0) out.sgrPrice = node.sgrPrice;
576
- const createdAt = toIso2(node.createDateTime);
1411
+ const createdAt = toIso3(node.createDateTime);
577
1412
  if (createdAt) out.createdAt = createdAt;
578
- const updatedAt = toIso2(node.lastUpdateDate);
1413
+ const updatedAt = toIso3(node.lastUpdateDate);
579
1414
  if (updatedAt) out.updatedAt = updatedAt;
580
- const priceChanged = toIso2(node.lastPriceChangeDate);
1415
+ const priceChanged = toIso3(node.lastPriceChangeDate);
581
1416
  if (priceChanged) out.lastPriceChangedAt = priceChanged;
582
- const stockChanged = toIso2(node.lastStockChangeDate);
1417
+ const stockChanged = toIso3(node.lastStockChangeDate);
583
1418
  if (stockChanged) out.lastStockChangedAt = stockChanged;
584
1419
  return out;
585
1420
  }
@@ -604,7 +1439,7 @@ function normalizeProductBase(data) {
604
1439
  archived: !!data.archived,
605
1440
  raw: data
606
1441
  };
607
- const approvedAt = toIso2(data.approvedDate);
1442
+ const approvedAt = toIso3(data.approvedDate);
608
1443
  if (approvedAt) out.approvedAt = approvedAt;
609
1444
  if (data.listingId !== void 0) out.listingId = data.listingId;
610
1445
  if (data.contentId !== void 0) out.contentId = String(data.contentId);
@@ -626,9 +1461,9 @@ function normalizeBatchResult(data) {
626
1461
  };
627
1462
  if (typeof data.itemCount === "number") out.itemCount = data.itemCount;
628
1463
  if (typeof data.failedItemCount === "number") out.failedItemCount = data.failedItemCount;
629
- const createdAt = toIso2(data.creationDate);
1464
+ const createdAt = toIso3(data.creationDate);
630
1465
  if (createdAt) out.createdAt = createdAt;
631
- const lastModifiedAt = toIso2(data.lastModification);
1466
+ const lastModifiedAt = toIso3(data.lastModification);
632
1467
  if (lastModifiedAt) out.lastModifiedAt = lastModifiedAt;
633
1468
  if (data.sourceType !== void 0) out.sourceType = data.sourceType;
634
1469
  if (data.batchRequestType !== void 0) out.batchRequestType = data.batchRequestType;
@@ -974,6 +1809,120 @@ var ProductsResource = class {
974
1809
  return { batchRequestId: data?.batchRequestId ?? "" };
975
1810
  }
976
1811
  };
1812
+ function toIso4(epochMs) {
1813
+ if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
1814
+ return void 0;
1815
+ }
1816
+ return new Date(epochMs).toISOString();
1817
+ }
1818
+ function normalizeAnswer(a) {
1819
+ if (!a) return void 0;
1820
+ const out = {};
1821
+ if (a.text !== void 0) out.text = a.text;
1822
+ const createdAt = toIso4(a.creationDate);
1823
+ if (createdAt) out.createdAt = createdAt;
1824
+ if (a.status !== void 0) out.status = a.status;
1825
+ return out;
1826
+ }
1827
+ function normalizeQuestion(node) {
1828
+ const out = {
1829
+ id: node.id !== void 0 ? String(node.id) : "",
1830
+ raw: node
1831
+ };
1832
+ if (node.text !== void 0) out.text = node.text;
1833
+ if (node.customerId !== void 0) out.customerId = String(node.customerId);
1834
+ if (node.userName !== void 0) out.userName = node.userName;
1835
+ if (typeof node.showUserName === "boolean") out.showUserName = node.showUserName;
1836
+ if (node.status !== void 0) out.status = node.status;
1837
+ if (typeof node.public === "boolean") out.public = node.public;
1838
+ if (node.productMainId !== void 0) out.productMainId = node.productMainId;
1839
+ if (node.productName !== void 0) out.productName = node.productName;
1840
+ if (node.imageUrl !== void 0) out.imageUrl = node.imageUrl;
1841
+ if (node.webUrl !== void 0) out.webUrl = node.webUrl;
1842
+ const createdAt = toIso4(node.creationDate);
1843
+ if (createdAt) out.createdAt = createdAt;
1844
+ if (node.answeredDateMessage !== void 0) out.answeredDateMessage = node.answeredDateMessage;
1845
+ const answer = normalizeAnswer(node.answer);
1846
+ if (answer) out.answer = answer;
1847
+ const rejectedAnswer = normalizeAnswer(node.rejectedAnswer);
1848
+ if (rejectedAnswer) out.rejectedAnswer = rejectedAnswer;
1849
+ const rejectedAt = toIso4(node.rejectedDate);
1850
+ if (rejectedAt) out.rejectedAt = rejectedAt;
1851
+ if (node.reason !== void 0) out.reason = node.reason;
1852
+ if (node.reportReason !== void 0) out.reportReason = node.reportReason;
1853
+ const reportedAt = toIso4(node.reportedDate);
1854
+ if (reportedAt) out.reportedAt = reportedAt;
1855
+ return out;
1856
+ }
1857
+ var QuestionsResource = class {
1858
+ constructor(transport, sellerId, limiter) {
1859
+ this.transport = transport;
1860
+ this.sellerId = sellerId;
1861
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 500, intervalMs: 6e4 });
1862
+ }
1863
+ transport;
1864
+ sellerId;
1865
+ limiter;
1866
+ /** Fetch a single question by its numeric ID. */
1867
+ async get(questionId) {
1868
+ const data = await this.transport.request({
1869
+ method: "GET",
1870
+ path: `/integration/qna/sellers/${this.sellerId}/questions/${encodeURIComponent(String(questionId))}`,
1871
+ rateLimiter: this.limiter
1872
+ });
1873
+ return normalizeQuestion(data);
1874
+ }
1875
+ /**
1876
+ * Filter questions by barcode / date range / status. Page-based
1877
+ * pagination internally; SDK exposes the opaque-cursor convention.
1878
+ */
1879
+ async list(params = {}) {
1880
+ const size = Math.min(params.limit ?? 50, 200);
1881
+ const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
1882
+ const query = { page, size };
1883
+ if (params.barcode) query.barcode = params.barcode;
1884
+ if (params.startDate) query.startDate = params.startDate.getTime();
1885
+ if (params.endDate) query.endDate = params.endDate.getTime();
1886
+ if (params.status) query.status = params.status;
1887
+ const data = await this.transport.request({
1888
+ method: "GET",
1889
+ path: `/integration/qna/sellers/${this.sellerId}/questions/filter`,
1890
+ query,
1891
+ rateLimiter: this.limiter
1892
+ });
1893
+ const items = (data.content ?? []).map(normalizeQuestion);
1894
+ const result = { items };
1895
+ const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
1896
+ if (page + 1 < totalPages) {
1897
+ result.nextCursor = String(page + 1);
1898
+ }
1899
+ return result;
1900
+ }
1901
+ /**
1902
+ * Reply to a question. Trendyol enforces 10–2000 characters on the
1903
+ * answer text; the SDK pre-validates client-side.
1904
+ *
1905
+ * @throws {ValidationError} when `text` is outside the 10–2000 char range.
1906
+ */
1907
+ async answer(questionId, text) {
1908
+ if (typeof text !== "string" || text.length < 10) {
1909
+ throw new ValidationError({
1910
+ message: `questions.answer: text must be at least 10 chars (got ${text?.length ?? 0})`
1911
+ });
1912
+ }
1913
+ if (text.length > 2e3) {
1914
+ throw new ValidationError({
1915
+ message: `questions.answer: text must be at most 2000 chars (got ${text.length})`
1916
+ });
1917
+ }
1918
+ return this.transport.request({
1919
+ method: "POST",
1920
+ path: `/integration/qna/sellers/${this.sellerId}/questions/${encodeURIComponent(String(questionId))}/answers`,
1921
+ body: { text },
1922
+ rateLimiter: this.limiter
1923
+ });
1924
+ }
1925
+ };
977
1926
  var VALID_TYPES = /* @__PURE__ */ new Set([
978
1927
  "SHIPMENT",
979
1928
  "RETURNING",
@@ -1054,6 +2003,170 @@ var SuppliersResource = class {
1054
2003
  return items;
1055
2004
  }
1056
2005
  };
2006
+ var TestOrdersResource = class {
2007
+ constructor(transport, sellerId, limiter) {
2008
+ this.transport = transport;
2009
+ this.sellerId = sellerId;
2010
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 60, intervalMs: 6e4 });
2011
+ }
2012
+ transport;
2013
+ sellerId;
2014
+ limiter;
2015
+ /**
2016
+ * Create a test order with the given customer / addresses / lines. The
2017
+ * SDK forwards the typed payload verbatim — drill into Trendyol's
2018
+ * `createTestOrder` doc for inner field rules.
2019
+ *
2020
+ * @throws {ValidationError} when required top-level fields are missing.
2021
+ */
2022
+ async create(input) {
2023
+ for (const k of ["customer", "invoiceAddress", "shippingAddress", "seller", "lines"]) {
2024
+ if (!input?.[k]) {
2025
+ throw new ValidationError({ message: `testOrders.create: ${k} is required` });
2026
+ }
2027
+ }
2028
+ return this.transport.request({
2029
+ method: "POST",
2030
+ path: `/integration/test/order/orders/core`,
2031
+ body: input,
2032
+ rateLimiter: this.limiter
2033
+ });
2034
+ }
2035
+ /** Push a test shipment package to the given status. */
2036
+ async updateStatus(packageId, status) {
2037
+ return this.transport.request({
2038
+ method: "PUT",
2039
+ path: `/integration/test/order/sellers/${this.sellerId}/shipment-packages/${encodeURIComponent(String(packageId))}/status`,
2040
+ body: { status },
2041
+ rateLimiter: this.limiter
2042
+ });
2043
+ }
2044
+ /** Move test claims to the `WaitingInAction` state. */
2045
+ async setClaimsWaitingInAction() {
2046
+ return this.transport.request({
2047
+ method: "PUT",
2048
+ path: `/integration/test/order/sellers/${this.sellerId}/claims/waiting-in-action`,
2049
+ rateLimiter: this.limiter
2050
+ });
2051
+ }
2052
+ };
2053
+ function normalizeWebhook(node) {
2054
+ const out = {
2055
+ id: node.id !== void 0 ? String(node.id) : "",
2056
+ raw: node
2057
+ };
2058
+ if (node.url !== void 0) out.url = node.url;
2059
+ if (node.authenticationType !== void 0) out.authenticationType = node.authenticationType;
2060
+ if (node.username !== void 0) out.username = node.username;
2061
+ if (node.apiKey !== void 0) out.apiKey = node.apiKey;
2062
+ if (node.subscribedStatuses !== void 0) out.subscribedStatuses = node.subscribedStatuses;
2063
+ if (typeof node.active === "boolean") out.active = node.active;
2064
+ else if (typeof node.isActive === "boolean") out.active = node.isActive;
2065
+ else if (typeof node.status === "string") out.active = node.status.toUpperCase() === "ACTIVE";
2066
+ return out;
2067
+ }
2068
+ var WebhooksResource = class {
2069
+ constructor(transport, sellerId, limiter) {
2070
+ this.transport = transport;
2071
+ this.sellerId = sellerId;
2072
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 50, intervalMs: 6e4 });
2073
+ }
2074
+ transport;
2075
+ sellerId;
2076
+ limiter;
2077
+ /**
2078
+ * Create a new webhook subscription. Trendyol caps subscriptions at 15
2079
+ * per seller — the SDK does NOT pre-check (you'd need to call `list()`
2080
+ * first), but Trendyol returns 400 when the cap is exceeded.
2081
+ *
2082
+ * @throws {ValidationError} when `url` or `authenticationType` is missing.
2083
+ */
2084
+ async create(input) {
2085
+ this.validateInput(input, "create");
2086
+ return this.transport.request({
2087
+ method: "POST",
2088
+ path: `/integration/sellers/${this.sellerId}/webhooks`,
2089
+ body: input,
2090
+ rateLimiter: this.limiter
2091
+ });
2092
+ }
2093
+ /** List all registered webhook subscriptions. */
2094
+ async list() {
2095
+ const data = await this.transport.request({
2096
+ method: "GET",
2097
+ path: `/integration/sellers/${this.sellerId}/webhooks`,
2098
+ rateLimiter: this.limiter
2099
+ });
2100
+ const rows = Array.isArray(data) ? data : Array.isArray(data?.webhooks) ? data.webhooks : Array.isArray(data?.content) ? data.content : [];
2101
+ return rows.map((r) => normalizeWebhook(r));
2102
+ }
2103
+ /**
2104
+ * Update a webhook subscription. Same input shape as `create`; replaces
2105
+ * the whole subscription (Trendyol does NOT partially update).
2106
+ */
2107
+ async update(webhookId, input) {
2108
+ this.validateInput(input, "update");
2109
+ return this.transport.request({
2110
+ method: "PUT",
2111
+ path: this.webhookPath(webhookId),
2112
+ body: input,
2113
+ rateLimiter: this.limiter
2114
+ });
2115
+ }
2116
+ /** Permanently delete a webhook subscription. */
2117
+ async delete(webhookId) {
2118
+ return this.transport.request({
2119
+ method: "DELETE",
2120
+ path: this.webhookPath(webhookId),
2121
+ rateLimiter: this.limiter
2122
+ });
2123
+ }
2124
+ /** Re-activate a previously-deactivated webhook subscription. */
2125
+ async activate(webhookId) {
2126
+ return this.transport.request({
2127
+ method: "PUT",
2128
+ path: `${this.webhookPath(webhookId)}/activate`,
2129
+ rateLimiter: this.limiter
2130
+ });
2131
+ }
2132
+ /**
2133
+ * Deactivate a webhook subscription. Trendyol automatically deactivates
2134
+ * a subscription after persistent delivery failures (and sends 2 emails);
2135
+ * use `activate()` to bring it back online once your endpoint is healthy.
2136
+ */
2137
+ async deactivate(webhookId) {
2138
+ return this.transport.request({
2139
+ method: "PUT",
2140
+ path: `${this.webhookPath(webhookId)}/deactivate`,
2141
+ rateLimiter: this.limiter
2142
+ });
2143
+ }
2144
+ validateInput(input, method) {
2145
+ if (!input?.url || typeof input.url !== "string") {
2146
+ throw new ValidationError({ message: `webhooks.${method}: url is required` });
2147
+ }
2148
+ if (input.authenticationType !== "BASIC_AUTHENTICATION" && input.authenticationType !== "API_KEY") {
2149
+ throw new ValidationError({
2150
+ message: `webhooks.${method}: authenticationType must be 'BASIC_AUTHENTICATION' or 'API_KEY'`
2151
+ });
2152
+ }
2153
+ if (input.authenticationType === "BASIC_AUTHENTICATION") {
2154
+ if (!input.username || !input.password) {
2155
+ throw new ValidationError({
2156
+ message: `webhooks.${method}: BASIC_AUTHENTICATION requires username + password`
2157
+ });
2158
+ }
2159
+ } else if (!input.apiKey) {
2160
+ throw new ValidationError({
2161
+ message: `webhooks.${method}: API_KEY requires apiKey`
2162
+ });
2163
+ }
2164
+ if (input.subscribedStatuses && input.subscribedStatuses.length === 0) ;
2165
+ }
2166
+ webhookPath(webhookId) {
2167
+ return `/integration/sellers/${this.sellerId}/webhooks/${encodeURIComponent(String(webhookId))}`;
2168
+ }
2169
+ };
1057
2170
 
1058
2171
  // src/auth.ts
1059
2172
  function buildAuthHeader(apiKey, apiSecret) {
@@ -1146,7 +2259,12 @@ var TrendyolTransport = class {
1146
2259
  signal: this.composeSignal(opts.signal)
1147
2260
  };
1148
2261
  if (opts.body !== void 0 && opts.method !== "GET") {
1149
- init.body = JSON.stringify(opts.body);
2262
+ if (opts.body instanceof FormData) {
2263
+ init.body = opts.body;
2264
+ delete headers["Content-Type"];
2265
+ } else {
2266
+ init.body = JSON.stringify(opts.body);
2267
+ }
1150
2268
  }
1151
2269
  this.logger.debug("trendyol.request", {
1152
2270
  method: opts.method,
@@ -1261,10 +2379,18 @@ function createTrendyolClient(opts) {
1261
2379
  suppliers: new SuppliersResource(transport, opts.sellerId),
1262
2380
  products: new ProductsResource(transport, opts.sellerId),
1263
2381
  inventory: new InventoryResource(transport, opts.sellerId),
1264
- orders: new OrdersResource(transport, opts.sellerId)
2382
+ orders: new OrdersResource(transport, opts.sellerId),
2383
+ claims: new ClaimsResource(transport, opts.sellerId),
2384
+ webhooks: new WebhooksResource(transport, opts.sellerId),
2385
+ questions: new QuestionsResource(transport, opts.sellerId),
2386
+ invoices: new InvoicesResource(transport, opts.sellerId),
2387
+ finance: new FinanceResource(transport, opts.sellerId),
2388
+ labels: new LabelsResource(transport, opts.sellerId),
2389
+ testOrders: new TestOrdersResource(transport, opts.sellerId),
2390
+ locations: new LocationsResource(transport)
1265
2391
  };
1266
2392
  }
1267
2393
 
1268
- export { BrandsResource, CategoriesResource, InventoryResource, OrdersResource, ProductsResource, SuppliersResource, createTrendyolClient };
2394
+ export { BrandsResource, CategoriesResource, ClaimsResource, FinanceResource, InventoryResource, InvoicesResource, LabelsResource, LocationsResource, OrdersResource, ProductsResource, QuestionsResource, SuppliersResource, TestOrdersResource, WebhooksResource, createTrendyolClient };
1269
2395
  //# sourceMappingURL=index.js.map
1270
2396
  //# sourceMappingURL=index.js.map