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.
Files changed (44) hide show
  1. package/README.md +328 -0
  2. package/dist/carriers/aymakan/adapter.d.ts +87 -0
  3. package/dist/carriers/aymakan/adapter.d.ts.map +1 -0
  4. package/dist/carriers/aymakan/index.d.ts +9 -0
  5. package/dist/carriers/aymakan/index.d.ts.map +1 -0
  6. package/dist/carriers/aymakan/index.js +625 -0
  7. package/dist/carriers/aymakan/index.js.map +12 -0
  8. package/dist/carriers/aymakan/mappers.d.ts +21 -0
  9. package/dist/carriers/aymakan/mappers.d.ts.map +1 -0
  10. package/dist/carriers/aymakan/services.d.ts +47 -0
  11. package/dist/carriers/aymakan/services.d.ts.map +1 -0
  12. package/dist/carriers/aymakan/types.d.ts +287 -0
  13. package/dist/carriers/aymakan/types.d.ts.map +1 -0
  14. package/dist/carriers/base.d.ts +88 -0
  15. package/dist/carriers/base.d.ts.map +1 -0
  16. package/dist/carriers/smsaexpress/adapter.d.ts +57 -0
  17. package/dist/carriers/smsaexpress/adapter.d.ts.map +1 -0
  18. package/dist/carriers/smsaexpress/index.d.ts +9 -0
  19. package/dist/carriers/smsaexpress/index.d.ts.map +1 -0
  20. package/dist/carriers/smsaexpress/index.js +408 -0
  21. package/dist/carriers/smsaexpress/index.js.map +12 -0
  22. package/dist/carriers/smsaexpress/mappers.d.ts +45 -0
  23. package/dist/carriers/smsaexpress/mappers.d.ts.map +1 -0
  24. package/dist/carriers/smsaexpress/services.d.ts +21 -0
  25. package/dist/carriers/smsaexpress/services.d.ts.map +1 -0
  26. package/dist/carriers/smsaexpress/types.d.ts +197 -0
  27. package/dist/carriers/smsaexpress/types.d.ts.map +1 -0
  28. package/dist/client.d.ts +44 -0
  29. package/dist/client.d.ts.map +1 -0
  30. package/dist/core/errors.d.ts +69 -0
  31. package/dist/core/errors.d.ts.map +1 -0
  32. package/dist/core/http.d.ts +72 -0
  33. package/dist/core/http.d.ts.map +1 -0
  34. package/dist/core/schemas.d.ts +832 -0
  35. package/dist/core/schemas.d.ts.map +1 -0
  36. package/dist/core/types.d.ts +234 -0
  37. package/dist/core/types.d.ts.map +1 -0
  38. package/dist/index-x8sk1kw9.js +4600 -0
  39. package/dist/index-x8sk1kw9.js.map +22 -0
  40. package/dist/index.d.ts +27 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +109 -0
  43. package/dist/index.js.map +10 -0
  44. package/package.json +63 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * SMSA Express Carrier Adapter
