shipflow 0.3.1 → 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.
Files changed (35) hide show
  1. package/README.md +22 -0
  2. package/dist/carriers/aramex/adapter.d.ts.map +1 -1
  3. package/dist/carriers/aramex/index.js +12 -11
  4. package/dist/carriers/aramex/index.js.map +4 -4
  5. package/dist/carriers/aramex/mappers.d.ts +13 -5
  6. package/dist/carriers/aramex/mappers.d.ts.map +1 -1
  7. package/dist/carriers/aymakan/adapter.d.ts +17 -21
  8. package/dist/carriers/aymakan/adapter.d.ts.map +1 -1
  9. package/dist/carriers/aymakan/index.js +67 -53
  10. package/dist/carriers/aymakan/index.js.map +4 -4
  11. package/dist/carriers/aymakan/mappers.d.ts +2 -0
  12. package/dist/carriers/aymakan/mappers.d.ts.map +1 -1
  13. package/dist/carriers/aymakan/types.d.ts +1 -1
  14. package/dist/carriers/aymakan/types.d.ts.map +1 -1
  15. package/dist/carriers/smsaexpress/adapter.d.ts +43 -4
  16. package/dist/carriers/smsaexpress/adapter.d.ts.map +1 -1
  17. package/dist/carriers/smsaexpress/index.js +141 -25
  18. package/dist/carriers/smsaexpress/index.js.map +5 -5
  19. package/dist/carriers/smsaexpress/mappers.d.ts +12 -3
  20. package/dist/carriers/smsaexpress/mappers.d.ts.map +1 -1
  21. package/dist/carriers/smsaexpress/services.d.ts +3 -1
  22. package/dist/carriers/smsaexpress/services.d.ts.map +1 -1
  23. package/dist/core/city-resolver.d.ts +44 -0
  24. package/dist/core/city-resolver.d.ts.map +1 -0
  25. package/dist/core/http.d.ts +8 -0
  26. package/dist/core/http.d.ts.map +1 -1
  27. package/dist/core/retry.d.ts +8 -3
  28. package/dist/core/retry.d.ts.map +1 -1
  29. package/dist/{index-qnxj8bct.js → index-8cpdcbsf.js} +32 -6
  30. package/dist/{index-qnxj8bct.js.map → index-8cpdcbsf.js.map} +5 -5
  31. package/dist/index-dfx6rbm2.js +36 -0
  32. package/dist/index-dfx6rbm2.js.map +10 -0
  33. package/dist/index.js +3 -3
  34. package/dist/index.js.map +3 -3
  35. package/package.json +1 -1
@@ -1,13 +1,17 @@
1
+ import {
2
+ CITIES_CACHE_TTL,
3
+ CITIES_FAILURE_COOLDOWN,
4
+ findCityMatch
5
+ } from "../../index-dfx6rbm2.js";
1
6
  import {
2
7
  APIError,
3
8
  BaseCarrierAdapter,
4
9
  HttpClient,
5
- UnsupportedOperationError,
6
10
  ValidationError,
7
11
  WebhookVerificationError,
8
12
  validateCreateShipmentInput,
9
13
  validatePickupRequest
10
- } from "../../index-qnxj8bct.js";
14
+ } from "../../index-8cpdcbsf.js";
11
15
 
12
16
  // src/carriers/aymakan/services.ts
13
17
  var AymakanService = {
@@ -204,13 +208,14 @@ function mapTrackingEvent(event) {
204
208
  };
205
209
  }
206
210
  function mapTrackingResult(data) {
207
- const events = data.tracking_info.map(mapTrackingEvent);
208
- const latestEvent = events.length ? [...events].sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())[0] : undefined;
211
+ const events = (data.tracking_info ?? []).map(mapTrackingEvent);
212
+ const sortedEvents = events.length ? [...events].sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) : [];
213
+ const mostRecentRecognizedEvent = sortedEvents.find((e) => e.status !== "unknown");
209
214
  let status;
