shipflow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +328 -0
- package/dist/carriers/aymakan/adapter.d.ts +87 -0
- package/dist/carriers/aymakan/adapter.d.ts.map +1 -0
- package/dist/carriers/aymakan/index.d.ts +9 -0
- package/dist/carriers/aymakan/index.d.ts.map +1 -0
- package/dist/carriers/aymakan/index.js +625 -0
- package/dist/carriers/aymakan/index.js.map +12 -0
- package/dist/carriers/aymakan/mappers.d.ts +21 -0
- package/dist/carriers/aymakan/mappers.d.ts.map +1 -0
- package/dist/carriers/aymakan/services.d.ts +47 -0
- package/dist/carriers/aymakan/services.d.ts.map +1 -0
- package/dist/carriers/aymakan/types.d.ts +287 -0
- package/dist/carriers/aymakan/types.d.ts.map +1 -0
- package/dist/carriers/base.d.ts +88 -0
- package/dist/carriers/base.d.ts.map +1 -0
- package/dist/carriers/smsaexpress/adapter.d.ts +57 -0
- package/dist/carriers/smsaexpress/adapter.d.ts.map +1 -0
- package/dist/carriers/smsaexpress/index.d.ts +9 -0
- package/dist/carriers/smsaexpress/index.d.ts.map +1 -0
- package/dist/carriers/smsaexpress/index.js +408 -0
- package/dist/carriers/smsaexpress/index.js.map +12 -0
- package/dist/carriers/smsaexpress/mappers.d.ts +45 -0
- package/dist/carriers/smsaexpress/mappers.d.ts.map +1 -0
- package/dist/carriers/smsaexpress/services.d.ts +21 -0
- package/dist/carriers/smsaexpress/services.d.ts.map +1 -0
- package/dist/carriers/smsaexpress/types.d.ts +197 -0
- package/dist/carriers/smsaexpress/types.d.ts.map +1 -0
- package/dist/client.d.ts +44 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/core/errors.d.ts +69 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/http.d.ts +72 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/schemas.d.ts +832 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/types.d.ts +234 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index-x8sk1kw9.js +4600 -0
- package/dist/index-x8sk1kw9.js.map +22 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +10 -0
- package/package.json +63 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APIError,
|
|
3
|
+
BaseCarrierAdapter,
|
|
4
|
+
HttpClient,
|
|
5
|
+
UnsupportedOperationError,
|
|
6
|
+
ValidationError,
|
|
7
|
+
WebhookVerificationError,
|
|
8
|
+
validateCreateShipmentInput,
|
|
9
|
+
validatePickupRequest
|
|
10
|
+
} from "../../index-x8sk1kw9.js";
|
|
11
|
+
|
|
12
|
+
// src/carriers/aymakan/services.ts
|
|
13
|
+
var AymakanService = {
|
|
14
|
+
ECOMMERCE: "ONP",
|
|
15
|
+
DOCUMENTS: "DOC",
|
|
16
|
+
SAME_DAY: "SDD",
|
|
17
|
+
REVERSE_PICKUP: "RVP",
|
|
18
|
+
EXCHANGE: "EXH",
|
|
19
|
+
LOCKERS: "LOC",
|
|
20
|
+
HEAVY: "BLK",
|
|
21
|
+
PALLET: "PLT",
|
|
22
|
+
IMPORT_EXPRESS: "IPX",
|
|
23
|
+
EXPORT_EXPRESS: "EPX"
|
|
24
|
+
};
|
|
25
|
+
var AymakanStatusCodes = {
|
|
26
|
+
"AY-0001": "created",
|
|
27
|
+
"AY-0002": "picked_up",
|
|
28
|
+
"AY-0003": "at_warehouse",
|
|
29
|
+
"AY-0004": "out_for_delivery",
|
|
30
|
+
"AY-0005": "delivered",
|
|
31
|
+
"AY-0006": "exception",
|
|
32
|
+
"AY-0026": "at_warehouse",
|
|
33
|
+
"AY-0027": "in_transit",
|
|
34
|
+
"AY-0028": "in_transit",
|
|
35
|
+
"AY-0032": "pending",
|
|
36
|
+
"AY-0007": "returned",
|
|
37
|
+
"AY-0008": "cancelled",
|
|
38
|
+
"AY-0009": "returned"
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/carriers/aymakan/mappers.ts
|
|
42
|
+
function mapAymakanStatus(statusCode) {
|
|
43
|
+
const mapped = AymakanStatusCodes[statusCode];
|
|
44
|
+
return mapped;
|
|
45
|
+
}
|
|
46
|
+
function mapNationalAddress(addr) {
|
|
47
|
+
if (!addr)
|
|
48
|
+
return;
|
|
49
|
+
return {
|
|
50
|
+
short_code: addr.shortCode,
|
|
51
|
+
building_number: addr.buildingNumber,
|
|
52
|
+
street_name: addr.streetName,
|
|
53
|
+
district: addr.district,
|
|
54
|
+
additional_number: addr.additionalNumber
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function sanitizePhone(phone) {
|
|
58
|
+
return phone.replace(/\D/g, "");
|
|
59
|
+
}
|
|
60
|
+
var VALID_AYMAKAN_SERVICE_TYPES = new Set(Object.values(AymakanService));
|
|
61
|
+
function resolveServiceType(serviceType) {
|
|
62
|
+
if (!serviceType)
|
|
63
|
+
return;
|
|
64
|
+
if (VALID_AYMAKAN_SERVICE_TYPES.has(serviceType))
|
|
65
|
+
return serviceType;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
function mapCreateShipmentRequest(input) {
|
|
69
|
+
const firstParcel = input.parcels[0];
|
|
70
|
+
const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);
|
|
71
|
+
const totalWeight = input.parcels.reduce((sum, p) => sum + (p.weight.unit === "lb" ? p.weight.value * 0.453592 : p.weight.value), 0);
|
|
72
|
+
const totalItems = input.parcels.reduce((sum, p) => sum + (p.itemsCount ?? p.pieces), 0);
|
|
73
|
+
return {
|
|
74
|
+
requested_by: input.options?.requestedBy ?? input.shipper.name,
|
|
75
|
+
declared_value: input.declaredValue?.amount ?? input.cod?.amount ?? 0,
|
|
76
|
+
declared_value_currency: input.declaredValue?.currency ?? input.cod?.currency ?? "SAR",
|
|
77
|
+
reference: input.reference,
|
|
78
|
+
customer_tracking: input.options?.customerTracking,
|
|
79
|
+
service_type: resolveServiceType(input.serviceType),
|
|
80
|
+
is_cod: input.cod?.enabled ? 1 : 0,
|
|
81
|
+
cod_amount: input.cod?.enabled ? input.cod.amount : undefined,
|
|
82
|
+
fulfilment_customer_name: input.options?.fulfilmentCustomerName,
|
|
83
|
+
currency: input.cod?.currency ?? "SAR",
|
|
84
|
+
delivery_name: input.consignee.name,
|
|
85
|
+
delivery_email: input.consignee.email,
|
|
86
|
+
delivery_city: input.consignee.city,
|
|
87
|
+
delivery_address: input.consignee.line1,
|
|
88
|
+
delivery_neighbourhood: input.consignee.neighbourhood ?? input.consignee.state,
|
|
89
|
+
delivery_postcode: input.consignee.postalCode,
|
|
90
|
+
delivery_country: input.consignee.countryCode,
|
|
91
|
+
delivery_phone: sanitizePhone(input.consignee.phone),
|
|
92
|
+
delivery_description: input.consignee.description,
|
|
93
|
+
delivery_national_address: mapNationalAddress(input.consignee.nationalAddress),
|
|
94
|
+
collection_name: input.shipper.company ?? input.shipper.name,
|
|
95
|
+
collection_email: input.shipper.email,
|
|
96
|
+
collection_city: input.shipper.city,
|
|
97
|
+
collection_address: input.shipper.line1,
|
|
98
|
+
collection_neighbourhood: input.shipper.neighbourhood ?? input.shipper.state,
|
|
99
|
+
collection_postcode: input.shipper.postalCode,
|
|
100
|
+
collection_country: input.shipper.countryCode,
|
|
101
|
+
collection_phone: sanitizePhone(input.shipper.phone),
|
|
102
|
+
collection_description: input.shipper.description,
|
|
103
|
+
collection_national_address: mapNationalAddress(input.shipper.nationalAddress),
|
|
104
|
+
weight: totalWeight,
|
|
105
|
+
length: firstParcel?.dimensions?.length,
|
|
106
|
+
width: firstParcel?.dimensions?.width,
|
|
107
|
+
height: firstParcel?.dimensions?.height,
|
|
108
|
+
pieces: totalPieces,
|
|
109
|
+
items_count: totalItems,
|
|
110
|
+
is_insured: input.options?.isInsured ? 1 : 0,
|
|
111
|
+
international_metadata: input.options?.internationalMetadata ? {
|
|
112
|
+
document_id: input.options.internationalMetadata.documentId,
|
|
113
|
+
tax_identification_number: input.options.internationalMetadata.taxId,
|
|
114
|
+
invoice_number: input.options.internationalMetadata.invoiceNumber,
|
|
115
|
+
invoice_date: input.options.internationalMetadata.invoiceDate
|
|
116
|
+
} : undefined
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function mapPickupRequest(input) {
|
|
120
|
+
return {
|
|
121
|
+
reference: input.trackingNumbers?.[0],
|
|
122
|
+
pickup_date: input.date,
|
|
123
|
+
time_slot: input.timeSlot,
|
|
124
|
+
city: input.city,
|
|
125
|
+
contact_name: input.contactName,
|
|
126
|
+
contact_phone: input.contactPhone,
|
|
127
|
+
address: input.address,
|
|
128
|
+
shipments: input.shipmentCount
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function mapCustomerAddressRequest(addr) {
|
|
132
|
+
return {
|
|
133
|
+
title: addr.title,
|
|
134
|
+
name: addr.name,
|
|
135
|
+
email: addr.email,
|
|
136
|
+
city: addr.city,
|
|
137
|
+
address: addr.address,
|
|
138
|
+
neighbourhood: addr.neighbourhood,
|
|
139
|
+
postcode: addr.postalCode ?? "",
|
|
140
|
+
phone: addr.phone,
|
|
141
|
+
description: addr.description ?? ""
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function mapShipmentResponse(data) {
|
|
145
|
+
const codAmount = data.cod_amount != null ? typeof data.cod_amount === "string" ? parseFloat(data.cod_amount) : data.cod_amount : undefined;
|
|
146
|
+
return {
|
|
147
|
+
carrier: "aymakan",
|
|
148
|
+
trackingNumber: data.tracking_number,
|
|
149
|
+
customerTracking: data.customer_tracking ?? undefined,
|
|
150
|
+
reference: data.reference ?? undefined,
|
|
151
|
+
status: mapAymakanStatus(data.status) ?? "created",
|
|
152
|
+
statusLabel: data.status_label,
|
|
153
|
+
labelUrl: data.label || undefined,
|
|
154
|
+
pdfLabelUrl: data.pdf_label || undefined,
|
|
155
|
+
codAmount: codAmount && !Number.isNaN(codAmount) ? codAmount : undefined,
|
|
156
|
+
declaredValue: data.declared_value,
|
|
157
|
+
currency: data.currency,
|
|
158
|
+
createdAt: new Date(data.created_at),
|
|
159
|
+
raw: data
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function mapTrackingEvent(event) {
|
|
163
|
+
return {
|
|
164
|
+
timestamp: new Date(event.created_at),
|
|
165
|
+
statusCode: event.status_code,
|
|
166
|
+
status: mapAymakanStatus(event.status_code) ?? "unknown",
|
|
167
|
+
description: event.description,
|
|
168
|
+
descriptionArabic: event.description_ar ?? undefined
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function mapTrackingResult(data) {
|
|
172
|
+
return {
|
|
173
|
+
trackingNumber: data.tracking_number,
|
|
174
|
+
carrier: "aymakan",
|
|
175
|
+
reference: data.reference ?? undefined,
|
|
176
|
+
status: mapAymakanStatus(data.status) ?? "unknown",
|
|
177
|
+
statusLabel: data.status_label,
|
|
178
|
+
events: data.tracking_info.map(mapTrackingEvent),
|
|
179
|
+
deliveryDate: data.delivery_date ? new Date(data.delivery_date) : undefined,
|
|
180
|
+
pickupDate: data.pickup_date ? new Date(data.pickup_date) : undefined,
|
|
181
|
+
receivedAt: data.received_at ? new Date(data.received_at) : undefined,
|
|
182
|
+
codAmount: data.cod_amount ? Number.isNaN(parseFloat(data.cod_amount)) ? undefined : parseFloat(data.cod_amount) : undefined,
|
|
183
|
+
weight: Number.isNaN(parseFloat(data.weight)) ? undefined : parseFloat(data.weight),
|
|
184
|
+
pieces: data.pieces,
|
|
185
|
+
raw: data
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function mapCity(city) {
|
|
189
|
+
return {
|
|
190
|
+
nameEn: city.city_en,
|
|
191
|
+
nameAr: city.city_ar
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function mapPickupResponse(data) {
|
|
195
|
+
return {
|
|
196
|
+
id: data.id,
|
|
197
|
+
carrier: "aymakan",
|
|
198
|
+
status: ["pending", "processing", "completed", "cancelled"].includes(data.status) ? data.status : "pending",
|
|
199
|
+
date: data.pickup_date,
|
|
200
|
+
timeSlot: data.time_slot,
|
|
201
|
+
city: data.city,
|
|
202
|
+
contactName: data.contact_name,
|
|
203
|
+
contactPhone: data.contact_phone,
|
|
204
|
+
address: data.address,
|
|
205
|
+
shipmentCount: data.shipments,
|
|
206
|
+
warehouseId: data.warehouse_id,
|
|
207
|
+
warehouseName: data.warehouse_name,
|
|
208
|
+
createdAt: new Date(data.created_at),
|
|
209
|
+
raw: data
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function timingSafeEqual(a, b) {
|
|
213
|
+
const encoder = new TextEncoder;
|
|
214
|
+
const bufA = encoder.encode(a);
|
|
215
|
+
const bufB = encoder.encode(b);
|
|
216
|
+
if (bufA.byteLength !== bufB.byteLength)
|
|
217
|
+
return false;
|
|
218
|
+
let mismatch = 0;
|
|
219
|
+
for (let i = 0;i < bufA.byteLength; i++) {
|
|
220
|
+
mismatch |= bufA[i] ^ bufB[i];
|
|
221
|
+
}
|
|
222
|
+
return mismatch === 0;
|
|
223
|
+
}
|
|
224
|
+
function parseAymakanWebhook(payload, options) {
|
|
225
|
+
const { headers = {}, queryParams = {}, config } = options ?? {};
|
|
226
|
+
if (!payload || typeof payload !== "object" || !("tracking_number" in payload) || !("status" in payload)) {
|
|
227
|
+
throw new ValidationError("Invalid webhook payload: missing required fields", {
|
|
228
|
+
raw: payload
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const data = payload;
|
|
232
|
+
if (config?.authHeader && config?.authValue) {
|
|
233
|
+
const lowerKey = config.authHeader.toLowerCase();
|
|
234
|
+
const headerValue = Object.entries(headers).find(([k]) => k.toLowerCase() === lowerKey)?.[1];
|
|
235
|
+
if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {
|
|
236
|
+
throw new WebhookVerificationError("Invalid webhook auth header", {
|
|
237
|
+
carrier: "aymakan"
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (config?.authQueryParam && config?.authQueryValue) {
|
|
242
|
+
const paramValue = queryParams[config.authQueryParam];
|
|
243
|
+
if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {
|
|
244
|
+
throw new WebhookVerificationError("Invalid webhook auth query param", {
|
|
245
|
+
carrier: "aymakan"
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const eventType = data.event ?? "status_update";
|
|
250
|
+
const timestamp = new Date(data.date_time);
|
|
251
|
+
if (Number.isNaN(timestamp.getTime())) {
|
|
252
|
+
throw new ValidationError("Invalid webhook payload: invalid date_time value", { raw: data.date_time });
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
carrier: "aymakan",
|
|
256
|
+
eventType,
|
|
257
|
+
trackingNumber: data.tracking_number,
|
|
258
|
+
reference: data.reference ?? undefined,
|
|
259
|
+
status: eventType === "weight_update" ? "unknown" : mapAymakanStatus(data.status) ?? "unknown",
|
|
260
|
+
statusCode: data.status,
|
|
261
|
+
statusLabel: data.status_label,
|
|
262
|
+
reasonCode: data.reason_code ?? undefined,
|
|
263
|
+
reasonLabel: data.reason_en ?? undefined,
|
|
264
|
+
timestamp,
|
|
265
|
+
raw: data
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/carriers/aymakan/adapter.ts
|
|
270
|
+
var AYMAKAN_SANDBOX_URL = "https://dev-api.aymakan.com.sa/v2";
|
|
271
|
+
var AYMAKAN_PRODUCTION_URL = "https://api.aymakan.net/v2";
|
|
272
|
+
|
|
273
|
+
class AymakanAdapter extends BaseCarrierAdapter {
|
|
274
|
+
name = "aymakan";
|
|
275
|
+
supportedCountries = ["SA", "AE", "BH", "KW", "OM", "QA"];
|
|
276
|
+
http;
|
|
277
|
+
citiesCache = null;
|
|
278
|
+
citiesCacheTime = 0;
|
|
279
|
+
static CITIES_CACHE_TTL = 60 * 60 * 1000;
|
|
280
|
+
constructor(config) {
|
|
281
|
+
super(config);
|
|
282
|
+
this.http = new HttpClient({
|
|
283
|
+
baseUrl: this.getBaseUrl(),
|
|
284
|
+
carrier: "aymakan",
|
|
285
|
+
headers: {
|
|
286
|
+
Authorization: config.credentials.apiKey
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
getBaseUrl() {
|
|
291
|
+
return this.config.mode === "production" ? AYMAKAN_PRODUCTION_URL : AYMAKAN_SANDBOX_URL;
|
|
292
|
+
}
|
|
293
|
+
async ensureCitiesLoaded() {
|
|
294
|
+
if (this.citiesCache && Date.now() - this.citiesCacheTime < AymakanAdapter.CITIES_CACHE_TTL) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
this.citiesCache = await this.getCities();
|
|
299
|
+
this.citiesCacheTime = Date.now();
|
|
300
|
+
} catch {
|
|
301
|
+
if (!this.citiesCache)
|
|
302
|
+
this.citiesCache = [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
static normalizeArabic(text) {
|
|
306
|
+
return text.trim().replace(/[\u064B-\u065F\u0670]/g, "").replace(/[أإآ]/g, "ا").replace(/ة/g, "ه");
|
|
307
|
+
}
|
|
308
|
+
resolveCity(inputCity) {
|
|
309
|
+
if (!this.citiesCache || this.citiesCache.length === 0)
|
|
310
|
+
return inputCity;
|
|
311
|
+
const trimmed = inputCity.trim();
|
|
312
|
+
if (!trimmed)
|
|
313
|
+
return inputCity;
|
|
314
|
+
const lower = trimmed.toLowerCase();
|
|
315
|
+
const exactEn = this.citiesCache.find((c) => c.nameEn.toLowerCase() === lower);
|
|
316
|
+
if (exactEn)
|
|
317
|
+
return exactEn.nameEn;
|
|
318
|
+
const exactAr = this.citiesCache.find((c) => c.nameAr === trimmed);
|
|
319
|
+
if (exactAr)
|
|
320
|
+
return exactAr.nameEn;
|
|
321
|
+
const normalizedInput = AymakanAdapter.normalizeArabic(trimmed);
|
|
322
|
+
const normalizedAr = this.citiesCache.find((c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === normalizedInput);
|
|
323
|
+
if (normalizedAr)
|
|
324
|
+
return normalizedAr.nameEn;
|
|
325
|
+
const withoutAl = normalizedInput.startsWith("ال") ? normalizedInput.slice(2) : `ال${normalizedInput}`;
|
|
326
|
+
const alMatch = this.citiesCache.find((c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === withoutAl);
|
|
327
|
+
if (alMatch)
|
|
328
|
+
return alMatch.nameEn;
|
|
329
|
+
const containsEn = this.citiesCache.find((c) => c.nameEn.toLowerCase().includes(lower) || lower.includes(c.nameEn.toLowerCase()));
|
|
330
|
+
if (containsEn)
|
|
331
|
+
return containsEn.nameEn;
|
|
332
|
+
return trimmed;
|
|
333
|
+
}
|
|
334
|
+
resolveCitiesInInput(input) {
|
|
335
|
+
return {
|
|
336
|
+
...input,
|
|
337
|
+
shipper: {
|
|
338
|
+
...input.shipper,
|
|
339
|
+
city: this.resolveCity(input.shipper.city)
|
|
340
|
+
},
|
|
341
|
+
consignee: {
|
|
342
|
+
...input.consignee,
|
|
343
|
+
city: this.resolveCity(input.consignee.city)
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async executeCreateShipment(input) {
|
|
348
|
+
await this.ensureCitiesLoaded();
|
|
349
|
+
const resolved = this.resolveCitiesInInput(input);
|
|
350
|
+
const request = mapCreateShipmentRequest(resolved);
|
|
351
|
+
const response = await this.http.post("/shipping/create", request);
|
|
352
|
+
if (!response.success) {
|
|
353
|
+
throw new APIError("Failed to create shipment", {
|
|
354
|
+
carrier: "aymakan",
|
|
355
|
+
raw: response
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return mapShipmentResponse(response.shipping);
|
|
359
|
+
}
|
|
360
|
+
async createBulkShipments(inputs) {
|
|
361
|
+
inputs.forEach(validateCreateShipmentInput);
|
|
362
|
+
await this.ensureCitiesLoaded();
|
|
363
|
+
const requests = inputs.map((i) => this.resolveCitiesInInput(i)).map(mapCreateShipmentRequest);
|
|
364
|
+
const response = await this.http.post("/shipping/create_bulk", { data: requests });
|
|
365
|
+
if (!response.success) {
|
|
366
|
+
throw new APIError("Failed to create bulk shipments", {
|
|
367
|
+
carrier: "aymakan",
|
|
368
|
+
raw: response
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return response.data.shipments.map(mapShipmentResponse);
|
|
372
|
+
}
|
|
373
|
+
async cancelShipment(trackingNumber) {
|
|
374
|
+
const response = await this.http.post("/shipping/cancel", {
|
|
375
|
+
tracking: trackingNumber
|
|
376
|
+
});
|
|
377
|
+
return response.success;
|
|
378
|
+
}
|
|
379
|
+
async cancelByReference(reference) {
|
|
380
|
+
const response = await this.http.post(`/shipping/cancel/reference/${encodeURIComponent(reference)}`);
|
|
381
|
+
return response.success;
|
|
382
|
+
}
|
|
383
|
+
async updateDeliveryAddress(trackingNumber, address) {
|
|
384
|
+
await this.ensureCitiesLoaded();
|
|
385
|
+
const resolvedCity = this.resolveCity(address.city);
|
|
386
|
+
const response = await this.http.post(`/shipping/update_delivery_address/${encodeURIComponent(trackingNumber)}`, {
|
|
387
|
+
delivery_name: address.name,
|
|
388
|
+
delivery_email: address.email,
|
|
389
|
+
delivery_city: resolvedCity,
|
|
390
|
+
delivery_address: address.line1,
|
|
391
|
+
delivery_neighbourhood: address.neighbourhood,
|
|
392
|
+
delivery_postcode: address.postalCode,
|
|
393
|
+
delivery_country: address.countryCode,
|
|
394
|
+
delivery_phone: address.phone
|
|
395
|
+
});
|
|
396
|
+
return response.success;
|
|
397
|
+
}
|
|
398
|
+
async track(trackingNumber) {
|
|
399
|
+
const results = await this.trackMultiple([trackingNumber]);
|
|
400
|
+
const result = results[0];
|
|
401
|
+
if (!result) {
|
|
402
|
+
throw new APIError("Shipment not found", { carrier: "aymakan" });
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
async trackMultiple(trackingNumbers) {
|
|
407
|
+
const ids = trackingNumbers.map(encodeURIComponent).join(",");
|
|
408
|
+
const response = await this.http.get(`/shipping/track/${ids}`);
|
|
409
|
+
if (!response.success) {
|
|
410
|
+
throw new APIError("Failed to track shipments", {
|
|
411
|
+
carrier: "aymakan",
|
|
412
|
+
raw: response
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return response.data.shipments.map(mapTrackingResult);
|
|
416
|
+
}
|
|
417
|
+
async trackByReference(reference) {
|
|
418
|
+
const response = await this.http.get(`/shipments/by_reference/${encodeURIComponent(reference)}`);
|
|
419
|
+
if (!response.success || !response.data.shipments[0]) {
|
|
420
|
+
throw new APIError("Shipment not found", {
|
|
421
|
+
carrier: "aymakan",
|
|
422
|
+
raw: response
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return mapTrackingResult(response.data.shipments[0]);
|
|
426
|
+
}
|
|
427
|
+
async getLabel(trackingNumber, format) {
|
|
428
|
+
const response = await this.http.get(`/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}`);
|
|
429
|
+
if (!response.success) {
|
|
430
|
+
throw new APIError("Failed to get label", {
|
|
431
|
+
carrier: "aymakan",
|
|
432
|
+
raw: response
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
if (format === "PNG") {
|
|
436
|
+
return response.data.label || response.data.awb_url || response.data.pdf_label;
|
|
437
|
+
}
|
|
438
|
+
return response.data.pdf_label || response.data.awb_url || response.data.label;
|
|
439
|
+
}
|
|
440
|
+
async getBulkLabels(trackingNumbers) {
|
|
441
|
+
const response = await this.http.post("/shipping/bulk_awb_labels", {
|
|
442
|
+
tracking_numbers: trackingNumbers
|
|
443
|
+
});
|
|
444
|
+
if (!response.success) {
|
|
445
|
+
throw new APIError("Failed to get bulk labels", {
|
|
446
|
+
carrier: "aymakan",
|
|
447
|
+
raw: response
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return response.data.label_url;
|
|
451
|
+
}
|
|
452
|
+
async getPickupCities() {
|
|
453
|
+
const response = await this.http.get("/pickup_request/cities");
|
|
454
|
+
if (!response.success) {
|
|
455
|
+
throw new APIError("Failed to get pickup cities", {
|
|
456
|
+
carrier: "aymakan",
|
|
457
|
+
raw: response
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return response.data.cities.map(mapCity);
|
|
461
|
+
}
|
|
462
|
+
async getTimeSlots(_city, date) {
|
|
463
|
+
const response = await this.http.get(`/time_slots/${date}`);
|
|
464
|
+
if (!response.success || response.error || !response.data?.slots) {
|
|
465
|
+
const msg = response.message ?? "No slots available";
|
|
466
|
+
throw new APIError(msg, { carrier: "aymakan", raw: response });
|
|
467
|
+
}
|
|
468
|
+
return Object.entries(response.data.slots).map(([id, label]) => ({
|
|
469
|
+
id,
|
|
470
|
+
label
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
async createPickup(input) {
|
|
474
|
+
validatePickupRequest(input);
|
|
475
|
+
await this.ensureCitiesLoaded();
|
|
476
|
+
const resolvedInput = { ...input, city: this.resolveCity(input.city) };
|
|
477
|
+
const request = mapPickupRequest(resolvedInput);
|
|
478
|
+
const response = await this.http.post("/pickup_request/create", request);
|
|
479
|
+
if (!response.success) {
|
|
480
|
+
throw new APIError("Failed to create pickup", {
|
|
481
|
+
carrier: "aymakan",
|
|
482
|
+
raw: response
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return mapPickupResponse(response.data);
|
|
486
|
+
}
|
|
487
|
+
async cancelPickup(pickupId) {
|
|
488
|
+
const response = await this.http.post(`/pickup_request/cancel/${encodeURIComponent(String(pickupId))}`);
|
|
489
|
+
return response.success;
|
|
490
|
+
}
|
|
491
|
+
async getPickupRequests() {
|
|
492
|
+
const response = await this.http.get("/pickup_requests");
|
|
493
|
+
if (!response.success) {
|
|
494
|
+
throw new APIError("Failed to get pickup requests", {
|
|
495
|
+
carrier: "aymakan",
|
|
496
|
+
raw: response
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return response.data.pickup_requests.map(mapPickupResponse);
|
|
500
|
+
}
|
|
501
|
+
async getCities() {
|
|
502
|
+
const response = await this.http.get("/cities");
|
|
503
|
+
if (!response.success) {
|
|
504
|
+
throw new APIError("Failed to get cities", {
|
|
505
|
+
carrier: "aymakan",
|
|
506
|
+
raw: response
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return response.data.cities.map(mapCity);
|
|
510
|
+
}
|
|
511
|
+
async getDropoffLocations() {
|
|
512
|
+
const response = await this.http.get("/dropoff_locations");
|
|
513
|
+
if (!response.success) {
|
|
514
|
+
throw new APIError("Failed to get dropoff locations", {
|
|
515
|
+
carrier: "aymakan",
|
|
516
|
+
raw: response
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return response.data.locations.map((loc) => ({
|
|
520
|
+
id: String(loc.id),
|
|
521
|
+
name: loc.name,
|
|
522
|
+
nameAr: loc.name_ar,
|
|
523
|
+
address: loc.address,
|
|
524
|
+
city: loc.city
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
|
+
async createCustomerAddress(address) {
|
|
528
|
+
const request = mapCustomerAddressRequest(address);
|
|
529
|
+
const response = await this.http.post("/address/create", request);
|
|
530
|
+
if (!response.success) {
|
|
531
|
+
throw new APIError("Failed to create address", {
|
|
532
|
+
carrier: "aymakan",
|
|
533
|
+
raw: response
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
const addr = response.data.address;
|
|
537
|
+
return {
|
|
538
|
+
id: addr.id,
|
|
539
|
+
title: addr.title,
|
|
540
|
+
name: addr.name,
|
|
541
|
+
email: addr.email,
|
|
542
|
+
phone: addr.phone,
|
|
543
|
+
city: addr.city,
|
|
544
|
+
address: addr.address,
|
|
545
|
+
neighbourhood: addr.neighbourhood,
|
|
546
|
+
postalCode: addr.postcode,
|
|
547
|
+
countryCode: addr.country,
|
|
548
|
+
description: addr.description
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async getCustomerAddresses() {
|
|
552
|
+
const response = await this.http.get("/addresses");
|
|
553
|
+
if (!response.success) {
|
|
554
|
+
throw new APIError("Failed to get customer addresses", {
|
|
555
|
+
carrier: "aymakan",
|
|
556
|
+
raw: response
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return response.data.addresses.map((addr) => ({
|
|
560
|
+
id: addr.id,
|
|
561
|
+
title: addr.title,
|
|
562
|
+
name: addr.name,
|
|
563
|
+
email: addr.email,
|
|
564
|
+
phone: addr.phone,
|
|
565
|
+
city: addr.city,
|
|
566
|
+
address: addr.address,
|
|
567
|
+
neighbourhood: addr.neighbourhood,
|
|
568
|
+
postalCode: addr.postcode,
|
|
569
|
+
countryCode: addr.country,
|
|
570
|
+
description: addr.description
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
async updateCustomerAddress(id, address) {
|
|
574
|
+
const response = await this.http.put(`/address/update/${id}`, {
|
|
575
|
+
title: address.title,
|
|
576
|
+
name: address.name,
|
|
577
|
+
email: address.email,
|
|
578
|
+
city: address.city,
|
|
579
|
+
address: address.address,
|
|
580
|
+
neighbourhood: address.neighbourhood,
|
|
581
|
+
postcode: address.postalCode,
|
|
582
|
+
phone: address.phone,
|
|
583
|
+
description: address.description
|
|
584
|
+
});
|
|
585
|
+
if (!response.success) {
|
|
586
|
+
throw new APIError("Failed to update address", {
|
|
587
|
+
carrier: "aymakan",
|
|
588
|
+
raw: response
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
const addr = response.data.address;
|
|
592
|
+
return {
|
|
593
|
+
id: addr.id,
|
|
594
|
+
title: addr.title,
|
|
595
|
+
name: addr.name,
|
|
596
|
+
email: addr.email,
|
|
597
|
+
phone: addr.phone,
|
|
598
|
+
city: addr.city,
|
|
599
|
+
address: addr.address,
|
|
600
|
+
neighbourhood: addr.neighbourhood,
|
|
601
|
+
postalCode: addr.postcode,
|
|
602
|
+
countryCode: addr.country,
|
|
603
|
+
description: addr.description
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
async deleteCustomerAddress(id) {
|
|
607
|
+
const response = await this.http.delete(`/address/delete/${id}`);
|
|
608
|
+
return response.success;
|
|
609
|
+
}
|
|
610
|
+
parseWebhook(payload, options) {
|
|
611
|
+
return parseAymakanWebhook(payload, options);
|
|
612
|
+
}
|
|
613
|
+
getRates() {
|
|
614
|
+
throw new UnsupportedOperationError("aymakan", "getRates");
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
export {
|
|
618
|
+
parseAymakanWebhook,
|
|
619
|
+
mapAymakanStatus,
|
|
620
|
+
AymakanStatusCodes,
|
|
621
|
+
AymakanService,
|
|
622
|
+
AymakanAdapter
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
//# debugId=ECFB3CDDA31BC0F864756E2164756E21
|