shipflow 0.1.1 → 0.2.1
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 +139 -33
- package/dist/carriers/aramex/adapter.d.ts.map +1 -1
- package/dist/carriers/aramex/index.js +54 -21
- package/dist/carriers/aramex/index.js.map +4 -4
- package/dist/carriers/aramex/mappers.d.ts +8 -0
- package/dist/carriers/aramex/mappers.d.ts.map +1 -1
- package/dist/carriers/aymakan/adapter.d.ts +5 -0
- package/dist/carriers/aymakan/adapter.d.ts.map +1 -1
- package/dist/carriers/aymakan/index.js +95 -29
- package/dist/carriers/aymakan/index.js.map +4 -4
- package/dist/carriers/aymakan/mappers.d.ts +5 -0
- package/dist/carriers/aymakan/mappers.d.ts.map +1 -1
- package/dist/carriers/smsaexpress/index.js +21 -15
- package/dist/carriers/smsaexpress/index.js.map +3 -3
- package/dist/carriers/smsaexpress/mappers.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ShipFlow
|
|
2
2
|
|
|
3
|
-
Unified Shipping SDK for MENA region carriers. A single API to create shipments, track packages, manage labels, handle webhooks, and more — across Aymakan, SMSA Express, and future carriers.
|
|
3
|
+
Unified Shipping SDK for MENA region carriers. A single API to create shipments, track packages, manage labels, handle webhooks, and more — across Aymakan, SMSA Express, Aramex, and future carriers.
|
|
4
4
|
|
|
5
5
|
Think EasyPost / Shippo, but purpose-built for Saudi Arabia and the GCC.
|
|
6
6
|
|
|
@@ -25,6 +25,7 @@ bun add shipflow
|
|
|
25
25
|
import { ShipFlow } from "shipflow";
|
|
26
26
|
import { AymakanAdapter, AymakanService } from "shipflow/carriers/aymakan";
|
|
27
27
|
import { SMSAExpressAdapter, SMSAService } from "shipflow/carriers/smsaexpress";
|
|
28
|
+
import { AramexAdapter } from "shipflow/carriers/aramex";
|
|
28
29
|
|
|
29
30
|
const client = new ShipFlow({
|
|
30
31
|
adapters: [
|
|
@@ -36,6 +37,18 @@ const client = new ShipFlow({
|
|
|
36
37
|
mode: "sandbox",
|
|
37
38
|
credentials: { apiKey: process.env.SMSA_API_KEY! },
|
|
38
39
|
}),
|
|
40
|
+
// Aramex auth is a ClientInfo object sent in every request body (no API key)
|
|
41
|
+
new AramexAdapter({
|
|
42
|
+
mode: "sandbox",
|
|
43
|
+
credentials: {
|
|
44
|
+
userName: process.env.ARAMEX_USERNAME!,
|
|
45
|
+
password: process.env.ARAMEX_PASSWORD!,
|
|
46
|
+
accountNumber: process.env.ARAMEX_ACCOUNT_NUMBER!,
|
|
47
|
+
accountPin: process.env.ARAMEX_ACCOUNT_PIN!,
|
|
48
|
+
accountEntity: process.env.ARAMEX_ACCOUNT_ENTITY!, // e.g. "RUH"
|
|
49
|
+
accountCountryCode: process.env.ARAMEX_ACCOUNT_COUNTRY_CODE!, // e.g. "SA"
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
39
52
|
],
|
|
40
53
|
});
|
|
41
54
|
|
|
@@ -91,25 +104,26 @@ Every carrier adapter implements these **required** methods:
|
|
|
91
104
|
|
|
92
105
|
Plus these **optional** methods (availability varies by carrier):
|
|
93
106
|
|
|
94
|
-
| Method | Aymakan | SMSA |
|
|
95
|
-
| ------------------------------------ | ------- | ---- |
|
|
96
|
-
| `createBulkShipments(inputs)` | ✅ | — |
|
|
97
|
-
| `cancelByReference(ref)` | ✅ | — |
|
|
98
|
-
| `updateDeliveryAddress(tn, address)` | ✅ | — |
|
|
99
|
-
| `trackByReference(ref)` | ✅ | ✅ |
|
|
100
|
-
| `getBulkLabels(trackingNumbers)` | ✅ | — |
|
|
101
|
-
| `getPickupCities()` | ✅ | — |
|
|
102
|
-
| `getTimeSlots(city, date)` | ✅ | — |
|
|
103
|
-
| `createPickup(input)` | ✅ | — |
|
|
104
|
-
| `cancelPickup(id)` | ✅ | — |
|
|
105
|
-
| `getPickupRequests()` | ✅ | — |
|
|
106
|
-
| `getCities()` | ✅ | ✅ |
|
|
107
|
-
| `getDropoffLocations()` | ✅ | ✅ |
|
|
108
|
-
| `createCustomerAddress(addr)` | ✅ | — |
|
|
109
|
-
| `getCustomerAddresses()` | ✅ | — |
|
|
110
|
-
| `updateCustomerAddress(id, addr)` | ✅ | — |
|
|
111
|
-
| `deleteCustomerAddress(id)` | ✅ | — |
|
|
112
|
-
| `
|
|
107
|
+
| Method | Aymakan | SMSA | Aramex |
|
|
108
|
+
| ------------------------------------ | ------- | ---- | ------ |
|
|
109
|
+
| `createBulkShipments(inputs)` | ✅ | — | ✅ |
|
|
110
|
+
| `cancelByReference(ref)` | ✅ | — | — |
|
|
111
|
+
| `updateDeliveryAddress(tn, address)` | ✅ | — | — |
|
|
112
|
+
| `trackByReference(ref)` | ✅ | ✅ | ✅ |
|
|
113
|
+
| `getBulkLabels(trackingNumbers)` | ✅ | — | — |
|
|
114
|
+
| `getPickupCities()` | ✅ | — | — |
|
|
115
|
+
| `getTimeSlots(city, date)` | ✅ | — | — |
|
|
116
|
+
| `createPickup(input)` | ✅ | — | ✅ |
|
|
117
|
+
| `cancelPickup(id)` | ✅ | — | ✅ |
|
|
118
|
+
| `getPickupRequests()` | ✅ | — | — |
|
|
119
|
+
| `getCities()` | ✅ | ✅ | ✅ |
|
|
120
|
+
| `getDropoffLocations()` | ✅ | ✅ | ✅ |
|
|
121
|
+
| `createCustomerAddress(addr)` | ✅ | — | — |
|
|
122
|
+
| `getCustomerAddresses()` | ✅ | — | — |
|
|
123
|
+
| `updateCustomerAddress(id, addr)` | ✅ | — | — |
|
|
124
|
+
| `deleteCustomerAddress(id)` | ✅ | — | — |
|
|
125
|
+
| `getRates(input)` | — | — | ✅ |
|
|
126
|
+
| `parseWebhook(payload, options)` | ✅ | ✅ | — |
|
|
113
127
|
|
|
114
128
|
SMSA-specific methods:
|
|
115
129
|
|
|
@@ -123,19 +137,111 @@ SMSA-specific methods:
|
|
|
123
137
|
|
|
124
138
|
## Carrier Support
|
|
125
139
|
|
|
126
|
-
| Feature | Aymakan | SMSA Express |
|
|
127
|
-
| ----------------- | ------------------------------- | ---------------------------------- |
|
|
128
|
-
| Countries | SA, AE, BH, KW, OM, QA | SA, AE, BH, EG, KW, OM, QA, JO |
|
|
129
|
-
| Service types | 10 (ONP, SDD, RVP, EXH, ...) | 3 (EDDL, EDEL, EDCR) |
|
|
130
|
-
| Shipment creation | Single + Bulk | B2C + C2B + 2-Way |
|
|
131
|
-
| COD | ✅ | ✅ (B2C only) |
|
|
132
|
-
| Cancellation | By tracking # or reference | C2B only |
|
|
133
|
-
| Tracking | Single, bulk, by reference | Single, bulk, by reference |
|
|
134
|
-
| Labels | PDF/PNG, single + bulk | PDF/ZPL |
|
|
135
|
-
| Pickups | Full lifecycle | — |
|
|
136
|
-
| Webhooks | ✅ (with auth verification) | ✅ (batch, with auth verification) |
|
|
137
|
-
| City resolution | Arabic ↔ English smart matching | Code-based lookup |
|
|
138
|
-
| Rates | ❌ | ❌ |
|
|
140
|
+
| Feature | Aymakan | SMSA Express | Aramex |
|
|
141
|
+
| ----------------- | ------------------------------- | ---------------------------------- | -------------------------------------- |
|
|
142
|
+
| Countries | SA, AE, BH, KW, OM, QA | SA, AE, BH, EG, KW, OM, QA, JO | SA, AE, BH, KW, OM, QA, JO, EG, LB, IQ |
|
|
143
|
+
| Service types | 10 (ONP, SDD, RVP, EXH, ...) | 3 (EDDL, EDEL, EDCR) | 10 product types (OND, PPX, EPX, ...) |
|
|
144
|
+
| Shipment creation | Single + Bulk | B2C + C2B + 2-Way | Single + Bulk (native batch) |
|
|
145
|
+
| COD | ✅ | ✅ (B2C only) | ✅ |
|
|
146
|
+
| Cancellation | By tracking # or reference | C2B only | Pickups only (no shipment cancel API) |
|
|
147
|
+
| Tracking | Single, bulk, by reference | Single, bulk, by reference | Single, bulk, by reference |
|
|
148
|
+
| Labels | PDF/PNG, single + bulk | PDF/ZPL | URL (HTML/PDF) |
|
|
149
|
+
| Pickups | Full lifecycle | — | Create + cancel |
|
|
150
|
+
| Webhooks | ✅ (with auth verification) | ✅ (batch, with auth verification) | — (poll via tracking) |
|
|
151
|
+
| City resolution | Arabic ↔ English smart matching | Code-based lookup | Name list (FetchCities / FetchOffices) |
|
|
152
|
+
| Rates | ❌ | ❌ | ✅ (CalculateRate) |
|
|
153
|
+
|
|
154
|
+
## Aramex
|
|
155
|
+
|
|
156
|
+
Aramex is integrated via the **JSON flavor of the classic `ShippingAPI.V2` services**. A few
|
|
157
|
+
things make it different from the other carriers:
|
|
158
|
+
|
|
159
|
+
- **Auth is a `ClientInfo` object in every request body** (no API key / header, no token
|
|
160
|
+
exchange). Pass `userName`, `password`, `accountNumber`, `accountPin`, `accountEntity` (the
|
|
161
|
+
3-letter origin office, e.g. `RUH`/`DXB`/`AMM`) and `accountCountryCode`.
|
|
162
|
+
- **Four independent services on separate hosts** — Shipping, Tracking, RateCalculator, and
|
|
163
|
+
Location. The adapter holds one HTTP client per service and routes automatically. If your
|
|
164
|
+
account provisions the Location service on a different host (some WSDLs use `anfe02.aramex.com`),
|
|
165
|
+
set `locationBaseUrl` on the config.
|
|
166
|
+
- **"Fake 200 OK" errors** — Aramex returns HTTP 200 even on logical failures, with
|
|
167
|
+
`HasErrors: true` + `Notifications[]`. ShipFlow surfaces these as `APIError`, including
|
|
168
|
+
per-shipment errors inside an otherwise-clean `CreateShipments` batch.
|
|
169
|
+
- **Rates are supported** (`getRates` → `CalculateRate`), unlike Aymakan/SMSA.
|
|
170
|
+
- **`cancelShipment` is unsupported** (the classic API has no shipment-cancel operation) and
|
|
171
|
+
throws `UnsupportedOperationError`. Pickups can be cancelled via `cancelPickup`.
|
|
172
|
+
- **Labels resolve to a URL** — the `format` argument of `getLabel` can't be honored.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { AramexAdapter, AramexProductType } from "shipflow/carriers/aramex";
|
|
176
|
+
|
|
177
|
+
const aramex = client.carrier("aramex");
|
|
178
|
+
|
|
179
|
+
// Create a domestic COD shipment (freight prepaid, cash collected on delivery)
|
|
180
|
+
const shipment = await aramex.createShipment({
|
|
181
|
+
shipper: {
|
|
182
|
+
name: "My Store",
|
|
183
|
+
company: "ShipFlow",
|
|
184
|
+
phone: "966500000000",
|
|
185
|
+
line1: "King Fahd Road",
|
|
186
|
+
city: "Riyadh",
|
|
187
|
+
countryCode: "SA",
|
|
188
|
+
},
|
|
189
|
+
consignee: {
|
|
190
|
+
name: "Customer",
|
|
191
|
+
phone: "966500000001",
|
|
192
|
+
line1: "Prince Sultan Road",
|
|
193
|
+
city: "Jeddah",
|
|
194
|
+
countryCode: "SA",
|
|
195
|
+
},
|
|
196
|
+
parcels: [{ weight: { value: 1, unit: "kg" }, pieces: 1 }],
|
|
197
|
+
cod: { enabled: true, amount: 150, currency: "SAR" },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Quote a rate, then track
|
|
201
|
+
const rates = await aramex.getRates!(input);
|
|
202
|
+
const result = await aramex.track(shipment.trackingNumber);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Product group / type & payment** — ShipFlow infers `DOM` (domestic) when shipper and consignee
|
|
206
|
+
share a country, else `EXP`, and picks a sensible default product type (`OND` for domestic, `EPX`
|
|
207
|
+
for express). Override with `serviceType` (a valid Aramex code) or
|
|
208
|
+
`options.metadata.productGroup` / `productType`.
|
|
209
|
+
|
|
210
|
+
The freight **`PaymentType`** — who pays the *shipping cost* — defaults to `P` and is independent
|
|
211
|
+
of COD: enabling COD adds the `CODS` service and the cash amount to collect from the consignee, but
|
|
212
|
+
does **not** charge them freight. Override the freight payer with `options.metadata.paymentType`:
|
|
213
|
+
|
|
214
|
+
| Value | Freight billed to | When to use |
|
|
215
|
+
| ----- | ----------------- | ----------- |
|
|
216
|
+
| `"P"` _(default)_ | Shipper's Aramex account (prepaid) | Standard KSA/GCC e-commerce — merchant pays shipping, even with COD |
|
|
217
|
+
| `"C"` | Consignee, collected at delivery | Customer pays shipping on top of any COD |
|
|
218
|
+
| `"3"` | A third-party account | Freight billed to someone other than shipper/consignee |
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// Default: COD shipment with prepaid freight (PaymentType "P")
|
|
222
|
+
await aramex.createShipment({
|
|
223
|
+
...input,
|
|
224
|
+
cod: { enabled: true, amount: 150, currency: "SAR" }, // freight stays "P"
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Override: charge the customer freight at the door (PaymentType "C")
|
|
228
|
+
await aramex.createShipment({
|
|
229
|
+
...input,
|
|
230
|
+
cod: { enabled: true, amount: 150, currency: "SAR" },
|
|
231
|
+
options: { metadata: { paymentType: "C" } },
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Override: bill freight to a third party (PaymentType "3")
|
|
235
|
+
await aramex.createShipment({
|
|
236
|
+
...input,
|
|
237
|
+
options: { metadata: { paymentType: "3" } },
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> Aramex does not push webhooks — poll `track()` / `trackMultiple()` for status updates.
|
|
242
|
+
> Tracking `UpdateCode`s vary by region and aren't fully published, so ShipFlow maps known codes
|
|
243
|
+
> and falls back to a description-keyword heuristic (then `"unknown"`) — an unmapped code never
|
|
244
|
+
> breaks tracking.
|
|
139
245
|
|
|
140
246
|
## Webhook Handling
|
|
141
247
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/aramex/adapter.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAYH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/aramex/adapter.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAYH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAsD7C,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,WAAW,EAAE;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,aAAc,SAAQ,kBAAkB;IACnD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,kBAAkB,WAWzB;IAEF,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,YAAY,CAAa;gBAErB,MAAM,EAAE,YAAY;IAuBhC,SAAS,CAAC,UAAU,IAAI,MAAM;IAM9B,8EAA8E;IAC9E,OAAO,CAAC,WAAW;IAInB,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,CAAC,eAAe;IAQvB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAyBnC,OAAO,CAAC,MAAM,CAAC,qBAAqB;cAcpB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,QAAQ,CAAC;IA6CpB;;;;;;;;OAQG;IACG,mBAAmB,CACvB,MAAM,EAAE,mBAAmB,EAAE,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC;IAwDtB;;;OAGG;IACH,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnD,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAC9B,OAAO,CAAC,MAAM,CAAC;IA8BZ,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAetD,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IA4BnE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAU5D,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA4BrD,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAyDnD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBzD,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAehD,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAcrE"}
|
|
@@ -119,7 +119,7 @@ function resolvePaymentType(input) {
|
|
|
119
119
|
const meta = getMeta(input, "paymentType");
|
|
120
120
|
if (meta === "P" || meta === "C" || meta === "3")
|
|
121
121
|
return meta;
|
|
122
|
-
return
|
|
122
|
+
return "P";
|
|
123
123
|
}
|
|
124
124
|
function aggregateWeight(input) {
|
|
125
125
|
const allLb = input.parcels.every((p) => p.weight.unit === "lb");
|
|
@@ -141,19 +141,31 @@ function mapDimensions(dims) {
|
|
|
141
141
|
Unit: "CM"
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
function
|
|
144
|
+
function buildPartyAddress(fields) {
|
|
145
145
|
return {
|
|
146
|
-
Line1:
|
|
147
|
-
Line2:
|
|
148
|
-
Line3:
|
|
149
|
-
City:
|
|
150
|
-
StateOrProvinceCode:
|
|
151
|
-
PostCode:
|
|
152
|
-
CountryCode:
|
|
153
|
-
Longitude:
|
|
154
|
-
Latitude:
|
|
146
|
+
Line1: fields.line1,
|
|
147
|
+
Line2: fields.line2 ?? "",
|
|
148
|
+
Line3: fields.line3 ?? "",
|
|
149
|
+
City: fields.city,
|
|
150
|
+
StateOrProvinceCode: fields.state,
|
|
151
|
+
PostCode: fields.postCode ?? "",
|
|
152
|
+
CountryCode: fields.countryCode,
|
|
153
|
+
Longitude: fields.coordinates?.longitude,
|
|
154
|
+
Latitude: fields.coordinates?.latitude
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
+
function mapAddress(addr) {
|
|
158
|
+
return buildPartyAddress({
|
|
159
|
+
line1: addr.line1,
|
|
160
|
+
line2: addr.line2,
|
|
161
|
+
line3: addr.neighbourhood,
|
|
162
|
+
city: addr.city,
|
|
163
|
+
state: addr.state,
|
|
164
|
+
postCode: addr.postalCode,
|
|
165
|
+
countryCode: addr.countryCode,
|
|
166
|
+
coordinates: addr.coordinates
|
|
167
|
+
});
|
|
168
|
+
}
|
|
157
169
|
function buildContact(opts) {
|
|
158
170
|
return {
|
|
159
171
|
Department: "",
|
|
@@ -257,11 +269,11 @@ function mapPickupRequest(input, ctx) {
|
|
|
257
269
|
const closing = new Date(`${input.date}T17:00:00`);
|
|
258
270
|
return {
|
|
259
271
|
Reference1: input.trackingNumbers?.[0],
|
|
260
|
-
PickupAddress: {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
},
|
|
272
|
+
PickupAddress: buildPartyAddress({
|
|
273
|
+
line1: input.address,
|
|
274
|
+
city: input.city,
|
|
275
|
+
countryCode: ctx.countryCode
|
|
276
|
+
}),
|
|
265
277
|
PickupContact: buildContact({
|
|
266
278
|
personName: input.contactName,
|
|
267
279
|
companyName: ctx.companyName ?? input.contactName,
|
|
@@ -347,6 +359,16 @@ function normalizeTrackingResults(raw) {
|
|
|
347
359
|
}
|
|
348
360
|
return raw;
|
|
349
361
|
}
|
|
362
|
+
function mapNonExistingWaybill(waybill) {
|
|
363
|
+
return {
|
|
364
|
+
trackingNumber: waybill,
|
|
365
|
+
carrier: "aramex",
|
|
366
|
+
status: "unknown",
|
|
367
|
+
statusLabel: "Waybill not found",
|
|
368
|
+
events: [],
|
|
369
|
+
raw: { nonExisting: true, waybill }
|
|
370
|
+
};
|
|
371
|
+
}
|
|
350
372
|
function mapRate(response, input) {
|
|
351
373
|
const { productType } = resolveProductGroupAndType(input);
|
|
352
374
|
return {
|
|
@@ -548,7 +570,7 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
548
570
|
Transaction: EMPTY_TRANSACTION,
|
|
549
571
|
ShipmentNumber: trackingNumber,
|
|
550
572
|
LabelInfo: DEFAULT_LABEL_INFO
|
|
551
|
-
}, { errorExtractor: AramexAdapter.aramexErrorExtractor });
|
|
573
|
+
}, { retry: true, errorExtractor: AramexAdapter.aramexErrorExtractor });
|
|
552
574
|
const url = response.ShipmentLabel?.LabelURL;
|
|
553
575
|
if (!url) {
|
|
554
576
|
throw new APIError("Failed to get label", {
|
|
@@ -561,10 +583,10 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
561
583
|
async track(trackingNumber) {
|
|
562
584
|
const results = await this.trackMultiple([trackingNumber]);
|
|
563
585
|
const result = results[0];
|
|
564
|
-
if (!result) {
|
|
586
|
+
if (!result || result.status === "unknown" && result.events.length === 0) {
|
|
565
587
|
throw new APIError("Shipment not found", {
|
|
566
588
|
carrier: "aramex",
|
|
567
|
-
raw: { trackingNumber }
|
|
589
|
+
raw: result?.raw ?? { trackingNumber }
|
|
568
590
|
});
|
|
569
591
|
}
|
|
570
592
|
return result;
|
|
@@ -577,7 +599,9 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
577
599
|
GetLastTrackingUpdateOnly: false
|
|
578
600
|
}, { retry: true, errorExtractor: AramexAdapter.aramexErrorExtractor });
|
|
579
601
|
const map = normalizeTrackingResults(response.TrackingResults);
|
|
580
|
-
|
|
602
|
+
const found = Object.entries(map).map(([waybill, results]) => mapTrackingResult(waybill, results));
|
|
603
|
+
const nonExisting = (response.NonExistingWaybills ?? []).map(mapNonExistingWaybill);
|
|
604
|
+
return [...found, ...nonExisting];
|
|
581
605
|
}
|
|
582
606
|
async trackByReference(reference) {
|
|
583
607
|
const result = await this.track(reference);
|
|
@@ -618,6 +642,15 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
618
642
|
raw: response
|
|
619
643
|
});
|
|
620
644
|
}
|
|
645
|
+
const failedShipments = (processed.ProcessedShipments ?? []).filter((s) => s.HasErrors);
|
|
646
|
+
if (failedShipments.length > 0) {
|
|
647
|
+
const notifications = failedShipments.flatMap((s) => s.Notifications ?? []);
|
|
648
|
+
throw new APIError(notifications.map((n) => n.Message).filter(Boolean).join("; ") || `Pickup created but ${failedShipments.length} shipment(s) failed`, {
|
|
649
|
+
carrier: "aramex",
|
|
650
|
+
errors: AramexAdapter.notificationsToErrors(notifications),
|
|
651
|
+
raw: response
|
|
652
|
+
});
|
|
653
|
+
}
|
|
621
654
|
return mapPickupResponse(processed, input);
|
|
622
655
|
}
|
|
623
656
|
async cancelPickup(pickupId) {
|
|
@@ -658,4 +691,4 @@ export {
|
|
|
658
691
|
AramexAdapter
|
|
659
692
|
};
|
|
660
693
|
|
|
661
|
-
//# debugId=
|
|
694
|
+
//# debugId=BC16779A6E5B3D1F64756E2164756E21
|