210
- if (latestEvent && latestEvent.status !== "unknown") {
211
- status = latestEvent.status;
215
+ if (mostRecentRecognizedEvent) {
216
+ status = mostRecentRecognizedEvent.status;
212
217
  } else {
213
- status = mapAymakanStatus(data.status) ?? mapAymakanStatusLabel(data.status) ?? latestEvent?.status ?? "unknown";
218
+ status = mapAymakanStatus(data.status) ?? mapAymakanStatusLabel(data.status) ?? "unknown";
214
219
  }
215
220
  return {
216
221
  trackingNumber: data.tracking_number,
@@ -266,12 +271,6 @@ function timingSafeEqual(a, b) {
266
271
  }
267
272
  function parseAymakanWebhook(payload, options) {
268
273
  const { headers = {}, queryParams = {}, config } = options ?? {};
269
- if (!payload || typeof payload !== "object" || !("tracking_number" in payload) || !("status" in payload)) {
270
- throw new ValidationError("Invalid webhook payload: missing required fields", {
271
- raw: payload
272
- });
273
- }
274
- const data = payload;
275
274
  if (config?.authHeader && config?.authValue) {
276
275
  const lowerKey = config.authHeader.toLowerCase();
277
276
  const headerValue = Object.entries(headers).find(([k]) => k.toLowerCase() === lowerKey)?.[1];
@@ -289,6 +288,12 @@ function parseAymakanWebhook(payload, options) {
289
288
  });
290
289
  }
291
290
  }
291
+ if (!payload || typeof payload !== "object" || !("tracking_number" in payload) || !("status" in payload)) {
292
+ throw new ValidationError("Invalid webhook payload: missing required fields", {
293
+ raw: payload
294
+ });
295
+ }
296
+ const data = payload;
292
297
  const eventType = data.event ?? "status_update";
293
298
  const timestamp = new Date(data.date_time);
294
299
  if (Number.isNaN(timestamp.getTime())) {
@@ -312,6 +317,22 @@ function parseAymakanWebhook(payload, options) {
312
317
  // src/carriers/aymakan/adapter.ts
313
318
  var AYMAKAN_SANDBOX_URL = "https://dev-api.aymakan.com.sa/v2";
314
319
  var AYMAKAN_PRODUCTION_URL = "https://api.aymakan.net/v2";
320
+ function extractNestedMessage(value) {
321
+ if (typeof value === "string")
322
+ return value;
323
+ if (value && typeof value === "object") {
324
+ const obj = value;
325
+ if (typeof obj.message === "string")
326
+ return obj.message;
327
+ if (typeof obj.response === "string")
328
+ return obj.response;
329
+ }
330
+ return;
331
+ }
332
+ function joinFieldErrors(errors) {
333
+ const parts = Object.values(errors).flat().filter((v) => typeof v === "string");
334
+ return parts.length ? parts.join("; ") : undefined;
335
+ }
315
336
  function aymakanErrorExtractor(json) {
316
337
  if (!json || typeof json !== "object") {
317
338
  return { hasError: false };
@@ -322,7 +343,7 @@ function aymakanErrorExtractor(json) {
322
343
  if (!hasError) {
323
344
  return { hasError: false };
324
345
  }
325
- const message = typeof obj.message === "string" && obj.message || typeof obj.response === "string" && obj.response || typeof obj.error === "string" && obj.error || undefined;
346
+ const message = typeof obj.message === "string" && obj.message || typeof obj.response === "string" && obj.response || typeof obj.error === "string" && obj.error || extractNestedMessage(obj.error) || (hasValidationErrors ? joinFieldErrors(obj.errors) : undefined) || undefined;
326
347
  return {
327
348
  hasError: true,
328
349
  message,
@@ -337,7 +358,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
337
358
  errorOpts = { errorExtractor: aymakanErrorExtractor };
338
359
  citiesCache = null;
339
360
  citiesCacheTime = 0;
340
- static CITIES_CACHE_TTL = 60 * 60 * 1000;
361
+ citiesFetchFailedAt = 0;
341
362
  constructor(config) {
342
363
  super(config);
343
364
  this.http = new HttpClient({
@@ -352,56 +373,51 @@ class AymakanAdapter extends BaseCarrierAdapter {
352
373
  return this.config.mode === "production" ? AYMAKAN_PRODUCTION_URL : AYMAKAN_SANDBOX_URL;
353
374
  }
354
375
  async ensureCitiesLoaded() {
355
- if (this.citiesCache && Date.now() - this.citiesCacheTime < AymakanAdapter.CITIES_CACHE_TTL) {
376
+ const now = Date.now();
377
+ if (this.citiesCache && now - this.citiesCacheTime < CITIES_CACHE_TTL) {
378
+ return;
379
+ }
380
+ if (now - this.citiesFetchFailedAt < CITIES_FAILURE_COOLDOWN) {
381
+ if (!this.citiesCache)
382
+ this.citiesCache = [];
356
383
  return;
357
384
  }
358
385
  try {
359
386
  this.citiesCache = await this.getCities();
360
- this.citiesCacheTime = Date.now();
387
+ this.citiesCacheTime = now;
361
388
  } catch {
389
+ this.citiesFetchFailedAt = now;
362
390
  if (!this.citiesCache)
363
391
  this.citiesCache = [];
364
392
  }
365
393
  }
366
- static normalizeArabic(text) {
367
- return text.trim().replace(/[\u064B-\u065F\u0670]/g, "").replace(/[أإآ]/g, "ا").replace(/ة/g, "ه");
368
- }
369
394
  resolveCity(inputCity) {
370
395
  if (!this.citiesCache || this.citiesCache.length === 0)
371
396
  return inputCity;
372
397
  const trimmed = inputCity.trim();
373
398
  if (!trimmed)
374
399
  return inputCity;
375
- const lower = trimmed.toLowerCase();
376
- const exactEn = this.citiesCache.find((c) => c.nameEn.toLowerCase() === lower);
377
- if (exactEn)
378
- return exactEn.nameEn;
379
- const exactAr = this.citiesCache.find((c) => c.nameAr === trimmed);
380
- if (exactAr)
381
- return exactAr.nameEn;
382
- const normalizedInput = AymakanAdapter.normalizeArabic(trimmed);
383
- const normalizedAr = this.citiesCache.find((c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === normalizedInput);
384
- if (normalizedAr)
385
- return normalizedAr.nameEn;
386
- const withoutAl = normalizedInput.startsWith("ال") ? normalizedInput.slice(2) : `ال${normalizedInput}`;
387
- const alMatch = this.citiesCache.find((c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === withoutAl);
388
- if (alMatch)
389
- return alMatch.nameEn;
390
- const containsEn = this.citiesCache.find((c) => c.nameEn.toLowerCase().includes(lower) || lower.includes(c.nameEn.toLowerCase()));
391
- if (containsEn)
392
- return containsEn.nameEn;
393
- return trimmed;
400
+ return findCityMatch(trimmed, this.citiesCache)?.nameEn ?? trimmed;
401
+ }
402
+ resolveCityStrict(inputCity, field) {
403
+ if (!this.citiesCache || this.citiesCache.length === 0)
404
+ return inputCity;
405
+ const match = findCityMatch(inputCity, this.citiesCache);
406
+ if (!match) {
407
+ throw new ValidationError(`Unknown ${field.replace(".city", "")} city "${inputCity}": not in Aymakan's supported city list`, { field });
408
+ }
409
+ return match.nameEn;
394
410
  }
395
411
  resolveCitiesInInput(input) {
396
412
  return {
397
413
  ...input,
398
414
  shipper: {
399
415
  ...input.shipper,
400
- city: this.resolveCity(input.shipper.city)
416
+ city: this.resolveCityStrict(input.shipper.city, "shipper.city")
401
417
  },
402
418
  consignee: {
403
419
  ...input.consignee,
404
- city: this.resolveCity(input.consignee.city)
420
+ city: this.resolveCityStrict(input.consignee.city, "consignee.city")
405
421
  }
406
422
  };
407
423
  }
@@ -452,7 +468,8 @@ class AymakanAdapter extends BaseCarrierAdapter {
452
468
  async updateDeliveryAddress(trackingNumber, address) {
453
469
  await this.ensureCitiesLoaded();
454
470
  const resolvedCity = this.resolveCity(address.city);
455
- const response = await this.http.post(`/shipping/update_delivery_address/${encodeURIComponent(trackingNumber)}`, {
471
+ const response = await this.http.post("/shipping/update/delivery_address", {
472
+ tracking: trackingNumber,
456
473
  delivery_name: address.name,
457
474
  delivery_email: address.email,
458
475
  delivery_city: resolvedCity,
@@ -460,9 +477,9 @@ class AymakanAdapter extends BaseCarrierAdapter {
460
477
  delivery_neighbourhood: address.neighbourhood,
461
478
  delivery_postcode: address.postalCode,
462
479
  delivery_country: address.countryCode,
463
- delivery_phone: address.phone
464
- });
465
- return response.success;
480
+ delivery_phone: sanitizePhone(address.phone)
481
+ }, this.errorOpts);
482
+ return response.success === true;
466
483
  }
467
484
  async track(trackingNumber) {
468
485
  const results = await this.trackMultiple([trackingNumber]);
@@ -528,7 +545,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
528
545
  return response.data.cities.map(mapCity);
529
546
  }
530
547
  async getTimeSlots(_city, date) {
531
- const response = await this.http.get(`/time_slots/${date}`);
548
+ const response = await this.http.get(`/time_slots/${encodeURIComponent(date)}`, this.errorOpts);
532
549
  if (!response.success || response.error || !response.data?.slots) {
533
550
  const msg = response.message ?? "No slots available";
534
551
  throw new APIError(msg, { carrier: "aymakan", raw: response });
@@ -571,7 +588,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
571
588
  return list.map(mapPickupResponse);
572
589
  }
573
590
  async getCities() {
574
- const response = await this.http.get("/cities");
591
+ const response = await this.http.get("/cities", this.errorOpts);
575
592
  if (!response.success) {
576
593
  throw new APIError("Failed to get cities", {
577
594
  carrier: "aymakan",
@@ -581,7 +598,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
581
598
  return response.data.cities.map(mapCity);
582
599
  }
583
600
  async getDropoffLocations() {
584
- const response = await this.http.get("/dropoff_locations");
601
+ const response = await this.http.get("/dropoff_locations", this.errorOpts);
585
602
  if (!response.success) {
586
603
  throw new APIError("Failed to get dropoff locations", {
587
604
  carrier: "aymakan",
@@ -682,14 +699,11 @@ class AymakanAdapter extends BaseCarrierAdapter {
682
699
  }
683
700
  async deleteCustomerAddress(id) {
684
701
  const response = await this.http.delete("/address/delete", { body: { id }, errorExtractor: aymakanErrorExtractor });
685
- return response.success;
702
+ return response.success === true;
686
703
  }
687
704
  parseWebhook(payload, options) {
688
705
  return parseAymakanWebhook(payload, options);
689
706
  }
690
- getRates() {
691
- throw new UnsupportedOperationError("aymakan", "getRates");
692
- }
693
707
  }
694
708
  export {
695
709
  parseAymakanWebhook,
@@ -699,4 +713,4 @@ export {
699
713
  AymakanAdapter
700
714
  };
701
715
 
702
- //# debugId=52369388ABAA8E1964756E2164756E21
716
+ //# debugId=C3DBDA3066CA8E3964756E2164756E21
@@ -3,10 +3,10 @@
3
3
  "sources": ["../src/carriers/aymakan/services.ts", "../src/carriers/aymakan/mappers.ts", "../src/carriers/aymakan/adapter.ts"],
4
4
  "sourcesContent": [
5
5
  "// file: src/carriers/aymakan/services.ts\n/**\n * Aymakan Service Type Codes\n * Use these constants for type-safe service selection.\n */\n\nexport const AymakanService = {\n /** Default ecommerce service */\n ECOMMERCE: \"ONP\",\n /** Documents or banking docs service */\n DOCUMENTS: \"DOC\",\n /** Same day delivery service */\n SAME_DAY: \"SDD\",\n /** Reverse pickup service */\n REVERSE_PICKUP: \"RVP\",\n /** Exchange service */\n EXCHANGE: \"EXH\",\n /** Lockers service */\n LOCKERS: \"LOC\",\n /** Heavy and bulky service */\n HEAVY: \"BLK\",\n /** Pallet service */\n PALLET: \"PLT\",\n /** Import express (International) */\n IMPORT_EXPRESS: \"IPX\",\n /** Export express (International) */\n EXPORT_EXPRESS: \"EPX\",\n} as const;\n\nexport type AymakanServiceType =\n (typeof AymakanService)[keyof typeof AymakanService];\n\n/**\n * Aymakan Status Codes\n * Maps carrier-specific codes to our unified status.\n */\nexport const AymakanStatusCodes = {\n // Core lifecycle\n \"AY-0001\": \"created\", // Shipment created / AWB created at origin\n \"AY-0002\": \"picked_up\", // Collected from collection point\n \"AY-0003\": \"at_warehouse\", // Received at hub\n \"AY-0004\": \"out_for_delivery\", // Out for final destination\n \"AY-0005\": \"delivered\", // Delivered to customer\n \"AY-0006\": \"exception\", // Not delivered (failed attempt)\n\n // Warehouse / transfer\n \"AY-0026\": \"at_warehouse\", // Received at [City] Warehouse\n \"AY-0027\": \"in_transit\", // Transferred to destination city\n \"AY-0028\": \"in_transit\", // In transit between hubs\n\n // Pending / rescheduled (delivery was attempted but rescheduled)\n \"AY-0032\": \"pending\", // Pending — future delivery / rescheduled\n\n // Terminal states\n \"AY-0007\": \"returned\", // Returned to shipper\n \"AY-0008\": \"cancelled\", // Cancelled\n \"AY-0009\": \"returned\", // Return to origin\n} as const;\n",
6
- "// file: src/carriers/aymakan/mappers.ts\n/**\n * Aymakan Data Mappers\n * Transform between unified ShipFlow types and Aymakan API formats.\n */\n\nimport { ValidationError, WebhookVerificationError } from \"../../core/errors\";\nimport type {\n Address,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { AymakanService, AymakanStatusCodes } from \"./services\";\nimport type {\n AymakanAddressRequest,\n AymakanCity,\n AymakanCreateShipmentRequest,\n AymakanNationalAddressRequest,\n AymakanPickupRequest,\n AymakanPickupResponse,\n AymakanShipmentResponse,\n AymakanTrackingEvent,\n AymakanTrackShipmentData,\n AymakanWebhookPayload,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapAymakanStatus(statusCode: string): ShipmentStatus | undefined {\n const mapped =\n AymakanStatusCodes[statusCode as keyof typeof AymakanStatusCodes];\n return mapped as ShipmentStatus | undefined;\n}\n\n/**\n * The /track endpoint's top-level `status` is a human-readable English label\n * (e.g. \"Received at Warehouse\") rather than an AY code, so `mapAymakanStatus`\n * can never resolve it. This map recovers a real unified status from that label\n * when there are no tracking events to derive it from. Keys are compared\n * case-insensitively.\n */\nconst AymakanStatusLabels: Record<string, ShipmentStatus> = {\n \"awb created at origin\": \"created\",\n \"shipment created\": \"created\",\n \"collected from collection point\": \"picked_up\",\n collected: \"picked_up\",\n \"picked up\": \"picked_up\",\n \"received at hub\": \"at_warehouse\",\n \"received at warehouse\": \"at_warehouse\",\n \"out for delivery\": \"out_for_delivery\",\n \"out for final destination\": \"out_for_delivery\",\n delivered: \"delivered\",\n \"shipment is delivered\": \"delivered\",\n \"transferred to destination city\": \"in_transit\",\n \"in transit\": \"in_transit\",\n \"not delivered\": \"exception\",\n \"failed attempt\": \"exception\",\n pending: \"pending\",\n \"future delivery\": \"pending\",\n returned: \"returned\",\n \"returned to shipper\": \"returned\",\n \"return to origin\": \"returned\",\n cancelled: \"cancelled\",\n canceled: \"cancelled\",\n};\n\n/**\n * Map a human-readable Aymakan status label to a unified status.\n * Returns undefined for unrecognized labels.\n */\nexport function mapAymakanStatusLabel(\n label: string,\n): ShipmentStatus | undefined {\n return AymakanStatusLabels[label.trim().toLowerCase()];\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\nfunction mapNationalAddress(\n addr?: Address[\"nationalAddress\"],\n): AymakanNationalAddressRequest | undefined {\n if (!addr) return undefined;\n return {\n short_code: addr.shortCode,\n building_number: addr.buildingNumber,\n street_name: addr.streetName,\n district: addr.district,\n additional_number: addr.additionalNumber,\n };\n}\n\n/** Strip non-digit characters from phone numbers (Aymakan requires digits only) */\nfunction sanitizePhone(phone: string): string {\n return phone.replace(/\\D/g, '');\n}\n\n/** Set of valid Aymakan service type codes */\nconst VALID_AYMAKAN_SERVICE_TYPES = new Set(\n Object.values(AymakanService) as string[],\n);\n\n/**\n * Resolve service type to a valid Aymakan code.\n * Returns undefined for unknown values so the API uses its default (ONP/ecommerce).\n */\nfunction resolveServiceType(serviceType?: string): string | undefined {\n if (!serviceType) return undefined;\n if (VALID_AYMAKAN_SERVICE_TYPES.has(serviceType)) return serviceType;\n return undefined;\n}\n\nexport function mapCreateShipmentRequest(\n input: CreateShipmentInput,\n): AymakanCreateShipmentRequest {\n const firstParcel = input.parcels[0];\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n const totalItems = input.parcels.reduce(\n (sum, p) => sum + (p.itemsCount ?? p.pieces),\n 0,\n );\n\n // COD (collected from the buyer on delivery) and declared/customs value are\n // distinct concepts and must never be conflated. Declared value comes solely\n // from input.declaredValue; COD comes solely from input.cod.\n const codEnabled = input.cod?.enabled === true;\n const declaredValueCurrency = input.declaredValue?.currency ?? \"SAR\";\n\n return {\n requested_by: input.options?.requestedBy ?? input.shipper.name,\n declared_value: input.declaredValue?.amount ?? 0,\n declared_value_currency: declaredValueCurrency,\n reference: input.reference,\n customer_tracking: input.options?.customerTracking,\n service_type: resolveServiceType(input.serviceType),\n is_cod: codEnabled ? 1 : 0,\n cod_amount: codEnabled ? input.cod?.amount : undefined,\n fulfilment_customer_name: input.options?.fulfilmentCustomerName,\n // Top-level currency prefers the COD currency when COD is enabled (it's the\n // amount actually collected), otherwise the declared-value currency.\n currency: codEnabled\n ? (input.cod?.currency ?? \"SAR\")\n : declaredValueCurrency,\n\n // Delivery (consignee)\n delivery_name: input.consignee.name,\n delivery_email: input.consignee.email,\n delivery_city: input.consignee.city,\n delivery_address: input.consignee.line1,\n delivery_neighbourhood:\n input.consignee.neighbourhood ?? input.consignee.state,\n delivery_postcode: input.consignee.postalCode,\n delivery_country: input.consignee.countryCode,\n delivery_phone: sanitizePhone(input.consignee.phone),\n delivery_description: input.consignee.description,\n delivery_national_address: mapNationalAddress(\n input.consignee.nationalAddress,\n ),\n\n // Collection (shipper)\n collection_name: input.shipper.company ?? input.shipper.name,\n collection_email: input.shipper.email,\n collection_city: input.shipper.city,\n collection_address: input.shipper.line1,\n collection_neighbourhood:\n input.shipper.neighbourhood ?? input.shipper.state,\n collection_postcode: input.shipper.postalCode,\n collection_country: input.shipper.countryCode,\n collection_phone: sanitizePhone(input.shipper.phone),\n collection_description: input.shipper.description,\n collection_national_address: mapNationalAddress(\n input.shipper.nationalAddress,\n ),\n\n // Parcel details\n weight: totalWeight,\n length: firstParcel?.dimensions?.length,\n width: firstParcel?.dimensions?.width,\n height: firstParcel?.dimensions?.height,\n pieces: totalPieces,\n items_count: totalItems,\n is_insured: input.options?.isInsured ? 1 : 0,\n\n // International\n international_metadata: input.options?.internationalMetadata\n ? {\n document_id: input.options.internationalMetadata.documentId,\n tax_identification_number: input.options.internationalMetadata.taxId,\n invoice_number: input.options.internationalMetadata.invoiceNumber,\n invoice_date: input.options.internationalMetadata.invoiceDate,\n }\n : undefined,\n };\n}\n\nexport function mapPickupRequest(input: PickupRequest): AymakanPickupRequest {\n return {\n reference: input.trackingNumbers?.[0],\n pickup_date: input.date,\n time_slot: input.timeSlot,\n city: input.city,\n contact_name: input.contactName,\n contact_phone: input.contactPhone,\n address: input.address,\n shipments: input.shipmentCount,\n };\n}\n\nexport function mapCustomerAddressRequest(\n addr: CustomerAddress,\n): AymakanAddressRequest {\n return {\n title: addr.title,\n name: addr.name,\n email: addr.email,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postcode: addr.postalCode ?? \"\",\n phone: addr.phone,\n description: addr.description ?? \"\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(data: AymakanShipmentResponse): Shipment {\n // Parse cod_amount: API returns string like \"150.00\", we need number\n const codAmount =\n data.cod_amount != null\n ? typeof data.cod_amount === \"string\"\n ? parseFloat(data.cod_amount)\n : data.cod_amount\n : undefined;\n\n return {\n carrier: \"aymakan\",\n trackingNumber: data.tracking_number,\n customerTracking: data.customer_tracking ?? undefined,\n reference: data.reference ?? undefined,\n status: mapAymakanStatus(data.status) ?? \"created\",\n statusLabel: data.status_label,\n labelUrl: data.label || undefined,\n pdfLabelUrl: data.pdf_label || undefined,\n // Emit a defined COD amount whenever we parsed a real number. A COD of 0\n // means \"no cash collected\", so surface it as undefined, but never let NaN\n // logic on a falsy 0 drop an otherwise valid value.\n codAmount:\n codAmount != null && !Number.isNaN(codAmount) && codAmount !== 0\n ? codAmount\n : undefined,\n declaredValue: data.declared_value,\n currency: data.currency,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n/**\n * Parse an Aymakan timestamp. Top-level fields are ISO 8601 (with `T`/`Z`), but\n * `tracking_info[].created_at` values are space-separated \"YYYY-MM-DD HH:MM:SS\"\n * strings in Saudi local time (UTC+3) with no timezone marker. `new Date()`\n * would parse those in the host's local timezone, so normalize to +03:00.\n */\nfunction parseAymakanDate(value: string): Date {\n if (/[TZ]|[+-]\\d{2}:?\\d{2}$/.test(value)) {\n return new Date(value);\n }\n return new Date(`${value.replace(\" \", \"T\")}+03:00`);\n}\n\nexport function mapTrackingEvent(event: AymakanTrackingEvent): TrackingEvent {\n return {\n timestamp: parseAymakanDate(event.created_at),\n statusCode: event.status_code,\n status: mapAymakanStatus(event.status_code) ?? \"unknown\",\n description: event.description,\n descriptionArabic: event.description_ar ?? undefined,\n };\n}\n\nexport function mapTrackingResult(\n data: AymakanTrackShipmentData,\n): TrackingResult {\n const events = data.tracking_info.map(mapTrackingEvent);\n\n // The top-level `status` field is a human-readable label on the /track\n // endpoint (e.g. \"Received at Warehouse\") and an AY code on /by_reference.\n // Derive the unified status from the most recent tracking event (whose\n // status_code is always an AY code), falling back to mapping the top-level\n // status. The fallback tries the AY-code map first (handles /by_reference)\n // and then the human-label map (handles /track), so a real state is recovered\n // even when there are no events, then \"unknown\".\n const latestEvent = events.length\n ? [...events].sort(\n (a, b) => b.timestamp.getTime() - a.timestamp.getTime(),\n )[0]\n : undefined;\n let status: ShipmentStatus;\n if (latestEvent && latestEvent.status !== \"unknown\") {\n status = latestEvent.status;\n } else {\n status =\n mapAymakanStatus(data.status) ??\n mapAymakanStatusLabel(data.status) ??\n latestEvent?.status ??\n \"unknown\";\n }\n\n return {\n trackingNumber: data.tracking_number,\n carrier: \"aymakan\",\n reference: data.reference ?? undefined,\n status,\n statusLabel: data.status_label,\n events,\n deliveryDate: data.delivery_date ? new Date(data.delivery_date) : undefined,\n pickupDate: data.pickup_date ? new Date(data.pickup_date) : undefined,\n receivedAt: data.received_at ? new Date(data.received_at) : undefined,\n codAmount: data.cod_amount\n ? Number.isNaN(parseFloat(data.cod_amount))\n ? undefined\n : parseFloat(data.cod_amount)\n : undefined,\n weight: Number.isNaN(parseFloat(data.weight))\n ? undefined\n : parseFloat(data.weight),\n pieces: data.pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: AymakanCity): City {\n return {\n nameEn: city.city_en,\n nameAr: city.city_ar,\n };\n}\n\nexport function mapPickupResponse(data: AymakanPickupResponse[\"data\"]): Pickup {\n return {\n id: data.id,\n carrier: \"aymakan\",\n status: (\n [\"pending\", \"processing\", \"completed\", \"cancelled\"] as const\n ).includes(data.status as Pickup[\"status\"])\n ? (data.status as Pickup[\"status\"])\n : \"pending\",\n date: data.pickup_date,\n timeSlot: data.time_slot,\n city: data.city,\n contactName: data.contact_name,\n contactPhone: data.contact_phone,\n address: data.address,\n shipmentCount: data.shipments,\n warehouseId: data.warehouse_id,\n warehouseName: data.warehouse_name,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPER\n// ============================================================================\n\n/**\n * Timing-safe string comparison to prevent timing attacks on auth tokens.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n const encoder = new TextEncoder();\n const bufA = encoder.encode(a);\n const bufB = encoder.encode(b);\n if (bufA.byteLength !== bufB.byteLength) return false;\n // Use crypto.subtle-compatible constant-time compare\n let mismatch = 0;\n for (let i = 0; i < bufA.byteLength; i++) {\n mismatch |= bufA[i]! ^ bufB[i]!;\n }\n return mismatch === 0;\n}\n\nexport function parseAymakanWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n const { headers = {}, queryParams = {}, config } = options ?? {};\n\n // Validate required fields\n if (\n !payload ||\n typeof payload !== \"object\" ||\n !(\"tracking_number\" in payload) ||\n !(\"status\" in payload)\n ) {\n throw new ValidationError(\n \"Invalid webhook payload: missing required fields\",\n {\n raw: payload,\n },\n );\n }\n\n const data = payload as AymakanWebhookPayload;\n\n // Verify auth via header (case-insensitive lookup, timing-safe comparison)\n if (config?.authHeader && config?.authValue) {\n const lowerKey = config.authHeader.toLowerCase();\n const headerValue = Object.entries(headers).find(\n ([k]) => k.toLowerCase() === lowerKey,\n )?.[1];\n if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth header\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n // Verify auth via query param (timing-safe comparison)\n if (config?.authQueryParam && config?.authQueryValue) {\n const paramValue = queryParams[config.authQueryParam];\n if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth query param\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n const eventType = data.event ?? \"status_update\";\n\n // Validate timestamp\n const timestamp = new Date(data.date_time);\n if (Number.isNaN(timestamp.getTime())) {\n throw new ValidationError(\n \"Invalid webhook payload: invalid date_time value\",\n { raw: data.date_time },\n );\n }\n\n return {\n carrier: \"aymakan\",\n eventType,\n trackingNumber: data.tracking_number,\n reference: data.reference ?? undefined,\n // `status` is a core field present on every webhook regardless of event\n // type, so map it even for weight_update events. Consumers can still branch\n // on `eventType` to detect weight updates.\n status: mapAymakanStatus(data.status) ?? \"unknown\",\n statusCode: data.status,\n statusLabel: data.status_label,\n reasonCode: data.reason_code ?? undefined,\n reasonLabel: data.reason_en ?? undefined,\n timestamp,\n raw: data,\n };\n}\n",
7
- "// file: src/carriers/aymakan/adapter.ts\n/**\n * Aymakan Carrier Adapter\n * Full implementation of the CarrierAdapter interface for Aymakan API.\n */\n\nimport {\n APIError,\n UnsupportedOperationError,\n ValidationError,\n} from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport {\n validateCreateShipmentInput,\n validatePickupRequest,\n} from \"../../core/schemas\";\nimport type {\n Address,\n CarrierConfig,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n TimeSlot,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n mapCity,\n mapCreateShipmentRequest,\n mapCustomerAddressRequest,\n mapPickupRequest,\n mapPickupResponse,\n mapShipmentResponse,\n mapTrackingResult,\n parseAymakanWebhook,\n} from \"./mappers\";\nimport type {\n AymakanAddressResponse,\n AymakanCancelResponse,\n AymakanCitiesResponse,\n AymakanCreateShipmentResponse,\n AymakanPickupResponse,\n AymakanTrackResponse,\n} from \"./types\";\n\nconst AYMAKAN_SANDBOX_URL = \"https://dev-api.aymakan.com.sa/v2\";\nconst AYMAKAN_PRODUCTION_URL = \"https://api.aymakan.net/v2\";\n\n/**\n * Aymakan returns logical errors inside a fake-200 envelope (HTTP 200 with an\n * error flag/message in the body). Without an extractor these are swallowed —\n * e.g. cancel endpoints would return `success: undefined` and the adapter would\n * report `false` with no reason. This extractor surfaces those as APIError.\n *\n * Recognised error shapes (all on a 200 body):\n * - `{ error: true, ... }` → Laravel/Aymakan error flag\n * - `{ success: false, ... }` → explicit failure flag\n * - `{ message }` / `{ response }` → human-readable error text\n * - `{ errors: { field: [...] } }` → Laravel field validation errors\n *\n * A bare `{ message }` is NOT treated as an error on its own (many success\n * envelopes carry a `message`); we only flag when `error`/`success:false` is\n * present, or validation `errors` exist.\n */\nfunction aymakanErrorExtractor(json: unknown): {\n hasError: boolean;\n message?: string;\n errors?: Record<string, string[]>;\n} {\n if (!json || typeof json !== \"object\") {\n return { hasError: false };\n }\n const obj = json as Record<string, unknown>;\n\n const hasValidationErrors =\n !!obj.errors &&\n typeof obj.errors === \"object\" &&\n Object.keys(obj.errors as object).length > 0;\n const hasError = obj.error === true || obj.success === false || hasValidationErrors;\n\n if (!hasError) {\n return { hasError: false };\n }\n\n const message =\n (typeof obj.message === \"string\" && obj.message) ||\n (typeof obj.response === \"string\" && obj.response) ||\n (typeof obj.error === \"string\" && obj.error) ||\n undefined;\n\n return {\n hasError: true,\n message,\n errors: hasValidationErrors\n ? (obj.errors as Record<string, string[]>)\n : undefined,\n };\n}\n\nexport interface AymakanConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class AymakanAdapter extends BaseCarrierAdapter {\n readonly name = \"aymakan\";\n readonly supportedCountries = [\"SA\", \"AE\", \"BH\", \"KW\", \"OM\", \"QA\"];\n\n private http: HttpClient;\n\n /**\n * Shared options applied to every Aymakan http call so fake-200 error\n * envelopes surface as APIError instead of being silently swallowed.\n */\n private readonly errorOpts = { errorExtractor: aymakanErrorExtractor };\n\n /** Cached Aymakan cities list for city name resolution */\n private citiesCache: City[] | null = null;\n private citiesCacheTime = 0;\n /** Cache TTL: 1 hour */\n private static readonly CITIES_CACHE_TTL = 60 * 60 * 1000;\n\n constructor(config: AymakanConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"aymakan\",\n headers: {\n Authorization: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? AYMAKAN_PRODUCTION_URL\n : AYMAKAN_SANDBOX_URL;\n }\n\n // =========================================================================\n // CITY RESOLUTION\n // =========================================================================\n\n /**\n * Load cities list from Aymakan API and cache it.\n * Silently falls back to empty list on failure so shipments can still be attempted.\n */\n private async ensureCitiesLoaded(): Promise<void> {\n if (\n this.citiesCache &&\n Date.now() - this.citiesCacheTime < AymakanAdapter.CITIES_CACHE_TTL\n ) {\n return;\n }\n try {\n this.citiesCache = await this.getCities();\n this.citiesCacheTime = Date.now();\n } catch {\n if (!this.citiesCache) this.citiesCache = [];\n }\n }\n\n /**\n * Normalize Arabic text for comparison:\n * - Strip diacritics (tashkeel)\n * - Normalize alef variants (أ إ آ → ا)\n * - Normalize taa marbuta (ة → ه)\n * - Trim whitespace\n */\n private static normalizeArabic(text: string): string {\n return text\n .trim()\n .replace(/[\\u064B-\\u065F\\u0670]/g, \"\") // strip tashkeel\n .replace(/[أإآ]/g, \"ا\") // normalize alef\n .replace(/ة/g, \"ه\"); // normalize taa marbuta\n }\n\n /**\n * Resolve a user-input city name to the valid Aymakan English city name.\n *\n * Matching strategy (first match wins):\n * 1. Exact match on English name (case-insensitive)\n * 2. Exact match on Arabic name\n * 3. Normalized Arabic match (handles tashkeel, alef variants, taa marbuta)\n * 4. Match after stripping \"ال\" prefix from Arabic input\n * 5. Substring/contains match on English name (case-insensitive)\n *\n * Falls back to the original input if no match is found.\n */\n private resolveCity(inputCity: string): string {\n if (!this.citiesCache || this.citiesCache.length === 0) return inputCity;\n\n const trimmed = inputCity.trim();\n if (!trimmed) return inputCity;\n const lower = trimmed.toLowerCase();\n\n // 1. Exact English match (case-insensitive)\n const exactEn = this.citiesCache.find(\n (c) => c.nameEn.toLowerCase() === lower,\n );\n if (exactEn) return exactEn.nameEn;\n\n // 2. Exact Arabic match\n const exactAr = this.citiesCache.find((c) => c.nameAr === trimmed);\n if (exactAr) return exactAr.nameEn;\n\n // 3. Normalized Arabic match\n const normalizedInput = AymakanAdapter.normalizeArabic(trimmed);\n const normalizedAr = this.citiesCache.find(\n (c) =>\n c.nameAr &&\n AymakanAdapter.normalizeArabic(c.nameAr) === normalizedInput,\n );\n if (normalizedAr) return normalizedAr.nameEn;\n\n // 4. Arabic without \"ال\" prefix\n const withoutAl = normalizedInput.startsWith(\"ال\")\n ? normalizedInput.slice(2)\n : `ال${normalizedInput}`;\n const alMatch = this.citiesCache.find(\n (c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === withoutAl,\n );\n if (alMatch) return alMatch.nameEn;\n\n // 5. Contains match on English name (for partial matches like \"Riyadh\" in \"Riyadh City\")\n const containsEn = this.citiesCache.find(\n (c) =>\n c.nameEn.toLowerCase().includes(lower) ||\n lower.includes(c.nameEn.toLowerCase()),\n );\n if (containsEn) return containsEn.nameEn;\n\n // No match — return as-is and let the API return a validation error\n return trimmed;\n }\n\n /**\n * Resolve city names in a CreateShipmentInput to valid Aymakan city names.\n */\n private resolveCitiesInInput(\n input: CreateShipmentInput,\n ): CreateShipmentInput {\n return {\n ...input,\n shipper: {\n ...input.shipper,\n city: this.resolveCity(input.shipper.city),\n },\n consignee: {\n ...input.consignee,\n city: this.resolveCity(input.consignee.city),\n },\n };\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n await this.ensureCitiesLoaded();\n const resolved = this.resolveCitiesInInput(input);\n const request = mapCreateShipmentRequest(resolved);\n const response = await this.http.post<AymakanCreateShipmentResponse>(\n \"/shipping/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create shipment\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapShipmentResponse(response.shipping);\n }\n\n async createBulkShipments(\n inputs: CreateShipmentInput[],\n ): Promise<Shipment[]> {\n // Nothing to create — avoid an empty request to the carrier.\n if (inputs.length === 0) return [];\n // Aymakan rejects batches larger than 30 (\"Only 30 shipments can be\n // created at a time.\"), so fail fast with a clear error before the request.\n if (inputs.length > 30) {\n throw new ValidationError(\n \"Aymakan bulk create accepts at most 30 shipments per request\",\n { raw: { count: inputs.length } },\n );\n }\n inputs.forEach(validateCreateShipmentInput);\n await this.ensureCitiesLoaded();\n const requests = inputs\n .map((i) => this.resolveCitiesInInput(i))\n .map(mapCreateShipmentRequest);\n const response = await this.http.post<{\n success: boolean;\n message?: string;\n bulk_awb?: string;\n total_shipments?: number;\n shipments: AymakanCreateShipmentResponse[\"shipping\"][];\n }>(\"/shipping/create/bulk_shipping\", { shipments: requests }, this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to create bulk shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.shipments)) {\n throw new APIError(\n \"Aymakan bulk create response is missing the shipments array\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return response.shipments.map(mapShipmentResponse);\n }\n\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n // The errorExtractor surfaces fake-200 error envelopes as APIError, so a\n // real failure throws with the carrier's message rather than silently\n // returning false.\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel\",\n {\n tracking: trackingNumber,\n },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async cancelByReference(reference: string): Promise<boolean> {\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel_by_reference\",\n { reference },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async updateDeliveryAddress(\n trackingNumber: string,\n address: Address,\n ): Promise<boolean> {\n await this.ensureCitiesLoaded();\n const resolvedCity = this.resolveCity(address.city);\n const response = await this.http.post<{ success: boolean }>(\n `/shipping/update_delivery_address/${encodeURIComponent(trackingNumber)}`,\n {\n delivery_name: address.name,\n delivery_email: address.email,\n delivery_city: resolvedCity,\n delivery_address: address.line1,\n delivery_neighbourhood: address.neighbourhood,\n delivery_postcode: address.postalCode,\n delivery_country: address.countryCode,\n delivery_phone: address.phone,\n },\n );\n return response.success;\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const results = await this.trackMultiple([trackingNumber]);\n const result = results[0];\n if (!result) {\n throw new APIError(\"Shipment not found\", { carrier: \"aymakan\" });\n }\n return result;\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/track/${ids}`,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to track shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.data?.shipments)) {\n throw new APIError(\"Aymakan track response is missing data.shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.shipments.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/by_reference/${encodeURIComponent(reference)}`,\n this.errorOpts,\n );\n\n const shipment = response.data?.shipments?.[0];\n if (!response.success || !shipment) {\n throw new APIError(\"Shipment not found\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapTrackingResult(shipment);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"ZPL\" | \"PNG\",\n ): Promise<string> {\n // Note: this endpoint always returns a single PDF download URL, so the\n // requested `format` cannot be honored.\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { awb_url: string };\n }>(\n `/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}`,\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.awb_url) {\n const msg = response.message ?? response.response ?? \"Failed to get label\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.awb_url;\n }\n\n async getBulkLabels(trackingNumbers: string[]): Promise<string> {\n // Documented as a GET with comma-separated tracking numbers in the path.\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { bulk_awb_url: string };\n }>(`/shipping/bulk_awb/trackings/${ids}`, this.errorOpts);\n\n if (!response.success || !response.data?.bulk_awb_url) {\n const msg =\n response.message ?? response.response ?? \"Failed to get bulk labels\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.bulk_awb_url;\n }\n\n // =========================================================================\n // PICKUPS\n // =========================================================================\n\n async getPickupCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\n \"/pickup_request/cities\",\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getTimeSlots(_city: string, date: string): Promise<TimeSlot[]> {\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n data: {\n date: string;\n slots: Record<string, string>;\n };\n }>(`/time_slots/${date}`);\n\n // Aymakan returns error responses for invalid dates (past, Fridays, etc.)\n if (!response.success || response.error || !response.data?.slots) {\n const msg = response.message ?? \"No slots available\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n // data.slots is an object like { \"afternoon\": \"After Noon (02 PM - 06 PM)\" }\n return Object.entries(response.data.slots).map(([id, label]) => ({\n id,\n label,\n }));\n }\n\n async createPickup(input: PickupRequest): Promise<Pickup> {\n validatePickupRequest(input);\n await this.ensureCitiesLoaded();\n const resolvedInput = { ...input, city: this.resolveCity(input.city) };\n const request = mapPickupRequest(resolvedInput);\n const response = await this.http.post<AymakanPickupResponse>(\n \"/pickup_request/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create pickup\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapPickupResponse(response.data);\n }\n\n async cancelPickup(pickupId: string | number): Promise<boolean> {\n const response = await this.http.post<{ success: boolean }>(\n \"/pickup_request/cancel\",\n { pickup_request: Number(pickupId) },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async getPickupRequests(): Promise<Pickup[]> {\n // The list endpoint returns a Laravel paginated structure:\n // { success, data: { pickupRequests: { data: [...] } } }\n const response = await this.http.get<{\n success: boolean;\n data: { pickupRequests: { data: AymakanPickupResponse[\"data\"][] } };\n }>(\"/pickup_request/list\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup requests\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const list = response.data?.pickupRequests?.data;\n if (!Array.isArray(list)) {\n throw new APIError(\n \"Aymakan pickup list response is missing data.pickupRequests.data\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return list.map(mapPickupResponse);\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\"/cities\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<\n {\n id: string;\n name: string;\n address?: string;\n city?: string;\n latitude?: number;\n longitude?: number;\n }[]\n > {\n // The API returns `data` as a flat array of warehouse objects. There is no\n // `id` field — the warehouse code lives in `name` (e.g. \"RUH-WH\").\n const response = await this.http.get<{\n success: boolean;\n data: Array<{\n name: string;\n city?: string;\n address?: string;\n manager?: string;\n mobile_phone?: string;\n email?: string;\n location_lat?: number | null;\n location_lng?: number | null;\n }>;\n }>(\"/dropoff_locations\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get dropoff locations\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.map((loc) => ({\n id: loc.name,\n name: loc.name,\n address: loc.address,\n city: loc.city,\n latitude: loc.location_lat ?? undefined,\n longitude: loc.location_lng ?? undefined,\n }));\n }\n\n // =========================================================================\n // CUSTOMER ADDRESSES\n // =========================================================================\n\n async createCustomerAddress(\n address: CustomerAddress,\n ): Promise<CustomerAddress> {\n const request = mapCustomerAddressRequest(address);\n const response = await this.http.post<AymakanAddressResponse>(\n \"/address/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.address) {\n throw new APIError(\"Failed to create address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async getCustomerAddresses(): Promise<CustomerAddress[]> {\n const response = await this.http.get<{\n success: boolean;\n data: { address: AymakanAddressResponse[\"data\"][\"address\"][] };\n }>(\"/address/list\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get customer addresses\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.data?.address)) {\n throw new APIError(\n \"Aymakan address list response is missing data.address\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return response.data.address.map((addr) => ({\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n }));\n }\n\n async updateCustomerAddress(\n id: number,\n address: Partial<CustomerAddress>,\n ): Promise<CustomerAddress> {\n const response = await this.http.put<AymakanAddressResponse>(\n \"/address/update\",\n {\n id,\n title: address.title,\n name: address.name,\n email: address.email,\n city: address.city,\n address: address.address,\n neighbourhood: address.neighbourhood,\n postcode: address.postalCode,\n phone: address.phone,\n description: address.description,\n },\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.address) {\n throw new APIError(\"Failed to update address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async deleteCustomerAddress(id: number): Promise<boolean> {\n // The id must be sent in the JSON request body, not the URL path.\n const response = await this.http.delete<{ success: boolean }>(\n \"/address/delete\",\n { body: { id }, errorExtractor: aymakanErrorExtractor },\n );\n return response.success;\n }\n\n // =========================================================================\n // WEBHOOKS\n // =========================================================================\n\n parseWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent {\n return parseAymakanWebhook(payload, options);\n }\n\n // =========================================================================\n // NOT SUPPORTED\n // =========================================================================\n\n getRates(): Promise<never> {\n throw new UnsupportedOperationError(\"aymakan\", \"getRates\");\n }\n}\n"
6
+ "// file: src/carriers/aymakan/mappers.ts\n/**\n * Aymakan Data Mappers\n * Transform between unified ShipFlow types and Aymakan API formats.\n */\n\nimport { ValidationError, WebhookVerificationError } from \"../../core/errors\";\nimport type {\n Address,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { AymakanService, AymakanStatusCodes } from \"./services\";\nimport type {\n AymakanAddressRequest,\n AymakanCity,\n AymakanCreateShipmentRequest,\n AymakanNationalAddressRequest,\n AymakanPickupRequest,\n AymakanPickupResponse,\n AymakanShipmentResponse,\n AymakanTrackingEvent,\n AymakanTrackShipmentData,\n AymakanWebhookPayload,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapAymakanStatus(statusCode: string): ShipmentStatus | undefined {\n const mapped =\n AymakanStatusCodes[statusCode as keyof typeof AymakanStatusCodes];\n return mapped as ShipmentStatus | undefined;\n}\n\n/**\n * The /track endpoint's top-level `status` is a human-readable English label\n * (e.g. \"Received at Warehouse\") rather than an AY code, so `mapAymakanStatus`\n * can never resolve it. This map recovers a real unified status from that label\n * when there are no tracking events to derive it from. Keys are compared\n * case-insensitively.\n */\nconst AymakanStatusLabels: Record<string, ShipmentStatus> = {\n \"awb created at origin\": \"created\",\n \"shipment created\": \"created\",\n \"collected from collection point\": \"picked_up\",\n collected: \"picked_up\",\n \"picked up\": \"picked_up\",\n \"received at hub\": \"at_warehouse\",\n \"received at warehouse\": \"at_warehouse\",\n \"out for delivery\": \"out_for_delivery\",\n \"out for final destination\": \"out_for_delivery\",\n delivered: \"delivered\",\n \"shipment is delivered\": \"delivered\",\n \"transferred to destination city\": \"in_transit\",\n \"in transit\": \"in_transit\",\n \"not delivered\": \"exception\",\n \"failed attempt\": \"exception\",\n pending: \"pending\",\n \"future delivery\": \"pending\",\n returned: \"returned\",\n \"returned to shipper\": \"returned\",\n \"return to origin\": \"returned\",\n cancelled: \"cancelled\",\n canceled: \"cancelled\",\n};\n\n/**\n * Map a human-readable Aymakan status label to a unified status.\n * Returns undefined for unrecognized labels.\n */\nexport function mapAymakanStatusLabel(\n label: string,\n): ShipmentStatus | undefined {\n return AymakanStatusLabels[label.trim().toLowerCase()];\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\nfunction mapNationalAddress(\n addr?: Address[\"nationalAddress\"],\n): AymakanNationalAddressRequest | undefined {\n if (!addr) return undefined;\n return {\n short_code: addr.shortCode,\n building_number: addr.buildingNumber,\n street_name: addr.streetName,\n district: addr.district,\n additional_number: addr.additionalNumber,\n };\n}\n\n/** Strip non-digit characters from phone numbers (Aymakan requires digits only) */\nexport function sanitizePhone(phone: string): string {\n return phone.replace(/\\D/g, '');\n}\n\n/** Set of valid Aymakan service type codes */\nconst VALID_AYMAKAN_SERVICE_TYPES = new Set(\n Object.values(AymakanService) as string[],\n);\n\n/**\n * Resolve service type to a valid Aymakan code.\n * Returns undefined for unknown values so the API uses its default (ONP/ecommerce).\n */\nfunction resolveServiceType(serviceType?: string): string | undefined {\n if (!serviceType) return undefined;\n if (VALID_AYMAKAN_SERVICE_TYPES.has(serviceType)) return serviceType;\n return undefined;\n}\n\nexport function mapCreateShipmentRequest(\n input: CreateShipmentInput,\n): AymakanCreateShipmentRequest {\n const firstParcel = input.parcels[0];\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n const totalItems = input.parcels.reduce(\n (sum, p) => sum + (p.itemsCount ?? p.pieces),\n 0,\n );\n\n // COD (collected from the buyer on delivery) and declared/customs value are\n // distinct concepts and must never be conflated. Declared value comes solely\n // from input.declaredValue; COD comes solely from input.cod.\n const codEnabled = input.cod?.enabled === true;\n const declaredValueCurrency = input.declaredValue?.currency ?? \"SAR\";\n\n return {\n requested_by: input.options?.requestedBy ?? input.shipper.name,\n declared_value: input.declaredValue?.amount ?? 0,\n declared_value_currency: declaredValueCurrency,\n reference: input.reference,\n customer_tracking: input.options?.customerTracking,\n service_type: resolveServiceType(input.serviceType),\n is_cod: codEnabled ? 1 : 0,\n cod_amount: codEnabled ? input.cod?.amount : undefined,\n fulfilment_customer_name: input.options?.fulfilmentCustomerName,\n // Top-level currency prefers the COD currency when COD is enabled (it's the\n // amount actually collected), otherwise the declared-value currency.\n currency: codEnabled\n ? (input.cod?.currency ?? \"SAR\")\n : declaredValueCurrency,\n\n // Delivery (consignee)\n delivery_name: input.consignee.name,\n delivery_email: input.consignee.email,\n delivery_city: input.consignee.city,\n delivery_address: input.consignee.line1,\n delivery_neighbourhood:\n input.consignee.neighbourhood ?? input.consignee.state,\n delivery_postcode: input.consignee.postalCode,\n delivery_country: input.consignee.countryCode,\n delivery_phone: sanitizePhone(input.consignee.phone),\n delivery_description: input.consignee.description,\n delivery_national_address: mapNationalAddress(\n input.consignee.nationalAddress,\n ),\n\n // Collection (shipper)\n collection_name: input.shipper.company ?? input.shipper.name,\n collection_email: input.shipper.email,\n collection_city: input.shipper.city,\n collection_address: input.shipper.line1,\n collection_neighbourhood:\n input.shipper.neighbourhood ?? input.shipper.state,\n collection_postcode: input.shipper.postalCode,\n collection_country: input.shipper.countryCode,\n collection_phone: sanitizePhone(input.shipper.phone),\n collection_description: input.shipper.description,\n collection_national_address: mapNationalAddress(\n input.shipper.nationalAddress,\n ),\n\n // Parcel details\n weight: totalWeight,\n length: firstParcel?.dimensions?.length,\n width: firstParcel?.dimensions?.width,\n height: firstParcel?.dimensions?.height,\n pieces: totalPieces,\n items_count: totalItems,\n is_insured: input.options?.isInsured ? 1 : 0,\n\n // International\n international_metadata: input.options?.internationalMetadata\n ? {\n document_id: input.options.internationalMetadata.documentId,\n tax_identification_number: input.options.internationalMetadata.taxId,\n invoice_number: input.options.internationalMetadata.invoiceNumber,\n invoice_date: input.options.internationalMetadata.invoiceDate,\n }\n : undefined,\n };\n}\n\nexport function mapPickupRequest(input: PickupRequest): AymakanPickupRequest {\n return {\n reference: input.trackingNumbers?.[0],\n pickup_date: input.date,\n time_slot: input.timeSlot,\n city: input.city,\n contact_name: input.contactName,\n contact_phone: input.contactPhone,\n address: input.address,\n shipments: input.shipmentCount,\n };\n}\n\nexport function mapCustomerAddressRequest(\n addr: CustomerAddress,\n): AymakanAddressRequest {\n return {\n title: addr.title,\n name: addr.name,\n email: addr.email,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postcode: addr.postalCode ?? \"\",\n phone: addr.phone,\n description: addr.description ?? \"\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(data: AymakanShipmentResponse): Shipment {\n // Parse cod_amount: API returns string like \"150.00\", we need number\n const codAmount =\n data.cod_amount != null\n ? typeof data.cod_amount === \"string\"\n ? parseFloat(data.cod_amount)\n : data.cod_amount\n : undefined;\n\n return {\n carrier: \"aymakan\",\n trackingNumber: data.tracking_number,\n customerTracking: data.customer_tracking ?? undefined,\n reference: data.reference ?? undefined,\n status: mapAymakanStatus(data.status) ?? \"created\",\n statusLabel: data.status_label,\n labelUrl: data.label || undefined,\n pdfLabelUrl: data.pdf_label || undefined,\n // Emit a defined COD amount whenever we parsed a real number. A COD of 0\n // means \"no cash collected\", so surface it as undefined, but never let NaN\n // logic on a falsy 0 drop an otherwise valid value.\n codAmount:\n codAmount != null && !Number.isNaN(codAmount) && codAmount !== 0\n ? codAmount\n : undefined,\n declaredValue: data.declared_value,\n currency: data.currency,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n/**\n * Parse an Aymakan timestamp. Top-level fields are ISO 8601 (with `T`/`Z`), but\n * `tracking_info[].created_at` values are space-separated \"YYYY-MM-DD HH:MM:SS\"\n * strings in Saudi local time (UTC+3) with no timezone marker. `new Date()`\n * would parse those in the host's local timezone, so normalize to +03:00.\n */\nfunction parseAymakanDate(value: string): Date {\n if (/[TZ]|[+-]\\d{2}:?\\d{2}$/.test(value)) {\n return new Date(value);\n }\n return new Date(`${value.replace(\" \", \"T\")}+03:00`);\n}\n\nexport function mapTrackingEvent(event: AymakanTrackingEvent): TrackingEvent {\n return {\n timestamp: parseAymakanDate(event.created_at),\n statusCode: event.status_code,\n status: mapAymakanStatus(event.status_code) ?? \"unknown\",\n description: event.description,\n descriptionArabic: event.description_ar ?? undefined,\n };\n}\n\nexport function mapTrackingResult(\n data: AymakanTrackShipmentData,\n): TrackingResult {\n const events = (data.tracking_info ?? []).map(mapTrackingEvent);\n\n // The top-level `status` field is a human-readable label on the /track\n // endpoint (e.g. \"Received at Warehouse\") and an AY code on /by_reference.\n // Derive the unified status from the most recent tracking event (whose\n // status_code is always an AY code), falling back to mapping the top-level\n // status. The fallback tries the AY-code map first (handles /by_reference)\n // and then the human-label map (handles /track), so a real state is recovered\n // even when there are no events, then \"unknown\".\n const sortedEvents = events.length\n ? [...events].sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n : [];\n // Some event codes aren't in AymakanStatusCodes and map to \"unknown\" — scan\n // back through older events (newest-first) for the most recent one we can\n // actually recognize, instead of giving up as soon as the latest is unknown.\n const mostRecentRecognizedEvent = sortedEvents.find(\n (e) => e.status !== \"unknown\",\n );\n let status: ShipmentStatus;\n if (mostRecentRecognizedEvent) {\n status = mostRecentRecognizedEvent.status;\n } else {\n // No recognizable event (or none at all) — derive from the top-level\n // status: AY-code map first (/by_reference), then human-label map (/track),\n // else \"unknown\".\n status =\n mapAymakanStatus(data.status) ??\n mapAymakanStatusLabel(data.status) ??\n \"unknown\";\n }\n\n return {\n trackingNumber: data.tracking_number,\n carrier: \"aymakan\",\n reference: data.reference ?? undefined,\n status,\n statusLabel: data.status_label,\n events,\n deliveryDate: data.delivery_date ? new Date(data.delivery_date) : undefined,\n pickupDate: data.pickup_date ? new Date(data.pickup_date) : undefined,\n receivedAt: data.received_at ? new Date(data.received_at) : undefined,\n codAmount: data.cod_amount\n ? Number.isNaN(parseFloat(data.cod_amount))\n ? undefined\n : parseFloat(data.cod_amount)\n : undefined,\n weight: Number.isNaN(parseFloat(data.weight))\n ? undefined\n : parseFloat(data.weight),\n pieces: data.pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: AymakanCity): City {\n return {\n nameEn: city.city_en,\n nameAr: city.city_ar,\n };\n}\n\nexport function mapPickupResponse(data: AymakanPickupResponse[\"data\"]): Pickup {\n return {\n id: data.id,\n carrier: \"aymakan\",\n status: (\n [\"pending\", \"processing\", \"completed\", \"cancelled\"] as const\n ).includes(data.status as Pickup[\"status\"])\n ? (data.status as Pickup[\"status\"])\n : \"pending\",\n date: data.pickup_date,\n timeSlot: data.time_slot,\n city: data.city,\n contactName: data.contact_name,\n contactPhone: data.contact_phone,\n address: data.address,\n shipmentCount: data.shipments,\n warehouseId: data.warehouse_id,\n warehouseName: data.warehouse_name,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPER\n// ============================================================================\n\n/**\n * Timing-safe string comparison to prevent timing attacks on auth tokens.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n const encoder = new TextEncoder();\n const bufA = encoder.encode(a);\n const bufB = encoder.encode(b);\n if (bufA.byteLength !== bufB.byteLength) return false;\n // Use crypto.subtle-compatible constant-time compare\n let mismatch = 0;\n for (let i = 0; i < bufA.byteLength; i++) {\n mismatch |= bufA[i]! ^ bufB[i]!;\n }\n return mismatch === 0;\n}\n\nexport function parseAymakanWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n const { headers = {}, queryParams = {}, config } = options ?? {};\n\n // Verify auth FIRST, before inspecting the payload shape. These checks depend\n // only on `options` (headers/queryParams/config), so an unauthenticated caller\n // is rejected before any payload-validation detail can leak back to them.\n\n // Verify auth via header (case-insensitive lookup, timing-safe comparison)\n if (config?.authHeader && config?.authValue) {\n const lowerKey = config.authHeader.toLowerCase();\n const headerValue = Object.entries(headers).find(\n ([k]) => k.toLowerCase() === lowerKey,\n )?.[1];\n if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth header\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n // Verify auth via query param (timing-safe comparison)\n if (config?.authQueryParam && config?.authQueryValue) {\n const paramValue = queryParams[config.authQueryParam];\n if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth query param\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n // Validate required fields (only after auth has passed)\n if (\n !payload ||\n typeof payload !== \"object\" ||\n !(\"tracking_number\" in payload) ||\n !(\"status\" in payload)\n ) {\n throw new ValidationError(\n \"Invalid webhook payload: missing required fields\",\n {\n raw: payload,\n },\n );\n }\n\n const data = payload as AymakanWebhookPayload;\n\n const eventType = data.event ?? \"status_update\";\n\n // Validate timestamp\n const timestamp = new Date(data.date_time);\n if (Number.isNaN(timestamp.getTime())) {\n throw new ValidationError(\n \"Invalid webhook payload: invalid date_time value\",\n { raw: data.date_time },\n );\n }\n\n return {\n carrier: \"aymakan\",\n eventType,\n trackingNumber: data.tracking_number,\n reference: data.reference ?? undefined,\n // `status` is a core field present on every webhook regardless of event\n // type, so map it even for weight_update events. Consumers can still branch\n // on `eventType` to detect weight updates.\n status: mapAymakanStatus(data.status) ?? \"unknown\",\n statusCode: data.status,\n statusLabel: data.status_label,\n reasonCode: data.reason_code ?? undefined,\n reasonLabel: data.reason_en ?? undefined,\n timestamp,\n raw: data,\n };\n}\n",
7
+ "// file: src/carriers/aymakan/adapter.ts\n/**\n * Aymakan Carrier Adapter\n * Full implementation of the CarrierAdapter interface for Aymakan API.\n */\n\nimport {\n CITIES_CACHE_TTL,\n CITIES_FAILURE_COOLDOWN,\n findCityMatch,\n} from \"../../core/city-resolver\";\nimport { APIError, ValidationError } from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport {\n validateCreateShipmentInput,\n validatePickupRequest,\n} from \"../../core/schemas\";\nimport type {\n Address,\n CarrierConfig,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n TimeSlot,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n mapCity,\n mapCreateShipmentRequest,\n mapCustomerAddressRequest,\n mapPickupRequest,\n mapPickupResponse,\n mapShipmentResponse,\n mapTrackingResult,\n parseAymakanWebhook,\n sanitizePhone,\n} from \"./mappers\";\nimport type {\n AymakanAddressResponse,\n AymakanCancelResponse,\n AymakanCitiesResponse,\n AymakanCreateShipmentResponse,\n AymakanPickupResponse,\n AymakanShipmentResponse,\n AymakanTrackResponse,\n} from \"./types\";\n\nconst AYMAKAN_SANDBOX_URL = \"https://dev-api.aymakan.com.sa/v2\";\nconst AYMAKAN_PRODUCTION_URL = \"https://api.aymakan.net/v2\";\n\n/**\n * Aymakan returns logical errors inside a fake-200 envelope (HTTP 200 with an\n * error flag/message in the body). Without an extractor these are swallowed —\n * e.g. cancel endpoints would return `success: undefined` and the adapter would\n * report `false` with no reason. This extractor surfaces those as APIError.\n *\n * Recognised error shapes (all on a 200 body):\n * - `{ error: true, ... }` → Laravel/Aymakan error flag\n * - `{ success: false, ... }` → explicit failure flag\n * - `{ message }` / `{ response }` → human-readable error text\n * - `{ errors: { field: [...] } }` → Laravel field validation errors\n *\n * A bare `{ message }` is NOT treated as an error on its own (many success\n * envelopes carry a `message`); we only flag when `error`/`success:false` is\n * present, or validation `errors` exist.\n */\n/**\n * Pull a human-readable message out of an object-shaped error value, e.g.\n * `{ error: { message: \"...\" } }` or `{ error: { response: \"...\" } }`.\n */\nfunction extractNestedMessage(value: unknown): string | undefined {\n if (typeof value === \"string\") return value;\n if (value && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n if (typeof obj.message === \"string\") return obj.message;\n if (typeof obj.response === \"string\") return obj.response;\n }\n return undefined;\n}\n\n/** Join Laravel-style `{ field: [msg, ...] }` validation errors into one string. */\nfunction joinFieldErrors(errors: Record<string, unknown>): string | undefined {\n const parts = Object.values(errors)\n .flat()\n .filter((v): v is string => typeof v === \"string\");\n return parts.length ? parts.join(\"; \") : undefined;\n}\n\nfunction aymakanErrorExtractor(json: unknown): {\n hasError: boolean;\n message?: string;\n errors?: Record<string, string[]>;\n} {\n if (!json || typeof json !== \"object\") {\n return { hasError: false };\n }\n const obj = json as Record<string, unknown>;\n\n const hasValidationErrors =\n !!obj.errors &&\n typeof obj.errors === \"object\" &&\n Object.keys(obj.errors as object).length > 0;\n const hasError = obj.error === true || obj.success === false || hasValidationErrors;\n\n if (!hasError) {\n return { hasError: false };\n }\n\n const message =\n (typeof obj.message === \"string\" && obj.message) ||\n (typeof obj.response === \"string\" && obj.response) ||\n (typeof obj.error === \"string\" && obj.error) ||\n extractNestedMessage(obj.error) ||\n (hasValidationErrors\n ? joinFieldErrors(obj.errors as Record<string, string[]>)\n : undefined) ||\n undefined;\n\n return {\n hasError: true,\n message,\n errors: hasValidationErrors\n ? (obj.errors as Record<string, string[]>)\n : undefined,\n };\n}\n\nexport interface AymakanConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class AymakanAdapter extends BaseCarrierAdapter {\n readonly name = \"aymakan\";\n readonly supportedCountries = [\"SA\", \"AE\", \"BH\", \"KW\", \"OM\", \"QA\"];\n\n private http: HttpClient;\n\n /**\n * Shared options applied to every Aymakan http call so fake-200 error\n * envelopes surface as APIError instead of being silently swallowed.\n */\n private readonly errorOpts = { errorExtractor: aymakanErrorExtractor };\n\n /** Cached Aymakan cities list for city name resolution */\n private citiesCache: City[] | null = null;\n private citiesCacheTime = 0;\n /** Timestamp of the last failed /cities fetch attempt, if any */\n private citiesFetchFailedAt = 0;\n\n constructor(config: AymakanConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"aymakan\",\n headers: {\n Authorization: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? AYMAKAN_PRODUCTION_URL\n : AYMAKAN_SANDBOX_URL;\n }\n\n // =========================================================================\n // CITY RESOLUTION\n // =========================================================================\n\n /**\n * Load cities list from Aymakan API and cache it.\n * Silently falls back to empty list on failure so shipments can still be attempted.\n */\n private async ensureCitiesLoaded(): Promise<void> {\n const now = Date.now();\n if (\n this.citiesCache &&\n now - this.citiesCacheTime < CITIES_CACHE_TTL\n ) {\n return;\n }\n // A recent failure is still in its cooldown window — skip re-attempting\n // the /cities fetch and fall back to whatever cache we have (possibly\n // empty), so an outage never hard-blocks shipment/pickup creation.\n if (now - this.citiesFetchFailedAt < CITIES_FAILURE_COOLDOWN) {\n if (!this.citiesCache) this.citiesCache = [];\n return;\n }\n try {\n this.citiesCache = await this.getCities();\n this.citiesCacheTime = now;\n } catch {\n this.citiesFetchFailedAt = now;\n if (!this.citiesCache) this.citiesCache = [];\n }\n }\n\n /**\n * Resolve a user-input city name to the valid Aymakan English city name via\n * the shared matcher (see core/city-resolver.ts for the strategy). Lenient:\n * falls back to the original input when nothing matches or the cities list\n * is unavailable — used for pickups, where an invalid city only affects the\n * merchant's own pickup request.\n */\n private resolveCity(inputCity: string): string {\n if (!this.citiesCache || this.citiesCache.length === 0) return inputCity;\n const trimmed = inputCity.trim();\n if (!trimmed) return inputCity;\n return findCityMatch(trimmed, this.citiesCache)?.nameEn ?? trimmed;\n }\n\n /**\n * Strict variant used for shipment creation: when Aymakan's own city list is\n * loaded and the input matches nothing in it, the booking would be rejected\n * by the API anyway — fail fast with a clear, field-scoped error instead of\n * an opaque carrier 400. When the list is unavailable (fetch failed /\n * cooldown), fall back to pass-through so a /cities outage never blocks\n * shipment creation.\n */\n private resolveCityStrict(\n inputCity: string,\n field: \"shipper.city\" | \"consignee.city\",\n ): string {\n if (!this.citiesCache || this.citiesCache.length === 0) return inputCity;\n const match = findCityMatch(inputCity, this.citiesCache);\n if (!match) {\n throw new ValidationError(\n `Unknown ${field.replace(\".city\", \"\")} city \"${inputCity}\": not in Aymakan's supported city list`,\n { field },\n );\n }\n return match.nameEn;\n }\n\n /**\n * Resolve city names in a CreateShipmentInput to valid Aymakan city names,\n * rejecting cities Aymakan does not serve (strict — see resolveCityStrict).\n */\n private resolveCitiesInInput(\n input: CreateShipmentInput,\n ): CreateShipmentInput {\n return {\n ...input,\n shipper: {\n ...input.shipper,\n city: this.resolveCityStrict(input.shipper.city, \"shipper.city\"),\n },\n consignee: {\n ...input.consignee,\n city: this.resolveCityStrict(input.consignee.city, \"consignee.city\"),\n },\n };\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n await this.ensureCitiesLoaded();\n const resolved = this.resolveCitiesInInput(input);\n const request = mapCreateShipmentRequest(resolved);\n const response = await this.http.post<AymakanCreateShipmentResponse>(\n \"/shipping/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create shipment\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapShipmentResponse(response.shipping);\n }\n\n async createBulkShipments(\n inputs: CreateShipmentInput[],\n ): Promise<Shipment[]> {\n // Nothing to create — avoid an empty request to the carrier.\n if (inputs.length === 0) return [];\n // Aymakan rejects batches larger than 30 (\"Only 30 shipments can be\n // created at a time.\"), so fail fast with a clear error before the request.\n if (inputs.length > 30) {\n throw new ValidationError(\n \"Aymakan bulk create accepts at most 30 shipments per request\",\n { raw: { count: inputs.length } },\n );\n }\n inputs.forEach(validateCreateShipmentInput);\n await this.ensureCitiesLoaded();\n const requests = inputs\n .map((i) => this.resolveCitiesInInput(i))\n .map(mapCreateShipmentRequest);\n const response = await this.http.post<{\n success: boolean;\n message?: string;\n bulk_awb?: string;\n total_shipments?: number;\n shipments: AymakanCreateShipmentResponse[\"shipping\"][];\n }>(\"/shipping/create/bulk_shipping\", { shipments: requests }, this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to create bulk shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.shipments)) {\n throw new APIError(\n \"Aymakan bulk create response is missing the shipments array\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return response.shipments.map(mapShipmentResponse);\n }\n\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n // The errorExtractor surfaces fake-200 error envelopes as APIError, so a\n // real failure throws with the carrier's message rather than silently\n // returning false.\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel\",\n {\n tracking: trackingNumber,\n },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async cancelByReference(reference: string): Promise<boolean> {\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel_by_reference\",\n { reference },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async updateDeliveryAddress(\n trackingNumber: string,\n address: Address,\n ): Promise<boolean> {\n await this.ensureCitiesLoaded();\n const resolvedCity = this.resolveCity(address.city);\n // Documented endpoint: POST /shipping/update/delivery_address with the\n // tracking number as a `tracking` field in the JSON body (NOT in the path).\n const response = await this.http.post<{ success: boolean }>(\n \"/shipping/update/delivery_address\",\n {\n tracking: trackingNumber,\n delivery_name: address.name,\n delivery_email: address.email,\n delivery_city: resolvedCity,\n delivery_address: address.line1,\n delivery_neighbourhood: address.neighbourhood,\n delivery_postcode: address.postalCode,\n delivery_country: address.countryCode,\n delivery_phone: sanitizePhone(address.phone),\n },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const results = await this.trackMultiple([trackingNumber]);\n const result = results[0];\n if (!result) {\n throw new APIError(\"Shipment not found\", { carrier: \"aymakan\" });\n }\n return result;\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/track/${ids}`,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to track shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.data?.shipments)) {\n throw new APIError(\"Aymakan track response is missing data.shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.shipments.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/by_reference/${encodeURIComponent(reference)}`,\n this.errorOpts,\n );\n\n const shipment = response.data?.shipments?.[0];\n if (!response.success || !shipment) {\n throw new APIError(\"Shipment not found\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapTrackingResult(shipment);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"ZPL\" | \"PNG\",\n ): Promise<string> {\n // Note: this endpoint always returns a single PDF download URL, so the\n // requested `format` cannot be honored.\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { awb_url: string };\n }>(\n `/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}`,\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.awb_url) {\n const msg = response.message ?? response.response ?? \"Failed to get label\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.awb_url;\n }\n\n async getBulkLabels(trackingNumbers: string[]): Promise<string> {\n // Documented as a GET with comma-separated tracking numbers in the path.\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { bulk_awb_url: string };\n }>(`/shipping/bulk_awb/trackings/${ids}`, this.errorOpts);\n\n if (!response.success || !response.data?.bulk_awb_url) {\n const msg =\n response.message ?? response.response ?? \"Failed to get bulk labels\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.bulk_awb_url;\n }\n\n // =========================================================================\n // PICKUPS\n // =========================================================================\n\n async getPickupCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\n \"/pickup_request/cities\",\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getTimeSlots(_city: string, date: string): Promise<TimeSlot[]> {\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n data: {\n date: string;\n slots: Record<string, string>;\n };\n }>(`/time_slots/${encodeURIComponent(date)}`, this.errorOpts);\n\n // Aymakan returns error responses for invalid dates (past, Fridays, etc.)\n if (!response.success || response.error || !response.data?.slots) {\n const msg = response.message ?? \"No slots available\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n // data.slots is an object like { \"afternoon\": \"After Noon (02 PM - 06 PM)\" }\n return Object.entries(response.data.slots).map(([id, label]) => ({\n id,\n label,\n }));\n }\n\n async createPickup(input: PickupRequest): Promise<Pickup> {\n validatePickupRequest(input);\n await this.ensureCitiesLoaded();\n const resolvedInput = { ...input, city: this.resolveCity(input.city) };\n const request = mapPickupRequest(resolvedInput);\n const response = await this.http.post<AymakanPickupResponse>(\n \"/pickup_request/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create pickup\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapPickupResponse(response.data);\n }\n\n async cancelPickup(pickupId: string | number): Promise<boolean> {\n const response = await this.http.post<{ success: boolean }>(\n \"/pickup_request/cancel\",\n { pickup_request: Number(pickupId) },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async getPickupRequests(): Promise<Pickup[]> {\n // The list endpoint returns a Laravel paginated structure:\n // { success, data: { pickupRequests: { data: [...] } } }\n const response = await this.http.get<{\n success: boolean;\n data: { pickupRequests: { data: AymakanPickupResponse[\"data\"][] } };\n }>(\"/pickup_request/list\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup requests\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const list = response.data?.pickupRequests?.data;\n if (!Array.isArray(list)) {\n throw new APIError(\n \"Aymakan pickup list response is missing data.pickupRequests.data\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return list.map(mapPickupResponse);\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\n \"/cities\",\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to get cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<\n {\n id: string;\n name: string;\n address?: string;\n city?: string;\n latitude?: number;\n longitude?: number;\n }[]\n > {\n // The API returns `data` as a flat array of warehouse objects. There is no\n // `id` field — the warehouse code lives in `name` (e.g. \"RUH-WH\").\n const response = await this.http.get<{\n success: boolean;\n data: Array<{\n name: string;\n city?: string;\n address?: string;\n manager?: string;\n mobile_phone?: string;\n email?: string;\n location_lat?: number | null;\n location_lng?: number | null;\n }>;\n }>(\"/dropoff_locations\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get dropoff locations\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.map((loc) => ({\n id: loc.name,\n name: loc.name,\n address: loc.address,\n city: loc.city,\n latitude: loc.location_lat ?? undefined,\n longitude: loc.location_lng ?? undefined,\n }));\n }\n\n // =========================================================================\n // CUSTOMER ADDRESSES\n // =========================================================================\n\n async createCustomerAddress(\n address: CustomerAddress,\n ): Promise<CustomerAddress> {\n const request = mapCustomerAddressRequest(address);\n const response = await this.http.post<AymakanAddressResponse>(\n \"/address/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.address) {\n throw new APIError(\"Failed to create address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async getCustomerAddresses(): Promise<CustomerAddress[]> {\n const response = await this.http.get<{\n success: boolean;\n data: { address: AymakanAddressResponse[\"data\"][\"address\"][] };\n }>(\"/address/list\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get customer addresses\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.data?.address)) {\n throw new APIError(\n \"Aymakan address list response is missing data.address\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return response.data.address.map((addr) => ({\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n }));\n }\n\n async updateCustomerAddress(\n id: number,\n address: Partial<CustomerAddress>,\n ): Promise<CustomerAddress> {\n const response = await this.http.put<AymakanAddressResponse>(\n \"/address/update\",\n {\n id,\n title: address.title,\n name: address.name,\n email: address.email,\n city: address.city,\n address: address.address,\n neighbourhood: address.neighbourhood,\n postcode: address.postalCode,\n phone: address.phone,\n description: address.description,\n },\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.address) {\n throw new APIError(\"Failed to update address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async deleteCustomerAddress(id: number): Promise<boolean> {\n // The id must be sent in the JSON request body, not the URL path.\n const response = await this.http.delete<{ success: boolean }>(\n \"/address/delete\",\n { body: { id }, errorExtractor: aymakanErrorExtractor },\n );\n return response.success === true;\n }\n\n // =========================================================================\n // WEBHOOKS\n // =========================================================================\n\n parseWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent {\n return parseAymakanWebhook(payload, options);\n }\n}\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "52369388ABAA8E1964756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "C3DBDA3066CA8E3964756E2164756E21",
11
11
  "names": []
12
12
  }
@@ -10,6 +10,8 @@ export declare function mapAymakanStatus(statusCode: string): ShipmentStatus | u
10
10
  * Returns undefined for unrecognized labels.
11
11
  */
12
12
  export declare function mapAymakanStatusLabel(label: string): ShipmentStatus | undefined;
13
+ /** Strip non-digit characters from phone numbers (Aymakan requires digits only) */
14
+ export declare function sanitizePhone(phone: string): string;
13
15
  export declare function mapCreateShipmentRequest(input: CreateShipmentInput): AymakanCreateShipmentRequest;
14
16
  export declare function mapPickupRequest(input: PickupRequest): AymakanPickupRequest;
15
17
  export declare function mapCustomerAddressRequest(addr: CustomerAddress): AymakanAddressRequest;
@@ -1 +1 @@
1
- {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/aymakan/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAEV,IAAI,EACJ,mBAAmB,EACnB,eAAe,EACf,MAAM,EACN,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,qBAAqB,EACrB,WAAW,EACX,4BAA4B,EAE5B,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI/E;AAkCD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GACZ,cAAc,GAAG,SAAS,CAE5B;AAuCD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CAqF9B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,oBAAoB,CAW3E;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,GACpB,qBAAqB,CAYvB;AAMD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,QAAQ,CA8B3E;AAeD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,GAAG,aAAa,CAQ3E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,wBAAwB,GAC7B,cAAc,CA+ChB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAK/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC,GAAG,MAAM,CAqB7E;AAsBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,CAsEd"}
1
+ {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/aymakan/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAEV,IAAI,EACJ,mBAAmB,EACnB,eAAe,EACf,MAAM,EACN,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,qBAAqB,EACrB,WAAW,EACX,4BAA4B,EAE5B,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI/E;AAkCD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GACZ,cAAc,GAAG,SAAS,CAE5B;AAmBD,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAiBD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CAqF9B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,oBAAoB,CAW3E;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,GACpB,qBAAqB,CAYvB;AAMD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,QAAQ,CA8B3E;AAeD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,GAAG,aAAa,CAQ3E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,wBAAwB,GAC7B,cAAc,CAqDhB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAK/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC,GAAG,MAAM,CAqB7E;AAsBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,CA0Ed"}
@@ -176,7 +176,7 @@ export interface AymakanTrackShipmentData {
176
176
  created_at: string;
177
177
  id_customer: number;
178
178
  is_reverse_pickup: 0 | 1;
179
- tracking_info: AymakanTrackingEvent[];
179
+ tracking_info?: AymakanTrackingEvent[];
180
180
  }
181
181
  export interface AymakanTrackResponse {
182
182
  success: boolean;