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 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
- | `parseWebhook(payload, options)` | | ✅ |
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;AAqD7C,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;IA4BZ,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAYtD,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAkBnE,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;IAgCnD,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"}
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 input.cod?.enabled ? "C" : "P";
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 mapAddress(addr) {
144
+ function buildPartyAddress(fields) {
145
145
  return {
146
- Line1: addr.line1,
147
- Line2: addr.line2 ?? "",
148
- Line3: addr.neighbourhood ?? "",
149
- City: addr.city,
150
- StateOrProvinceCode: addr.state,
151
- PostCode: addr.postalCode ?? "",
152
- CountryCode: addr.countryCode,
153
- Longitude: addr.coordinates?.longitude,
154
- Latitude: addr.coordinates?.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
- Line1: input.address,
262
- City: input.city,
263
- CountryCode: ctx.countryCode
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
- return Object.entries(map).map(([waybill, results]) => mapTrackingResult(waybill, results));
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=F2518B67CA4397C364756E2164756E21
694
+ //# debugId=BC16779A6E5B3D1F64756E2164756E21