3
+ * Full implementation of the CarrierAdapter interface for SMSA Express API.
4
+ */
5
+ import type { CarrierConfig, City, CreateShipmentInput, Location, Shipment, TrackingResult, WebhookConfig, WebhookEvent } from "../../core/types.js";
6
+ import { BaseCarrierAdapter } from "../base.js";
7
+ import type { SMSAPushIdDetailsRequest, SMSAPushIdDetailsResponse, SMSASendInvoiceRequest, SMSAShortAddressResponse } from "./types.js";
8
+ export interface SMSAExpressConfig extends CarrierConfig {
9
+ credentials: {
10
+ apiKey: string;
11
+ };
12
+ }
13
+ export declare class SMSAExpressAdapter extends BaseCarrierAdapter {
14
+ readonly name = "smsaexpress";
15
+ readonly supportedCountries: string[];
16
+ private http;
17
+ constructor(config: SMSAExpressConfig);
18
+ protected getBaseUrl(): string;
19
+ protected executeCreateShipment(input: CreateShipmentInput): Promise<Shipment>;
20
+ private createC2BShipment;
21
+ /**
22
+ * Cancel a reverse-pickup (C2B) shipment.
23
+ *
24
+ * **Important:** SMSA only supports cancellation for C2B/reverse-pickup shipments.
25
+ * Calling this on a B2C shipment will result in an API error or return `false`.
26
+ * B2C shipments cannot be cancelled via the SMSA API.
27
+ */
28
+ cancelShipment(trackingNumber: string): Promise<boolean>;
29
+ track(trackingNumber: string): Promise<TrackingResult>;
30
+ trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]>;
31
+ trackByReference(reference: string): Promise<TrackingResult>;
32
+ getLabel(trackingNumber: string, _format?: "PDF" | "PNG"): Promise<string>;
33
+ getCities(countryCode?: string): Promise<City[]>;
34
+ getDropoffLocations(): Promise<Location[]>;
35
+ create2WayShipment(input: CreateShipmentInput): Promise<Shipment>;
36
+ sendInvoice(request: SMSASendInvoiceRequest): Promise<string>;
37
+ validateShortAddress(shortCode: string): Promise<SMSAShortAddressResponse>;
38
+ pushIdDetails(request: SMSAPushIdDetailsRequest): Promise<SMSAPushIdDetailsResponse>;
39
+ parseWebhook(payload: unknown, options?: {
40
+ headers?: Record<string, string>;
41
+ queryParams?: Record<string, string>;
42
+ config?: WebhookConfig;
43
+ }): WebhookEvent;
44
+ /**
45
+ * Parse a batch SMSA webhook payload into multiple events.
46
+ *
47
+ * SMSA sends webhook payloads as an array of shipments, each with their own
48
+ * tracking scans. This method returns a WebhookEvent for every shipment
49
+ * in the payload.
50
+ */
51
+ parseWebhookBatch(payload: unknown, options?: {
52
+ headers?: Record<string, string>;
53
+ queryParams?: Record<string, string>;
54
+ config?: WebhookConfig;
55
+ }): WebhookEvent[];
56
+ }
57
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/adapter.ts"],"names":[],"mappings":"AACA;;;GAGG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAY7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EAEtB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAQjB,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,qBAAa,kBAAmB,SAAQ,kBAAkB;IACxD,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,QAAQ,CAAC,kBAAkB,WASzB;IAEF,OAAO,CAAC,IAAI,CAAa;gBAEb,MAAM,EAAE,iBAAiB;IAWrC,SAAS,CAAC,UAAU,IAAI,MAAM;cAUd,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,QAAQ,CAAC;YAgBN,iBAAiB;IAY/B;;;;;;OAMG;IACG,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAcxD,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAQtD,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAUnE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAY5D,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,KAAK,GAAG,KAAK,GACtB,OAAO,CAAC,MAAM,CAAC;IAsCZ,SAAS,CAAC,WAAW,SAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAQ9C,mBAAmB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAY1C,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAcjE,WAAW,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7D,oBAAoB,CACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC;IAM9B,aAAa,CACjB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,yBAAyB,CAAC;IAWrC,YAAY,CACV,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,GACA,YAAY;IAIf;;;;;;OAMG;IACH,iBAAiB,CACf,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,aAAa,CAAC;KACxB,GACA,YAAY,EAAE;CAGlB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SMSA Express Carrier Exports
3
+ * Import from 'shipflow/carriers/smsaexpress' for tree-shaking.
4
+ */
5
+ export { SMSAExpressAdapter, type SMSAExpressConfig } from "./adapter.js";
6
+ export { SMSAService, SMSAStatusCodes, type SMSAServiceType } from "./services.js";
7
+ export { mapSMSAStatus, parseSMSAWebhook, parseSMSAWebhookBatch, } from "./mappers.js";
8
+ export type { SMSACreate2WayShipmentRequest, SMSACreateB2CShipmentRequest, SMSACreateC2BShipmentRequest, SMSAPushIdDetailsRequest, SMSAPushIdDetailsResponse, SMSASendInvoiceRequest, SMSAShipmentResponse, SMSAShortAddressResponse, SMSATrackingResponse, SMSATrackingScan, SMSAWebhookShipment, } from "./types.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/index.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGvE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAGhF,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAGnB,YAAY,EACV,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,408 @@
1
+ import {
2
+ APIError,
3
+ BaseCarrierAdapter,
4
+ HttpClient,
5
+ ValidationError,
6
+ WebhookVerificationError
7
+ } from "../../index-x8sk1kw9.js";
8
+
9
+ // src/carriers/smsaexpress/services.ts
10
+ var SMSAService = {
11
+ ECOMMERCE_DELIVERY: "EDDL",
12
+ EXPRESS_DELIVERY: "EDEL",
13
+ C2B_REVERSE: "EDCR"
14
+ };
15
+ var SMSAStatusCodes = {
16
+ DL: "delivered",
17
+ OD: "out_for_delivery",
18
+ AF: "at_warehouse",
19
+ HOP: "in_transit",
20
+ HOR: "in_transit",
21
+ HOT: "in_transit",
22
+ PU: "picked_up",
23
+ PKD: "picked_up",
24
+ CR: "in_transit",
25
+ CH: "in_transit",
26
+ DE: "exception",
27
+ DMG: "exception",
28
+ MISS: "exception",
29
+ CAN: "cancelled",
30
+ RTO: "returned",
31
+ RTN: "returned",
32
+ CC: "at_warehouse",
33
+ PP: "pending",
34
+ BK: "created",
35
+ NEW: "created"
36
+ };
37
+
38
+ // src/carriers/smsaexpress/mappers.ts
39
+ function mapSMSAStatus(scanType) {
40
+ return SMSAStatusCodes[scanType] ?? "in_transit";
41
+ }
42
+ function deriveStatusFromScans(scans, isDelivered) {
43
+ if (isDelivered) {
44
+ return { status: "delivered", statusLabel: "Delivered" };
45
+ }
46
+ if (scans.length === 0) {
47
+ return { status: "unknown", statusLabel: "Unknown" };
48
+ }
49
+ const sorted = [...scans].sort((a, b) => new Date(b.ScanDateTime).getTime() - new Date(a.ScanDateTime).getTime());
50
+ const latest = sorted[0];
51
+ return {
52
+ status: mapSMSAStatus(latest.ScanType),
53
+ statusLabel: latest.ScanDescription
54
+ };
55
+ }
56
+ function mapAddress(addr) {
57
+ const result = {
58
+ ContactName: addr.name,
59
+ ContactPhoneNumber: addr.phone,
60
+ Country: addr.countryCode,
61
+ City: addr.city,
62
+ AddressLine1: addr.line1,
63
+ AddressLine2: addr.line2,
64
+ District: addr.neighbourhood ?? addr.state,
65
+ PostalCode: addr.postalCode
66
+ };
67
+ if (addr.coordinates) {
68
+ result.Coordinates = `${addr.coordinates.latitude},${addr.coordinates.longitude}`;
69
+ }
70
+ if (addr.nationalAddress?.shortCode) {
71
+ result.ShortCode = addr.nationalAddress.shortCode;
72
+ }
73
+ return result;
74
+ }
75
+ function mapCreateB2CRequest(input) {
76
+ const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);
77
+ const totalWeight = input.parcels.reduce((sum, p) => sum + (p.weight.unit === "lb" ? p.weight.value * 0.453592 : p.weight.value), 0);
78
+ const consigneeAddress = mapAddress(input.consignee);
79
+ if (input.options?.metadata?.consigneeId) {
80
+ consigneeAddress.ConsigneeID = input.options.metadata.consigneeId;
81
+ }
82
+ return {
83
+ ConsigneeAddress: consigneeAddress,
84
+ ShipperAddress: mapAddress(input.shipper),
85
+ OrderNumber: input.reference ?? `ORD-${Date.now()}`,
86
+ CODAmount: input.cod?.enabled ? input.cod.amount : 0,
87
+ DeclaredValue: input.declaredValue?.amount ?? 0,
88
+ ContentDescription: input.parcels[0]?.description ?? "Shipment contents",
89
+ Parcels: totalPieces,
90
+ ShipDate: new Date().toISOString().slice(0, 19),
91
+ ShipmentCurrency: input.declaredValue?.currency ?? input.cod?.currency ?? "SAR",
92
+ Weight: totalWeight,
93
+ WeightUnit: "KG",
94
+ WaybillType: input.labelFormat === "ZPL" ? "ZPL" : "PDF",
95
+ ServiceCode: input.serviceType,
96
+ SMSARetailID: input.options?.metadata?.smsaRetailId ?? "0",
97
+ VatPaid: input.options?.metadata?.vatPaid ?? true,
98
+ DutyPaid: input.options?.metadata?.dutyPaid ?? false
99
+ };
100
+ }
101
+ function mapCreateC2BRequest(input) {
102
+ const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);
103
+ const totalWeight = input.parcels.reduce((sum, p) => sum + (p.weight.unit === "lb" ? p.weight.value * 0.453592 : p.weight.value), 0);
104
+ return {
105
+ PickupAddress: mapAddress(input.consignee),
106
+ ReturnToAddress: mapAddress(input.shipper),
107
+ OrderNumber: input.reference ?? `ORD-${Date.now()}`,
108
+ DeclaredValue: input.declaredValue?.amount ?? 0.1,
109
+ ContentDescription: input.parcels[0]?.description ?? "Return shipment contents",
110
+ Parcels: totalPieces,
111
+ ShipDate: new Date().toISOString().slice(0, 19),
112
+ ShipmentCurrency: input.declaredValue?.currency ?? input.cod?.currency ?? "SAR",
113
+ Weight: totalWeight,
114
+ WeightUnit: "KG",
115
+ WaybillType: input.labelFormat === "ZPL" ? "ZPL" : "PDF",
116
+ ServiceCode: input.serviceType ?? "EDCR",
117
+ SMSARetailID: input.options?.metadata?.smsaRetailId ?? "0"
118
+ };
119
+ }
120
+ function mapShipmentResponse(data, input) {
121
+ const firstWaybill = data.waybills?.[0];
122
+ const trackingNumber = firstWaybill?.awb ?? data.sawb;
123
+ if (!trackingNumber) {
124
+ throw new APIError("No tracking number in shipment response", {
125
+ carrier: "smsaexpress",
126
+ raw: data
127
+ });
128
+ }
129
+ return {
130
+ carrier: "smsaexpress",
131
+ trackingNumber,
132
+ reference: input.reference,
133
+ status: "created",
134
+ statusLabel: "Created",
135
+ codAmount: input.cod?.enabled ? input.cod.amount : undefined,
136
+ declaredValue: input.declaredValue?.amount,
137
+ currency: input.declaredValue?.currency ?? input.cod?.currency ?? "SAR",
138
+ returnLabel: firstWaybill?.returnBarcode ? `data:application/pdf;base64,${firstWaybill.returnBarcode}` : undefined,
139
+ createdAt: new Date(/[Z]$|[+-]\d{2}(:\d{2})?$/.test(data.createDate) ? data.createDate : `${data.createDate}+03:00`),
140
+ raw: data
141
+ };
142
+ }
143
+ function mapTrackingEvent(scan) {
144
+ return {
145
+ timestamp: new Date(scan.ScanTimeZone ? `${scan.ScanDateTime}${scan.ScanTimeZone}` : scan.ScanDateTime),
146
+ statusCode: scan.ScanType,
147
+ status: mapSMSAStatus(scan.ScanType),
148
+ description: scan.ScanDescription,
149
+ location: scan.City
150
+ };
151
+ }
152
+ function mapTrackingResult(data) {
153
+ const { status, statusLabel } = deriveStatusFromScans(data.Scans, data.isDelivered);
154
+ const deliveredScan = data.Scans.find((s) => s.ScanType === "DL");
155
+ const deliveredTimestamp = deliveredScan ? new Date(deliveredScan.ScanTimeZone ? `${deliveredScan.ScanDateTime}${deliveredScan.ScanTimeZone}` : deliveredScan.ScanDateTime) : undefined;
156
+ return {
157
+ trackingNumber: data.AWB,
158
+ carrier: "smsaexpress",
159
+ reference: data.Reference || undefined,
160
+ status,
161
+ statusLabel,
162
+ events: data.Scans.map(mapTrackingEvent),
163
+ deliveryDate: deliveredTimestamp,
164
+ codAmount: data.CODAmount > 0 ? data.CODAmount : undefined,
165
+ pieces: data.Pieces,
166
+ raw: data
167
+ };
168
+ }
169
+ function mapCity(city) {
170
+ return {
171
+ nameEn: city.cityName,
172
+ code: city.cityCode
173
+ };
174
+ }
175
+ function mapOffice(office) {
176
+ const [lat, lng] = (office.coordinates || "").split(",").map((s) => parseFloat(s.trim()));
177
+ return {
178
+ id: office.code,
179
+ name: office.address,
180
+ nameAr: office.addressAR,
181
+ city: office.cityName,
182
+ latitude: Number.isNaN(lat) ? undefined : lat,
183
+ longitude: Number.isNaN(lng) ? undefined : lng
184
+ };
185
+ }
186
+ function mapCreate2WayRequest(input) {
187
+ const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);
188
+ const totalWeight = input.parcels.reduce((sum, p) => sum + (p.weight.unit === "lb" ? p.weight.value * 0.453592 : p.weight.value), 0);
189
+ const consigneeAddress = mapAddress(input.consignee);
190
+ if (input.options?.metadata?.consigneeId) {
191
+ consigneeAddress.ConsigneeID = input.options.metadata.consigneeId;
192
+ }
193
+ return {
194
+ ConsigneeAddress: consigneeAddress,
195
+ ShipperAddress: mapAddress(input.shipper),
196
+ OrderNumber: input.reference ?? `ORD-${Date.now()}`,
197
+ DeclaredValue: input.declaredValue?.amount ?? 0,
198
+ ContentDescription: input.parcels[0]?.description ?? "Shipment contents",
199
+ Parcels: totalPieces,
200
+ ShipDate: new Date().toISOString().slice(0, 19),
201
+ ShipmentCurrency: input.declaredValue?.currency ?? "SAR",
202
+ Weight: totalWeight,
203
+ WeightUnit: "KG",
204
+ WaybillType: input.labelFormat === "ZPL" ? "ZPL" : "PDF",
205
+ SMSARetailID: input.options?.metadata?.smsaRetailId ?? "0",
206
+ VatPaid: input.options?.metadata?.vatPaid ?? true,
207
+ DutyPaid: input.options?.metadata?.dutyPaid ?? false
208
+ };
209
+ }
210
+ function timingSafeEqual(a, b) {
211
+ const encoder = new TextEncoder;
212
+ const bufA = encoder.encode(a);
213
+ const bufB = encoder.encode(b);
214
+ if (bufA.byteLength !== bufB.byteLength)
215
+ return false;
216
+ let mismatch = 0;
217
+ for (let i = 0;i < bufA.byteLength; i++) {
218
+ mismatch |= bufA[i] ^ bufB[i];
219
+ }
220
+ return mismatch === 0;
221
+ }
222
+ function verifyWebhookAuth(options) {
223
+ const { headers = {}, queryParams = {}, config } = options ?? {};
224
+ if (config?.authHeader && config?.authValue) {
225
+ const lowerKey = config.authHeader.toLowerCase();
226
+ const headerValue = Object.entries(headers).find(([k]) => k.toLowerCase() === lowerKey)?.[1];
227
+ if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {
228
+ throw new WebhookVerificationError("Invalid webhook auth header", {
229
+ carrier: "smsaexpress"
230
+ });
231
+ }
232
+ }
233
+ if (config?.authQueryParam && config?.authQueryValue) {
234
+ const paramValue = queryParams[config.authQueryParam];
235
+ if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {
236
+ throw new WebhookVerificationError("Invalid webhook auth query param", {
237
+ carrier: "smsaexpress"
238
+ });
239
+ }
240
+ }
241
+ }
242
+ function mapWebhookShipmentToEvent(shipment) {
243
+ const { status, statusLabel } = deriveStatusFromScans(shipment.Scans, shipment.isDelivered);
244
+ const sorted = [...shipment.Scans].sort((a, b) => new Date(b.ScanDateTime).getTime() - new Date(a.ScanDateTime).getTime());
245
+ const latestScan = sorted[0];
246
+ const timestamp = latestScan ? new Date(latestScan.ScanTimeZone ? `${latestScan.ScanDateTime}${latestScan.ScanTimeZone}` : latestScan.ScanDateTime) : new Date;
247
+ return {
248
+ carrier: "smsaexpress",
249
+ eventType: "status_update",
250
+ trackingNumber: shipment.AWB,
251
+ reference: shipment.Reference || undefined,
252
+ status,
253
+ statusCode: latestScan?.ScanType ?? "unknown",
254
+ statusLabel,
255
+ timestamp,
256
+ raw: shipment
257
+ };
258
+ }
259
+ function validateWebhookPayload(payload) {
260
+ if (!Array.isArray(payload)) {
261
+ throw new ValidationError("Invalid SMSA webhook payload: expected an array of shipments", { raw: payload });
262
+ }
263
+ if (payload.length === 0) {
264
+ throw new ValidationError("Invalid SMSA webhook payload: empty array", {
265
+ raw: payload
266
+ });
267
+ }
268
+ for (const item of payload) {
269
+ if (!item || typeof item !== "object" || !("AWB" in item) || !("Scans" in item)) {
270
+ throw new ValidationError("Invalid SMSA webhook payload: shipment missing required fields (AWB, Scans)", { raw: item });
271
+ }
272
+ }
273
+ return payload;
274
+ }
275
+ function parseSMSAWebhook(payload, options) {
276
+ verifyWebhookAuth(options);
277
+ const shipments = validateWebhookPayload(payload);
278
+ return mapWebhookShipmentToEvent(shipments[0]);
279
+ }
280
+ function parseSMSAWebhookBatch(payload, options) {
281
+ verifyWebhookAuth(options);
282
+ const shipments = validateWebhookPayload(payload);
283
+ return shipments.map(mapWebhookShipmentToEvent);
284
+ }
285
+
286
+ // src/carriers/smsaexpress/adapter.ts
287
+ var SMSA_SANDBOX_URL = "https://ecomapis-sandbox.azurewebsites.net";
288
+ var SMSA_PRODUCTION_URL = "https://ecomapis.smsaexpress.com";
289
+ var C2B_SERVICE_CODES = new Set(["EDCR"]);
290
+
291
+ class SMSAExpressAdapter extends BaseCarrierAdapter {
292
+ name = "smsaexpress";
293
+ supportedCountries = [
294
+ "SA",
295
+ "AE",
296
+ "BH",
297
+ "EG",
298
+ "KW",
299
+ "OM",
300
+ "QA",
301
+ "JO"
302
+ ];
303
+ http;
304
+ constructor(config) {
305
+ super(config);
306
+ this.http = new HttpClient({
307
+ baseUrl: this.getBaseUrl(),
308
+ carrier: "smsaexpress",
309
+ headers: {
310
+ apikey: config.credentials.apiKey
311
+ }
312
+ });
313
+ }
314
+ getBaseUrl() {
315
+ return this.config.mode === "production" ? SMSA_PRODUCTION_URL : SMSA_SANDBOX_URL;
316
+ }
317
+ async executeCreateShipment(input) {
318
+ const isC2B = C2B_SERVICE_CODES.has(input.serviceType);
319
+ if (isC2B) {
320
+ return this.createC2BShipment(input);
321
+ }
322
+ const request = mapCreateB2CRequest(input);
323
+ const response = await this.http.post("/api/shipment/b2c/new", request);
324
+ return mapShipmentResponse(response, input);
325
+ }
326
+ async createC2BShipment(input) {
327
+ const request = mapCreateC2BRequest(input);
328
+ const response = await this.http.post("/api/c2b/new", request);
329
+ return mapShipmentResponse(response, input);
330
+ }
331
+ async cancelShipment(trackingNumber) {
332
+ const response = await this.http.post(`/api/c2b/cancel/${encodeURIComponent(trackingNumber)}`);
333
+ return typeof response === "string" ? response.toLowerCase().includes("cancelled") : true;
334
+ }
335
+ async track(trackingNumber) {
336
+ const response = await this.http.get(`/api/track/single/${encodeURIComponent(trackingNumber)}`);
337
+ return mapTrackingResult(response);
338
+ }
339
+ async trackMultiple(trackingNumbers) {
340
+ const response = await this.http.post("/api/track/bulk/", trackingNumbers, { retry: true });
341
+ return response.map(mapTrackingResult);
342
+ }
343
+ async trackByReference(reference) {
344
+ const response = await this.http.get(`/api/track/reference/${encodeURIComponent(reference)}`);
345
+ return mapTrackingResult(response);
346
+ }
347
+ async getLabel(trackingNumber, _format) {
348
+ const encoded = encodeURIComponent(trackingNumber);
349
+ for (const path of [
350
+ `/api/shipment/b2c/query/${encoded}`,
351
+ `/api/c2b/query/${encoded}`
352
+ ]) {
353
+ try {
354
+ const response = await this.http.get(path);
355
+ const waybill = response.waybills?.[0];
356
+ if (waybill?.awbFile) {
357
+ return `data:application/pdf;base64,${waybill.awbFile}`;
358
+ }
359
+ } catch (error) {
360
+ if (!(error instanceof APIError) || error.statusCode === 401 || error.statusCode !== undefined && error.statusCode >= 500) {
361
+ throw error;
362
+ }
363
+ }
364
+ }
365
+ throw new APIError("No label found for shipment", {
366
+ carrier: "smsaexpress",
367
+ raw: { trackingNumber }
368
+ });
369
+ }
370
+ async getCities(countryCode = "SA") {
371
+ const response = await this.http.get(`/api/lookup/cities/${encodeURIComponent(countryCode)}`);
372
+ return response.map(mapCity);
373
+ }
374
+ async getDropoffLocations() {
375
+ const response = await this.http.get("/api/lookup/smsaoffices");
376
+ return response.map(mapOffice);
377
+ }
378
+ async create2WayShipment(input) {
379
+ const request = mapCreate2WayRequest(input);
380
+ const response = await this.http.post("/api/TwoWayShipment/new", request);
381
+ return mapShipmentResponse(response, input);
382
+ }
383
+ async sendInvoice(request) {
384
+ return this.http.post("/api/invoice", request);
385
+ }
386
+ async validateShortAddress(shortCode) {
387
+ return this.http.get(`/api/Lookup/FullAddressByShortCode/${encodeURIComponent(shortCode)}`);
388
+ }
389
+ async pushIdDetails(request) {
390
+ return this.http.post("/api/shipment/identity-details", request);
391
+ }
392
+ parseWebhook(payload, options) {
393
+ return parseSMSAWebhook(payload, options);
394
+ }
395
+ parseWebhookBatch(payload, options) {
396
+ return parseSMSAWebhookBatch(payload, options);
397
+ }
398
+ }
399
+ export {
400
+ parseSMSAWebhookBatch,
401
+ parseSMSAWebhook,
402
+ mapSMSAStatus,
403
+ SMSAStatusCodes,
404
+ SMSAService,
405
+ SMSAExpressAdapter
406
+ };
407
+
408
+ //# debugId=C9233984938A6D4764756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/carriers/smsaexpress/services.ts", "../src/carriers/smsaexpress/mappers.ts", "../src/carriers/smsaexpress/adapter.ts"],
4
+ "sourcesContent": [
5
+ "// file: src/carriers/smsaexpress/services.ts\n/**\n * SMSA Express Service Type Codes\n * Use these constants for type-safe service selection.\n */\n\nexport const SMSAService = {\n /** E-commerce delivery */\n ECOMMERCE_DELIVERY: \"EDDL\",\n /** Express delivery */\n EXPRESS_DELIVERY: \"EDEL\",\n /** C2B / Reverse pickup */\n C2B_REVERSE: \"EDCR\",\n} as const;\n\nexport type SMSAServiceType = (typeof SMSAService)[keyof typeof SMSAService];\n\n/**\n * SMSA Express Scan Type Codes → Unified ShipmentStatus mapping.\n *\n * Based on /api/track/statuslookup response.\n * Scan types not explicitly listed default to \"in_transit\".\n */\nexport const SMSAStatusCodes: Record<string, string> = {\n // Delivery\n DL: \"delivered\",\n\n // Out for delivery\n OD: \"out_for_delivery\",\n\n // Arrived at facility\n AF: \"at_warehouse\",\n\n // Hub / sorting\n HOP: \"in_transit\",\n HOR: \"in_transit\",\n HOT: \"in_transit\",\n\n // Picked up / collected\n PU: \"picked_up\",\n PKD: \"picked_up\",\n\n // Customs\n CR: \"in_transit\",\n CH: \"in_transit\",\n\n // Exceptions\n DE: \"exception\",\n DMG: \"exception\",\n MISS: \"exception\",\n\n // Cancelled / returned\n CAN: \"cancelled\",\n RTO: \"returned\",\n RTN: \"returned\",\n\n // Processing\n CC: \"at_warehouse\",\n PP: \"pending\",\n\n // Created / booked\n BK: \"created\",\n NEW: \"created\",\n};\n",
6
+ "// file: src/carriers/smsaexpress/mappers.ts\n/**\n * SMSA Express Data Mappers\n * Transform between unified ShipFlow types and SMSA Express API formats.\n */\n\nimport {\n APIError,\n ValidationError,\n WebhookVerificationError,\n} from \"../../core/errors\";\nimport type {\n City,\n CreateShipmentInput,\n Location,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { SMSAStatusCodes } from \"./services\";\nimport type {\n SMSACityLookupItem,\n SMSACreate2WayShipmentRequest,\n SMSACreateB2CShipmentRequest,\n SMSACreateC2BShipmentRequest,\n SMSAOfficeLookupItem,\n SMSAShipmentAddress,\n SMSAShipmentResponse,\n SMSATrackingResponse,\n SMSATrackingScan,\n SMSAWebhookShipment,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapSMSAStatus(scanType: string): ShipmentStatus {\n return (SMSAStatusCodes[scanType] as ShipmentStatus) ?? \"in_transit\";\n}\n\n/**\n * Derive the current shipment status from tracking scans.\n * Defensively sorts by ScanDateTime descending before taking the latest.\n */\nfunction deriveStatusFromScans(\n scans: SMSATrackingScan[],\n isDelivered?: boolean,\n): { status: ShipmentStatus; statusLabel: string } {\n if (isDelivered) {\n return { status: \"delivered\", statusLabel: \"Delivered\" };\n }\n if (scans.length === 0) {\n return { status: \"unknown\", statusLabel: \"Unknown\" };\n }\n const sorted = [...scans].sort(\n (a, b) =>\n new Date(b.ScanDateTime).getTime() -\n new Date(a.ScanDateTime).getTime(),\n );\n const latest = sorted[0]!;\n return {\n status: mapSMSAStatus(latest.ScanType),\n statusLabel: latest.ScanDescription,\n };\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\nfunction mapAddress(\n addr: CreateShipmentInput[\"shipper\"] | CreateShipmentInput[\"consignee\"],\n): SMSAShipmentAddress {\n const result: SMSAShipmentAddress = {\n ContactName: addr.name,\n ContactPhoneNumber: addr.phone,\n Country: addr.countryCode,\n City: addr.city,\n AddressLine1: addr.line1,\n AddressLine2: addr.line2,\n District: addr.neighbourhood ?? addr.state,\n PostalCode: addr.postalCode,\n };\n\n if (addr.coordinates) {\n result.Coordinates = `${addr.coordinates.latitude},${addr.coordinates.longitude}`;\n }\n\n if (addr.nationalAddress?.shortCode) {\n result.ShortCode = addr.nationalAddress.shortCode;\n }\n\n return result;\n}\n\nexport function mapCreateB2CRequest(\n input: CreateShipmentInput,\n): SMSACreateB2CShipmentRequest {\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\n const consigneeAddress = mapAddress(input.consignee);\n if (input.options?.metadata?.consigneeId) {\n consigneeAddress.ConsigneeID = input.options.metadata.consigneeId as string;\n }\n\n return {\n ConsigneeAddress: consigneeAddress,\n ShipperAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? `ORD-${Date.now()}`,\n CODAmount: input.cod?.enabled ? input.cod.amount : 0,\n DeclaredValue: input.declaredValue?.amount ?? 0,\n ContentDescription: input.parcels[0]?.description ?? \"Shipment contents\",\n Parcels: totalPieces,\n ShipDate: new Date().toISOString().slice(0, 19),\n ShipmentCurrency:\n input.declaredValue?.currency ?? input.cod?.currency ?? \"SAR\",\n Weight: totalWeight,\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n ServiceCode: input.serviceType,\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n VatPaid: (input.options?.metadata?.vatPaid as boolean) ?? true,\n DutyPaid: (input.options?.metadata?.dutyPaid as boolean) ?? false,\n };\n}\n\n/**\n * Map to C2B (reverse pickup) request.\n * In C2B flow, the consignee is the pickup point (customer returning),\n * and the shipper is the return-to address (merchant warehouse).\n */\nexport function mapCreateC2BRequest(\n input: CreateShipmentInput,\n): SMSACreateC2BShipmentRequest {\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\n return {\n PickupAddress: mapAddress(input.consignee),\n ReturnToAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? `ORD-${Date.now()}`,\n DeclaredValue: input.declaredValue?.amount ?? 0.1,\n ContentDescription:\n input.parcels[0]?.description ?? \"Return shipment contents\",\n Parcels: totalPieces,\n ShipDate: new Date().toISOString().slice(0, 19),\n ShipmentCurrency:\n input.declaredValue?.currency ?? input.cod?.currency ?? \"SAR\",\n Weight: totalWeight,\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n ServiceCode: input.serviceType ?? \"EDCR\",\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(\n data: SMSAShipmentResponse,\n input: CreateShipmentInput,\n): Shipment {\n const firstWaybill = data.waybills?.[0];\n const trackingNumber = firstWaybill?.awb ?? data.sawb;\n\n if (!trackingNumber) {\n throw new APIError(\"No tracking number in shipment response\", {\n carrier: \"smsaexpress\",\n raw: data,\n });\n }\n\n return {\n carrier: \"smsaexpress\",\n trackingNumber,\n reference: input.reference,\n status: \"created\",\n statusLabel: \"Created\",\n codAmount: input.cod?.enabled ? input.cod.amount : undefined,\n declaredValue: input.declaredValue?.amount,\n currency: input.declaredValue?.currency ?? input.cod?.currency ?? \"SAR\",\n returnLabel: firstWaybill?.returnBarcode\n ? `data:application/pdf;base64,${firstWaybill.returnBarcode}`\n : undefined,\n createdAt: new Date(\n /[Z]$|[+-]\\d{2}(:\\d{2})?$/.test(data.createDate)\n ? data.createDate\n : `${data.createDate}+03:00`,\n ),\n raw: data,\n };\n}\n\nexport function mapTrackingEvent(scan: SMSATrackingScan): TrackingEvent {\n return {\n timestamp: new Date(\n scan.ScanTimeZone\n ? `${scan.ScanDateTime}${scan.ScanTimeZone}`\n : scan.ScanDateTime,\n ),\n statusCode: scan.ScanType,\n status: mapSMSAStatus(scan.ScanType),\n description: scan.ScanDescription,\n location: scan.City,\n };\n}\n\nexport function mapTrackingResult(data: SMSATrackingResponse): TrackingResult {\n const { status, statusLabel } = deriveStatusFromScans(\n data.Scans,\n data.isDelivered,\n );\n\n const deliveredScan = data.Scans.find((s) => s.ScanType === \"DL\");\n const deliveredTimestamp = deliveredScan\n ? new Date(\n deliveredScan.ScanTimeZone\n ? `${deliveredScan.ScanDateTime}${deliveredScan.ScanTimeZone}`\n : deliveredScan.ScanDateTime,\n )\n : undefined;\n\n return {\n trackingNumber: data.AWB,\n carrier: \"smsaexpress\",\n reference: data.Reference || undefined,\n status,\n statusLabel,\n events: data.Scans.map(mapTrackingEvent),\n deliveryDate: deliveredTimestamp,\n codAmount: data.CODAmount > 0 ? data.CODAmount : undefined,\n pieces: data.Pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: SMSACityLookupItem): City {\n return {\n nameEn: city.cityName,\n code: city.cityCode,\n };\n}\n\nexport function mapOffice(office: SMSAOfficeLookupItem): Location {\n const [lat, lng] = (office.coordinates || \"\")\n .split(\",\")\n .map((s) => parseFloat(s.trim()));\n\n return {\n id: office.code,\n name: office.address,\n nameAr: office.addressAR,\n city: office.cityName,\n latitude: Number.isNaN(lat) ? undefined : lat,\n longitude: Number.isNaN(lng) ? undefined : lng,\n };\n}\n\nexport function mapCreate2WayRequest(\n input: CreateShipmentInput,\n): SMSACreate2WayShipmentRequest {\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\n const consigneeAddress = mapAddress(input.consignee);\n if (input.options?.metadata?.consigneeId) {\n consigneeAddress.ConsigneeID = input.options.metadata.consigneeId as string;\n }\n\n return {\n ConsigneeAddress: consigneeAddress,\n ShipperAddress: mapAddress(input.shipper),\n OrderNumber: input.reference ?? `ORD-${Date.now()}`,\n DeclaredValue: input.declaredValue?.amount ?? 0,\n ContentDescription: input.parcels[0]?.description ?? \"Shipment contents\",\n Parcels: totalPieces,\n ShipDate: new Date().toISOString().slice(0, 19),\n ShipmentCurrency: input.declaredValue?.currency ?? \"SAR\",\n Weight: totalWeight,\n WeightUnit: \"KG\",\n WaybillType: input.labelFormat === \"ZPL\" ? \"ZPL\" : \"PDF\",\n SMSARetailID: (input.options?.metadata?.smsaRetailId as string) ?? \"0\",\n VatPaid: (input.options?.metadata?.vatPaid as boolean) ?? true,\n DutyPaid: (input.options?.metadata?.dutyPaid as boolean) ?? false,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPERS\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 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\nfunction verifyWebhookAuth(options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n}): void {\n const { headers = {}, queryParams = {}, config } = options ?? {};\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: \"smsaexpress\",\n });\n }\n }\n\n // Verify auth via query param (SMSA uses API key as query param, timing-safe)\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: \"smsaexpress\",\n });\n }\n }\n}\n\nfunction mapWebhookShipmentToEvent(\n shipment: SMSAWebhookShipment,\n): WebhookEvent {\n const { status, statusLabel } = deriveStatusFromScans(\n shipment.Scans,\n shipment.isDelivered,\n );\n\n // Sort scans descending to get the latest one for timestamp/statusCode\n const sorted = [...shipment.Scans].sort(\n (a, b) =>\n new Date(b.ScanDateTime).getTime() -\n new Date(a.ScanDateTime).getTime(),\n );\n const latestScan = sorted[0];\n const timestamp = latestScan\n ? new Date(\n latestScan.ScanTimeZone\n ? `${latestScan.ScanDateTime}${latestScan.ScanTimeZone}`\n : latestScan.ScanDateTime,\n )\n : new Date();\n\n return {\n carrier: \"smsaexpress\",\n eventType: \"status_update\",\n trackingNumber: shipment.AWB,\n reference: shipment.Reference || undefined,\n status,\n statusCode: latestScan?.ScanType ?? \"unknown\",\n statusLabel,\n timestamp,\n raw: shipment,\n };\n}\n\nfunction validateWebhookPayload(payload: unknown): SMSAWebhookShipment[] {\n if (!Array.isArray(payload)) {\n throw new ValidationError(\n \"Invalid SMSA webhook payload: expected an array of shipments\",\n { raw: payload },\n );\n }\n\n if (payload.length === 0) {\n throw new ValidationError(\"Invalid SMSA webhook payload: empty array\", {\n raw: payload,\n });\n }\n\n for (const item of payload) {\n if (\n !item ||\n typeof item !== \"object\" ||\n !(\"AWB\" in item) ||\n !(\"Scans\" in item)\n ) {\n throw new ValidationError(\n \"Invalid SMSA webhook payload: shipment missing required fields (AWB, Scans)\",\n { raw: item },\n );\n }\n }\n\n return payload as SMSAWebhookShipment[];\n}\n\n/**\n * Parse an SMSA webhook payload and return a single WebhookEvent\n * for the first shipment in the batch.\n *\n * SMSA sends webhooks as an array of shipments. This function returns\n * only the first item — use `parseSMSAWebhookBatch()` for full batch parsing.\n */\nexport function parseSMSAWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n verifyWebhookAuth(options);\n const shipments = validateWebhookPayload(payload);\n return mapWebhookShipmentToEvent(shipments[0]!);\n}\n\n/**\n * Parse an SMSA webhook payload and return a WebhookEvent for every\n * shipment in the batch.\n *\n * SMSA sends webhook payloads as an array of shipments, each with\n * their own tracking scans.\n */\nexport function parseSMSAWebhookBatch(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent[] {\n verifyWebhookAuth(options);\n const shipments = validateWebhookPayload(payload);\n return shipments.map(mapWebhookShipmentToEvent);\n}\n",
7
+ "// file: src/carriers/smsaexpress/adapter.ts\n/**\n * SMSA Express Carrier Adapter\n * Full implementation of the CarrierAdapter interface for SMSA Express API.\n */\n\nimport { APIError } from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport type {\n CarrierConfig,\n City,\n CreateShipmentInput,\n Location,\n Shipment,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n mapCity,\n mapCreate2WayRequest,\n mapCreateB2CRequest,\n mapCreateC2BRequest,\n mapOffice,\n mapShipmentResponse,\n mapTrackingResult,\n parseSMSAWebhook,\n parseSMSAWebhookBatch,\n} from \"./mappers\";\nimport type {\n SMSACityLookupItem,\n SMSAOfficeLookupItem,\n SMSAPushIdDetailsRequest,\n SMSAPushIdDetailsResponse,\n SMSASendInvoiceRequest,\n SMSAShipmentResponse,\n SMSAShortAddressResponse,\n SMSATrackingResponse,\n} from \"./types\";\n\nconst SMSA_SANDBOX_URL = \"https://ecomapis-sandbox.azurewebsites.net\";\nconst SMSA_PRODUCTION_URL = \"https://ecomapis.smsaexpress.com\";\n\n/** Reverse-pickup / C2B service codes */\nconst C2B_SERVICE_CODES = new Set([\"EDCR\"]);\n\nexport interface SMSAExpressConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class SMSAExpressAdapter extends BaseCarrierAdapter {\n readonly name = \"smsaexpress\";\n readonly supportedCountries = [\n \"SA\",\n \"AE\",\n \"BH\",\n \"EG\",\n \"KW\",\n \"OM\",\n \"QA\",\n \"JO\",\n ];\n\n private http: HttpClient;\n\n constructor(config: SMSAExpressConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"smsaexpress\",\n headers: {\n apikey: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? SMSA_PRODUCTION_URL\n : SMSA_SANDBOX_URL;\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const isC2B = C2B_SERVICE_CODES.has(input.serviceType);\n\n if (isC2B) {\n return this.createC2BShipment(input);\n }\n\n const request = mapCreateB2CRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/shipment/b2c/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n private async createC2BShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n const request = mapCreateC2BRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/c2b/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n /**\n * Cancel a reverse-pickup (C2B) shipment.\n *\n * **Important:** SMSA only supports cancellation for C2B/reverse-pickup shipments.\n * Calling this on a B2C shipment will result in an API error or return `false`.\n * B2C shipments cannot be cancelled via the SMSA API.\n */\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n const response = await this.http.post<string>(\n `/api/c2b/cancel/${encodeURIComponent(trackingNumber)}`,\n );\n // API returns a string message on success\n return typeof response === \"string\"\n ? response.toLowerCase().includes(\"cancelled\")\n : true;\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const response = await this.http.get<SMSATrackingResponse>(\n `/api/track/single/${encodeURIComponent(trackingNumber)}`,\n );\n\n return mapTrackingResult(response);\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const response = await this.http.post<SMSATrackingResponse[]>(\n \"/api/track/bulk/\",\n trackingNumbers,\n { retry: true },\n );\n\n return response.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<SMSATrackingResponse>(\n `/api/track/reference/${encodeURIComponent(reference)}`,\n );\n\n return mapTrackingResult(response);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"PNG\",\n ): Promise<string> {\n // SMSA returns the waybill file (base64 PDF) as part of the shipment query.\n // Try B2C first (most common), then C2B as fallback.\n const encoded = encodeURIComponent(trackingNumber);\n\n for (const path of [\n `/api/shipment/b2c/query/${encoded}`,\n `/api/c2b/query/${encoded}`,\n ]) {\n try {\n const response = await this.http.get<SMSAShipmentResponse>(path);\n const waybill = response.waybills?.[0];\n if (waybill?.awbFile) {\n return `data:application/pdf;base64,${waybill.awbFile}`;\n }\n } catch (error) {\n // Only swallow 4xx API errors (shipment type mismatch / not found).\n // Re-throw auth, network, server, and unexpected errors immediately.\n if (\n !(error instanceof APIError) ||\n error.statusCode === 401 ||\n (error.statusCode !== undefined && error.statusCode >= 500)\n ) {\n throw error;\n }\n }\n }\n\n throw new APIError(\"No label found for shipment\", {\n carrier: \"smsaexpress\",\n raw: { trackingNumber },\n });\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(countryCode = \"SA\"): Promise<City[]> {\n const response = await this.http.get<SMSACityLookupItem[]>(\n `/api/lookup/cities/${encodeURIComponent(countryCode)}`,\n );\n\n return response.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<Location[]> {\n const response = await this.http.get<SMSAOfficeLookupItem[]>(\n \"/api/lookup/smsaoffices\",\n );\n\n return response.map(mapOffice);\n }\n\n // =========================================================================\n // 2-WAY SHIPMENT\n // =========================================================================\n\n async create2WayShipment(input: CreateShipmentInput): Promise<Shipment> {\n const request = mapCreate2WayRequest(input);\n const response = await this.http.post<SMSAShipmentResponse>(\n \"/api/TwoWayShipment/new\",\n request,\n );\n\n return mapShipmentResponse(response, input);\n }\n\n // =========================================================================\n // INVOICE & ID OPERATIONS (SMSA-specific)\n // =========================================================================\n\n async sendInvoice(request: SMSASendInvoiceRequest): Promise<string> {\n return this.http.post<string>(\"/api/invoice\", request);\n }\n\n async validateShortAddress(\n shortCode: string,\n ): Promise<SMSAShortAddressResponse> {\n return this.http.get<SMSAShortAddressResponse>(\n `/api/Lookup/FullAddressByShortCode/${encodeURIComponent(shortCode)}`,\n );\n }\n\n async pushIdDetails(\n request: SMSAPushIdDetailsRequest,\n ): Promise<SMSAPushIdDetailsResponse> {\n return this.http.post<SMSAPushIdDetailsResponse>(\n \"/api/shipment/identity-details\",\n request,\n );\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 parseSMSAWebhook(payload, options);\n }\n\n /**\n * Parse a batch SMSA webhook payload into multiple events.\n *\n * SMSA sends webhook payloads as an array of shipments, each with their own\n * tracking scans. This method returns a WebhookEvent for every shipment\n * in the payload.\n */\n parseWebhookBatch(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent[] {\n return parseSMSAWebhookBatch(payload, options);\n }\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "C9233984938A6D4764756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * SMSA Express Data Mappers
3
+ * Transform between unified ShipFlow types and SMSA Express API formats.
4
+ */
5
+ import type { City, CreateShipmentInput, Location, Shipment, ShipmentStatus, TrackingEvent, TrackingResult, WebhookConfig, WebhookEvent } from "../../core/types.js";
6
+ import type { SMSACityLookupItem, SMSACreate2WayShipmentRequest, SMSACreateB2CShipmentRequest, SMSACreateC2BShipmentRequest, SMSAOfficeLookupItem, SMSAShipmentResponse, SMSATrackingResponse, SMSATrackingScan } from "./types.js";
7
+ export declare function mapSMSAStatus(scanType: string): ShipmentStatus;
8
+ export declare function mapCreateB2CRequest(input: CreateShipmentInput): SMSACreateB2CShipmentRequest;
9
+ /**
10
+ * Map to C2B (reverse pickup) request.
11
+ * In C2B flow, the consignee is the pickup point (customer returning),
12
+ * and the shipper is the return-to address (merchant warehouse).
13
+ */
14
+ export declare function mapCreateC2BRequest(input: CreateShipmentInput): SMSACreateC2BShipmentRequest;
15
+ export declare function mapShipmentResponse(data: SMSAShipmentResponse, input: CreateShipmentInput): Shipment;
16
+ export declare function mapTrackingEvent(scan: SMSATrackingScan): TrackingEvent;
17
+ export declare function mapTrackingResult(data: SMSATrackingResponse): TrackingResult;
18
+ export declare function mapCity(city: SMSACityLookupItem): City;
19
+ export declare function mapOffice(office: SMSAOfficeLookupItem): Location;
20
+ export declare function mapCreate2WayRequest(input: CreateShipmentInput): SMSACreate2WayShipmentRequest;
21
+ /**
22
+ * Parse an SMSA webhook payload and return a single WebhookEvent
23
+ * for the first shipment in the batch.
24
+ *
25
+ * SMSA sends webhooks as an array of shipments. This function returns
26
+ * only the first item — use `parseSMSAWebhookBatch()` for full batch parsing.
27
+ */
28
+ export declare function parseSMSAWebhook(payload: unknown, options?: {
29
+ headers?: Record<string, string>;
30
+ queryParams?: Record<string, string>;
31
+ config?: WebhookConfig;
32
+ }): WebhookEvent;
33
+ /**
34
+ * Parse an SMSA webhook payload and return a WebhookEvent for every
35
+ * shipment in the batch.
36
+ *
37
+ * SMSA sends webhook payloads as an array of shipments, each with
38
+ * their own tracking scans.
39
+ */
40
+ export declare function parseSMSAWebhookBatch(payload: unknown, options?: {
41
+ headers?: Record<string, string>;
42
+ queryParams?: Record<string, string>;
43
+ config?: WebhookConfig;
44
+ }): WebhookEvent[];
45
+ //# sourceMappingURL=mappers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAOH,OAAO,KAAK,EACV,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,kBAAkB,EAClB,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,oBAAoB,EAEpB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAE9D;AAyDD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CAiC9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CA0B9B;AAMD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,mBAAmB,GACzB,QAAQ,CA8BV;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAYtE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CA2B5E;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAKtD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,oBAAoB,GAAG,QAAQ,CAahE;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,mBAAmB,GACzB,6BAA6B,CA8B/B;AAuHD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,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,CAId;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,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,EAAE,CAIhB"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SMSA Express Service Type Codes
3
+ * Use these constants for type-safe service selection.
4
+ */
5
+ export declare const SMSAService: {
6
+ /** E-commerce delivery */
7
+ readonly ECOMMERCE_DELIVERY: "EDDL";
8
+ /** Express delivery */
9
+ readonly EXPRESS_DELIVERY: "EDEL";
10
+ /** C2B / Reverse pickup */
11
+ readonly C2B_REVERSE: "EDCR";
12
+ };
13
+ export type SMSAServiceType = (typeof SMSAService)[keyof typeof SMSAService];
14
+ /**
15
+ * SMSA Express Scan Type Codes → Unified ShipmentStatus mapping.
16
+ *
17
+ * Based on /api/track/statuslookup response.
18
+ * Scan types not explicitly listed default to "in_transit".
19
+ */
20
+ export declare const SMSAStatusCodes: Record<string, string>;
21
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../src/carriers/smsaexpress/services.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,eAAO,MAAM,WAAW;IACtB,0BAA0B;;IAE1B,uBAAuB;;IAEvB,2BAA2B;;CAEnB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwClD,CAAC"}