pathao-merchant-sdk 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,7 @@ An **unofficial** TypeScript SDK for integrating with the Pathao Merchant API. T
21
21
  - [API Reference](#api-reference)
22
22
  - [Examples](#examples)
23
23
  - [Error Handling](#error-handling)
24
+ - [Webhooks](#webhooks)
24
25
  - [Authentication](#authentication)
25
26
  - [Official Documentation](#official-documentation)
26
27
  - [Contributing](#contributing)
@@ -37,6 +38,8 @@ An **unofficial** TypeScript SDK for integrating with the Pathao Merchant API. T
37
38
  - 🏪 **Store Management** - Create and manage pickup/service points
38
39
  - 💰 **Price Calculation** - Get accurate delivery charges before creating orders
39
40
  - 🌍 **Location Services** - Access cities, zones, and areas data
41
+ - 🔔 **Webhook Support** - Verify and handle inbound Pathao webhook events
42
+ - ♻️ **Retry & Circuit Breaker** - Automatic retry with backoff, circuit breaker for resilience
40
43
  - ⚡ **Built with Axios** - Reliable HTTP client with request/response interceptors
41
44
  - 🛡️ **Error Handling** - Comprehensive error handling with detailed error messages
42
45
  - 📚 **Well Documented** - Extensive documentation and examples
@@ -406,6 +409,166 @@ try {
406
409
  }
407
410
  ```
408
411
 
412
+ ## Webhooks
413
+
414
+ The `pathao-merchant-sdk/webhooks` sub-module handles inbound Pathao webhook events. It has **zero extra runtime dependencies** — only Node.js built-ins (`crypto`, `events`).
415
+
416
+ > **Note:** Pathao has no official webhook documentation. This implementation is based on reverse-engineering the [`pathao-courier`](https://www.npmjs.com/package/pathao-courier) package. The signature scheme is plain shared-secret equality (no HMAC) via constant-time comparison.
417
+
418
+ ### Installation
419
+
420
+ The webhooks module is a separate entry point. Import it explicitly:
421
+
422
+ ```typescript
423
+ import {
424
+ PathaoWebhookHandler,
425
+ constructEvent,
426
+ verifySignature,
427
+ PathaoWebhookEvent,
428
+ } from 'pathao-merchant-sdk/webhooks';
429
+ ```
430
+
431
+ ```javascript
432
+ // CommonJS
433
+ const { PathaoWebhookHandler } = require('pathao-merchant-sdk/webhooks');
434
+ ```
435
+
436
+ ### Quick verification
437
+
438
+ ```typescript
439
+ import { verifySignature } from 'pathao-merchant-sdk/webhooks';
440
+
441
+ const isValid = verifySignature(
442
+ req.headers['x-pathao-signature'],
443
+ process.env.PATHAO_WEBHOOK_SECRET!,
444
+ );
445
+ ```
446
+
447
+ ### Verify + parse in one call
448
+
449
+ ```typescript
450
+ import { constructEvent, PathaoWebhookError } from 'pathao-merchant-sdk/webhooks';
451
+
452
+ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
453
+ try {
454
+ const payload = constructEvent(
455
+ req.body, // raw Buffer — do NOT pre-parse
456
+ req.headers,
457
+ process.env.PATHAO_WEBHOOK_SECRET!,
458
+ );
459
+ console.log('Event:', payload.event, payload.data);
460
+ res.sendStatus(200);
461
+ } catch (err) {
462
+ if (err instanceof PathaoWebhookError) {
463
+ res.status(400).send(err.message);
464
+ } else {
465
+ res.sendStatus(500);
466
+ }
467
+ }
468
+ });
469
+ ```
470
+
471
+ ### Express middleware
472
+
473
+ ```typescript
474
+ import { PathaoWebhookHandler, PathaoWebhookEvent } from 'pathao-merchant-sdk/webhooks';
475
+
476
+ const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
477
+
478
+ // Listen for specific events (fully typed payload)
479
+ handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
480
+ console.log('Delivered:', payload.consignment_id);
481
+ });
482
+
483
+ handler.on(PathaoWebhookEvent.ORDER_CANCELLED, (payload) => {
484
+ console.log('Cancelled:', payload.consignment_id, payload.reason);
485
+ });
486
+
487
+ // Catch all events
488
+ handler.on('webhook', (payload) => {
489
+ console.log('Any event:', payload.event, payload.data);
490
+ });
491
+
492
+ // Handle errors (invalid signature, bad JSON, etc.)
493
+ handler.on('error', (err) => {
494
+ console.error('Webhook error:', err.message);
495
+ });
496
+
497
+ // Mount — must use express.raw() BEFORE this middleware
498
+ app.post(
499
+ '/webhook/pathao',
500
+ express.raw({ type: 'application/json' }),
501
+ handler.expressMiddleware(),
502
+ );
503
+
504
+ // Access the parsed payload downstream
505
+ app.post('/webhook/pathao', express.raw({ type: 'application/json' }), handler.expressMiddleware(), (req, res) => {
506
+ console.log(req.pathaoWebhook); // typed PathaoWebhookPayload
507
+ res.sendStatus(200);
508
+ });
509
+ ```
510
+
511
+ ### Generic (non-Express) middleware
512
+
513
+ ```typescript
514
+ const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
515
+ const middleware = handler.middleware();
516
+
517
+ // Returns { payload, error } — never rejects
518
+ const { payload, error } = await middleware(rawBody, headers);
519
+ if (error) {
520
+ console.error('Bad webhook:', error.message);
521
+ } else {
522
+ console.log('Event:', payload!.event);
523
+ }
524
+ ```
525
+
526
+ ### Supported event types
527
+
528
+ | Event constant | Event string |
529
+ |---|---|
530
+ | `ORDER_CREATED` | `order.created` |
531
+ | `ORDER_ACCEPTED` | `order.accepted` |
532
+ | `ORDER_PICKUP_REQUESTED` | `order.pickup_requested` |
533
+ | `ORDER_PICKED` | `order.picked` |
534
+ | `ORDER_IN_TRANSIT` | `order.in_transit` |
535
+ | `ORDER_DELIVERED` | `order.delivered` |
536
+ | `ORDER_CANCELLED` | `order.cancelled` |
537
+ | `ORDER_HOLD` | `order.hold` |
538
+ | `ORDER_RETURN_REQUESTED` | `order.return_requested` |
539
+ | `ORDER_RETURN_PICKUP_REQUESTED` | `order.return_pickup_requested` |
540
+ | `ORDER_RETURN_PICKED` | `order.return_picked` |
541
+ | `ORDER_RETURN_IN_TRANSIT` | `order.return_in_transit` |
542
+ | `ORDER_PARTIAL_DELIVERED` | `order.partial_delivered` |
543
+ | `ORDER_DELIVERY_FAILED` | `order.delivery_failed` |
544
+ | `ORDER_ON_HOLD` | `order.on_hold` |
545
+ | `ORDER_PAID` | `order.paid` |
546
+ | `ORDER_PAID_RETURN` | `order.paid_return` |
547
+ | `ORDER_RETURNED` | `order.returned` |
548
+ | `ORDER_EXCHANGED` | `order.exchanged` |
549
+ | `STORE_CREATED` | `store.created` |
550
+ | `STORE_UPDATED` | `store.updated` |
551
+
552
+ ### TypeScript types
553
+
554
+ All event payloads are fully typed. Access them via `WebhookEventPayloadMap`:
555
+
556
+ ```typescript
557
+ import type {
558
+ WebhookEventPayloadMap,
559
+ PathaoWebhookEvent,
560
+ OrderDeliveredPayload,
561
+ } from 'pathao-merchant-sdk/webhooks';
562
+
563
+ // Explicit payload type
564
+ const handler = (payload: OrderDeliveredPayload) => {
565
+ console.log(payload.consignment_id, payload.amount_to_collect);
566
+ };
567
+
568
+ // Via mapped type
569
+ type DeliveredPayload = WebhookEventPayloadMap[PathaoWebhookEvent.ORDER_DELIVERED];
570
+ ```
571
+
409
572
  ## Authentication
410
573
 
411
574
  The SDK automatically handles OAuth2 authentication and token refresh. You only need to provide your credentials once during initialization:
@@ -511,6 +674,28 @@ This is an **unofficial** SDK and is not affiliated with or endorsed by Pathao.
511
674
 
512
675
  ## Changelog
513
676
 
677
+ ### 2.1.0
678
+ - Added webhook support via `pathao-merchant-sdk/webhooks` sub-path
679
+ - `PathaoWebhookHandler` — EventEmitter with typed `on()` overloads for all 21 event types
680
+ - `constructEvent()` and `verifySignature()` standalone helpers
681
+ - Express middleware and generic async middleware
682
+ - Constant-time signature comparison via `crypto.timingSafeEqual`
683
+ - Fixed case-insensitive header lookup for `X-PATHAO-Signature`
684
+
685
+ ### 2.0.x
686
+ - Added retry logic: 429 reads `Retry-After`, 5xx exponential backoff (max 2 retries)
687
+ - Added circuit breaker: throws `PathaoApiError` (code 503) when open
688
+ - Added `sandbox()` and `production()` named constructors
689
+ - Deferred config validation to first API call
690
+ - HTTPS-only enforcement in `validateConfiguration()`
691
+ - Auth token redacted as `Bearer [REDACTED]` in debug logs
692
+ - Added `User-Agent: pathao-merchant-sdk node/<version>` header
693
+ - `getStores()` accepts optional `page` parameter
694
+ - `validateContactNumber` requires `startsWith('01')`
695
+ - `is_active` and `cod_enabled` typed as `0 | 1`
696
+ - Fixed shell injection in `scripts/release.js` (replaced `execSync` with `spawnSync`)
697
+ - CI/CD: replaced deprecated actions, added version-existence check before publish
698
+
514
699
  ### 1.0.0
515
700
  - Initial release
516
701
  - Complete Pathao API integration
package/dist/index.d.mts CHANGED
@@ -65,7 +65,7 @@ interface PathaoStore {
65
65
  store_id: number;
66
66
  store_name: string;
67
67
  store_address: string;
68
- is_active: number;
68
+ is_active: 0 | 1;
69
69
  city_id: number;
70
70
  zone_id: number;
71
71
  hub_id: number;
@@ -107,7 +107,7 @@ interface PathaoPriceResponse {
107
107
  discount: number;
108
108
  promo_discount: number;
109
109
  plan_id: number;
110
- cod_enabled: number;
110
+ cod_enabled: 0 | 1;
111
111
  cod_percentage: number;
112
112
  additional_charge: number;
113
113
  final_price: number;
@@ -176,14 +176,27 @@ interface PathaoError {
176
176
  errors?: Record<string, string[]>;
177
177
  validation?: Record<string, string[]>;
178
178
  }
179
+ /**
180
+ * Delivery type codes defined by the Pathao API.
181
+ * Values correspond to Pathao's internal delivery_type IDs.
182
+ * @see https://developers.pathao.com - Order Creation endpoint
183
+ */
179
184
  declare enum DeliveryType {
185
+ /** Standard delivery (API value: 48) */
180
186
  NORMAL = 48,
187
+ /** Same-day on-demand delivery (API value: 12) */
181
188
  ON_DEMAND = 12
182
189
  }
183
190
  declare enum ItemType {
184
191
  DOCUMENT = 1,
185
192
  PARCEL = 2
186
193
  }
194
+ interface PathaoBulkOrderResponse {
195
+ message: string;
196
+ type: string;
197
+ code: number;
198
+ data: boolean;
199
+ }
187
200
 
188
201
  /**
189
202
  * Pathao Merchant API Service (Unofficial SDK)
@@ -233,7 +246,6 @@ declare class PathaoApiService {
233
246
  private config;
234
247
  private isAuthenticating;
235
248
  private authPromise;
236
- private requestQueue;
237
249
  private hasValidated;
238
250
  private debug;
239
251
  private circuitBreaker;
@@ -244,26 +256,21 @@ declare class PathaoApiService {
244
256
  private validateConfiguration;
245
257
  private ensureAuthenticated;
246
258
  private performAuthentication;
247
- private processRequestQueue;
248
259
  private authenticate;
249
260
  private refreshAccessToken;
250
261
  private getErrorMessage;
251
262
  private toPathaoApiError;
252
263
  private handleCircuitBreaker;
264
+ private delay;
253
265
  createOrder(orderData: PathaoOrderRequest): Promise<PathaoOrderResponse>;
254
266
  createStore(storeData: PathaoStoreRequest): Promise<PathaoStoreCreateResponse>;
255
- getStores(): Promise<PathaoStoreListResponse>;
267
+ getStores(page?: number): Promise<PathaoStoreListResponse>;
256
268
  calculatePrice(priceData: PathaoPriceRequest): Promise<PathaoPriceResponse>;
257
269
  getCities(): Promise<PathaoCityResponse>;
258
270
  getZones(cityId: number): Promise<PathaoZoneResponse>;
259
271
  getAreas(zoneId: number): Promise<PathaoAreaResponse>;
260
272
  getOrderStatus(consignmentId: string): Promise<PathaoOrderStatusResponse>;
261
- createBulkOrder(orders: PathaoOrderRequest[]): Promise<{
262
- message: string;
263
- type: string;
264
- code: number;
265
- data: boolean;
266
- }>;
273
+ createBulkOrder(orders: PathaoOrderRequest[]): Promise<PathaoBulkOrderResponse>;
267
274
  static validatePhoneNumber(phone: string): boolean;
268
275
  static formatPhoneNumber(phone: string): string;
269
276
  static validateAddress(address: string): boolean;
@@ -282,6 +289,14 @@ declare class PathaoApiService {
282
289
  debug?: boolean;
283
290
  circuitBreaker?: CircuitBreakerConfig;
284
291
  }): PathaoApiService;
292
+ static sandbox(credentials: Omit<PathaoConfig, 'baseURL'>, options?: {
293
+ debug?: boolean;
294
+ circuitBreaker?: CircuitBreakerConfig;
295
+ }): PathaoApiService;
296
+ static production(credentials: Omit<PathaoConfig, 'baseURL'>, options?: {
297
+ debug?: boolean;
298
+ circuitBreaker?: CircuitBreakerConfig;
299
+ }): PathaoApiService;
285
300
  }
286
301
 
287
- export { DeliveryType, ItemType, PathaoApiError, PathaoApiService, type PathaoAreaResponse, type PathaoAuthResponse, type PathaoCityResponse, type PathaoConfig, type PathaoError, type PathaoOrderRequest, type PathaoOrderResponse, type PathaoOrderStatusResponse, type PathaoPriceRequest, type PathaoPriceResponse, type PathaoStore, type PathaoStoreCreateResponse, type PathaoStoreListResponse, type PathaoStoreRequest, type PathaoZoneResponse };
302
+ export { type CircuitBreakerConfig, DeliveryType, ItemType, PathaoApiError, PathaoApiService, type PathaoAreaResponse, type PathaoAuthResponse, type PathaoBulkOrderResponse, type PathaoCityResponse, type PathaoConfig, type PathaoError, type PathaoOrderRequest, type PathaoOrderResponse, type PathaoOrderStatusResponse, type PathaoPriceRequest, type PathaoPriceResponse, type PathaoStore, type PathaoStoreCreateResponse, type PathaoStoreListResponse, type PathaoStoreRequest, type PathaoZoneResponse };
package/dist/index.d.ts CHANGED
@@ -65,7 +65,7 @@ interface PathaoStore {
65
65
  store_id: number;
66
66
  store_name: string;
67
67
  store_address: string;
68
- is_active: number;
68
+ is_active: 0 | 1;
69
69
  city_id: number;
70
70
  zone_id: number;
71
71
  hub_id: number;
@@ -107,7 +107,7 @@ interface PathaoPriceResponse {
107
107
  discount: number;
108
108
  promo_discount: number;
109
109
  plan_id: number;
110
- cod_enabled: number;
110
+ cod_enabled: 0 | 1;
111
111
  cod_percentage: number;
112
112
  additional_charge: number;
113
113
  final_price: number;
@@ -176,14 +176,27 @@ interface PathaoError {
176
176
  errors?: Record<string, string[]>;
177
177
  validation?: Record<string, string[]>;
178
178
  }
179
+ /**
180
+ * Delivery type codes defined by the Pathao API.
181
+ * Values correspond to Pathao's internal delivery_type IDs.
182
+ * @see https://developers.pathao.com - Order Creation endpoint
183
+ */
179
184
  declare enum DeliveryType {
185
+ /** Standard delivery (API value: 48) */
180
186
  NORMAL = 48,
187
+ /** Same-day on-demand delivery (API value: 12) */
181
188
  ON_DEMAND = 12
182
189
  }
183
190
  declare enum ItemType {
184
191
  DOCUMENT = 1,
185
192
  PARCEL = 2
186
193
  }
194
+ interface PathaoBulkOrderResponse {
195
+ message: string;
196
+ type: string;
197
+ code: number;
198
+ data: boolean;
199
+ }
187
200
 
188
201
  /**
189
202
  * Pathao Merchant API Service (Unofficial SDK)
@@ -233,7 +246,6 @@ declare class PathaoApiService {
233
246
  private config;
234
247
  private isAuthenticating;
235
248
  private authPromise;
236
- private requestQueue;
237
249
  private hasValidated;
238
250
  private debug;
239
251
  private circuitBreaker;
@@ -244,26 +256,21 @@ declare class PathaoApiService {
244
256
  private validateConfiguration;
245
257
  private ensureAuthenticated;
246
258
  private performAuthentication;
247
- private processRequestQueue;
248
259
  private authenticate;
249
260
  private refreshAccessToken;
250
261
  private getErrorMessage;
251
262
  private toPathaoApiError;
252
263
  private handleCircuitBreaker;
264
+ private delay;
253
265
  createOrder(orderData: PathaoOrderRequest): Promise<PathaoOrderResponse>;
254
266
  createStore(storeData: PathaoStoreRequest): Promise<PathaoStoreCreateResponse>;
255
- getStores(): Promise<PathaoStoreListResponse>;
267
+ getStores(page?: number): Promise<PathaoStoreListResponse>;
256
268
  calculatePrice(priceData: PathaoPriceRequest): Promise<PathaoPriceResponse>;
257
269
  getCities(): Promise<PathaoCityResponse>;
258
270
  getZones(cityId: number): Promise<PathaoZoneResponse>;
259
271
  getAreas(zoneId: number): Promise<PathaoAreaResponse>;
260
272
  getOrderStatus(consignmentId: string): Promise<PathaoOrderStatusResponse>;
261
- createBulkOrder(orders: PathaoOrderRequest[]): Promise<{
262
- message: string;
263
- type: string;
264
- code: number;
265
- data: boolean;
266
- }>;
273
+ createBulkOrder(orders: PathaoOrderRequest[]): Promise<PathaoBulkOrderResponse>;
267
274
  static validatePhoneNumber(phone: string): boolean;
268
275
  static formatPhoneNumber(phone: string): string;
269
276
  static validateAddress(address: string): boolean;
@@ -282,6 +289,14 @@ declare class PathaoApiService {
282
289
  debug?: boolean;
283
290
  circuitBreaker?: CircuitBreakerConfig;
284
291
  }): PathaoApiService;
292
+ static sandbox(credentials: Omit<PathaoConfig, 'baseURL'>, options?: {
293
+ debug?: boolean;
294
+ circuitBreaker?: CircuitBreakerConfig;
295
+ }): PathaoApiService;
296
+ static production(credentials: Omit<PathaoConfig, 'baseURL'>, options?: {
297
+ debug?: boolean;
298
+ circuitBreaker?: CircuitBreakerConfig;
299
+ }): PathaoApiService;
285
300
  }
286
301
 
287
- export { DeliveryType, ItemType, PathaoApiError, PathaoApiService, type PathaoAreaResponse, type PathaoAuthResponse, type PathaoCityResponse, type PathaoConfig, type PathaoError, type PathaoOrderRequest, type PathaoOrderResponse, type PathaoOrderStatusResponse, type PathaoPriceRequest, type PathaoPriceResponse, type PathaoStore, type PathaoStoreCreateResponse, type PathaoStoreListResponse, type PathaoStoreRequest, type PathaoZoneResponse };
302
+ export { type CircuitBreakerConfig, DeliveryType, ItemType, PathaoApiError, PathaoApiService, type PathaoAreaResponse, type PathaoAuthResponse, type PathaoBulkOrderResponse, type PathaoCityResponse, type PathaoConfig, type PathaoError, type PathaoOrderRequest, type PathaoOrderResponse, type PathaoOrderStatusResponse, type PathaoPriceRequest, type PathaoPriceResponse, type PathaoStore, type PathaoStoreCreateResponse, type PathaoStoreListResponse, type PathaoStoreRequest, type PathaoZoneResponse };
package/dist/index.js CHANGED
@@ -26,10 +26,11 @@ var PathaoApiService = class _PathaoApiService {
26
26
  this.tokenExpiry = null;
27
27
  this.isAuthenticating = false;
28
28
  this.authPromise = null;
29
- this.requestQueue = [];
30
29
  this.hasValidated = false;
31
30
  this.debug = false;
32
- const timeout = config.timeout || parseInt(process.env.PATHAO_TIMEOUT || "30000", 10);
31
+ const parsedTimeout = parseInt(process.env.PATHAO_TIMEOUT || "", 10);
32
+ const envTimeout = Number.isNaN(parsedTimeout) || parsedTimeout <= 0 ? 3e4 : parsedTimeout;
33
+ const timeout = config.timeout ?? envTimeout;
33
34
  this.config = {
34
35
  baseURL: config.baseURL || process.env.PATHAO_BASE_URL || "",
35
36
  timeout,
@@ -51,7 +52,8 @@ var PathaoApiService = class _PathaoApiService {
51
52
  timeout: this.config.timeout,
52
53
  headers: {
53
54
  "Content-Type": "application/json",
54
- Accept: "application/json"
55
+ Accept: "application/json",
56
+ "User-Agent": `pathao-merchant-sdk node/${process.version}`
55
57
  }
56
58
  });
57
59
  this.pathaoClient.interceptors.request.use(async (config2) => {
@@ -64,8 +66,10 @@ var PathaoApiService = class _PathaoApiService {
64
66
  config2.headers.Authorization = `Bearer ${this.accessToken}`;
65
67
  }
66
68
  if (this.debug) {
69
+ const safeHeaders = { ...config2.headers };
70
+ if (safeHeaders["Authorization"]) safeHeaders["Authorization"] = "Bearer [REDACTED]";
67
71
  console.log(`[Pathao SDK] ${config2.method?.toUpperCase()} ${config2.url}`, {
68
- headers: config2.headers,
72
+ headers: safeHeaders,
69
73
  data: config2.data
70
74
  });
71
75
  }
@@ -97,6 +101,22 @@ var PathaoApiService = class _PathaoApiService {
97
101
  return this.pathaoClient.request(error.config);
98
102
  }
99
103
  }
104
+ if (error.response?.status === 429 && error.config && !error.config._rateLimitRetry) {
105
+ error.config._rateLimitRetry = true;
106
+ const retryAfterHeader = error.response.headers["retry-after"];
107
+ const delayMs = retryAfterHeader ? parseInt(retryAfterHeader, 10) * 1e3 : 1e3;
108
+ await this.delay(delayMs);
109
+ return this.pathaoClient.request(error.config);
110
+ }
111
+ const status = error.response?.status;
112
+ if (status !== void 0 && status >= 500 && error.config) {
113
+ const retryCount = error.config._retryCount ?? 0;
114
+ if (retryCount < 2) {
115
+ error.config._retryCount = retryCount + 1;
116
+ await this.delay(Math.min(500 * 2 ** retryCount, 4e3));
117
+ return this.pathaoClient.request(error.config);
118
+ }
119
+ }
100
120
  this.handleCircuitBreaker();
101
121
  return Promise.reject(error);
102
122
  }
@@ -114,6 +134,12 @@ var PathaoApiService = class _PathaoApiService {
114
134
  "Configuration validation failed"
115
135
  );
116
136
  }
137
+ if (!this.config.baseURL.startsWith("https://")) {
138
+ throw this.toPathaoApiError(
139
+ new Error("Pathao API baseURL must use HTTPS (https://)"),
140
+ "Configuration validation failed"
141
+ );
142
+ }
117
143
  if (!this.config.clientId || !this.config.clientSecret || !this.config.username || !this.config.password) {
118
144
  throw this.toPathaoApiError(
119
145
  new Error(
@@ -130,8 +156,9 @@ var PathaoApiService = class _PathaoApiService {
130
156
  this.circuitBreaker.isOpen = false;
131
157
  this.circuitBreaker.failures = 0;
132
158
  } else {
133
- throw new Error(
134
- "Circuit breaker is open. Too many authentication failures."
159
+ throw new PathaoApiError(
160
+ "Circuit breaker is open. Too many authentication failures. Try again later.",
161
+ { code: 503 }
135
162
  );
136
163
  }
137
164
  }
@@ -148,7 +175,6 @@ var PathaoApiService = class _PathaoApiService {
148
175
  } finally {
149
176
  this.isAuthenticating = false;
150
177
  this.authPromise = null;
151
- this.processRequestQueue();
152
178
  }
153
179
  }
154
180
  async performAuthentication() {
@@ -162,16 +188,6 @@ var PathaoApiService = class _PathaoApiService {
162
188
  }
163
189
  await this.authenticate();
164
190
  }
165
- processRequestQueue() {
166
- const queue = [...this.requestQueue];
167
- this.requestQueue = [];
168
- queue.forEach(({ resolve }) => {
169
- try {
170
- resolve();
171
- } catch (error) {
172
- }
173
- });
174
- }
175
191
  async authenticate() {
176
192
  try {
177
193
  const response = await this.pathaoClient.post(
@@ -195,9 +211,6 @@ var PathaoApiService = class _PathaoApiService {
195
211
  }
196
212
  }
197
213
  async refreshAccessToken() {
198
- if (!this.refreshToken) {
199
- throw new Error("No refresh token available");
200
- }
201
214
  try {
202
215
  const response = await this.pathaoClient.post(
203
216
  "/aladdin/api/v1/issue-token",
@@ -226,14 +239,16 @@ var PathaoApiService = class _PathaoApiService {
226
239
  }
227
240
  return error.message;
228
241
  }
229
- return error.message || "Unknown error";
242
+ if (error instanceof Error) return error.message;
243
+ return "Unknown error";
230
244
  }
231
245
  toPathaoApiError(error, context) {
232
246
  if (error instanceof PathaoApiError) {
233
247
  return error;
234
248
  }
235
249
  const messageFallback = this.getErrorMessage(error);
236
- const axiosLike = error instanceof axios.AxiosError ? error : typeof error === "object" && error && "response" in error ? error : null;
250
+ const hasResponse = (e) => typeof e === "object" && e !== null && "response" in e;
251
+ const axiosLike = error instanceof axios.AxiosError ? error : hasResponse(error) ? error : null;
237
252
  if (axiosLike) {
238
253
  const pathaoError = axiosLike.response?.data;
239
254
  return new PathaoApiError(
@@ -257,6 +272,9 @@ var PathaoApiService = class _PathaoApiService {
257
272
  this.circuitBreaker.isOpen = true;
258
273
  }
259
274
  }
275
+ delay(ms) {
276
+ return new Promise((resolve) => setTimeout(resolve, ms));
277
+ }
260
278
  // Official Pathao API: Create Order
261
279
  async createOrder(orderData) {
262
280
  try {
@@ -282,10 +300,11 @@ var PathaoApiService = class _PathaoApiService {
282
300
  }
283
301
  }
284
302
  // Official Pathao API: Get Store List
285
- async getStores() {
303
+ async getStores(page) {
286
304
  try {
287
305
  const response = await this.pathaoClient.get(
288
- "/aladdin/api/v1/stores"
306
+ "/aladdin/api/v1/stores",
307
+ page !== void 0 ? { params: { page } } : void 0
289
308
  );
290
309
  return response.data;
291
310
  } catch (error) {
@@ -341,7 +360,7 @@ var PathaoApiService = class _PathaoApiService {
341
360
  async getOrderStatus(consignmentId) {
342
361
  try {
343
362
  const response = await this.pathaoClient.get(
344
- `/aladdin/api/v1/orders/${consignmentId}/info`
363
+ `/aladdin/api/v1/orders/${encodeURIComponent(consignmentId)}/info`
345
364
  );
346
365
  return response.data;
347
366
  } catch (error) {
@@ -351,7 +370,10 @@ var PathaoApiService = class _PathaoApiService {
351
370
  // Official Pathao API: Create Bulk Order
352
371
  async createBulkOrder(orders) {
353
372
  try {
354
- const response = await this.pathaoClient.post("/aladdin/api/v1/orders/bulk", { orders });
373
+ const response = await this.pathaoClient.post(
374
+ "/aladdin/api/v1/orders/bulk",
375
+ { orders }
376
+ );
355
377
  return response.data;
356
378
  } catch (error) {
357
379
  throw this.toPathaoApiError(error, "Failed to create bulk Pathao orders");
@@ -399,7 +421,7 @@ var PathaoApiService = class _PathaoApiService {
399
421
  // Helper method to validate contact number
400
422
  static validateContactNumber(phone) {
401
423
  const cleanPhone = phone.replace(/\D/g, "");
402
- return cleanPhone.length === 11;
424
+ return cleanPhone.length === 11 && cleanPhone.startsWith("01");
403
425
  }
404
426
  // Helper method to validate store address
405
427
  static validateStoreAddress(address) {
@@ -413,7 +435,6 @@ var PathaoApiService = class _PathaoApiService {
413
435
  this.tokenExpiry = null;
414
436
  this.isAuthenticating = false;
415
437
  this.authPromise = null;
416
- this.requestQueue = [];
417
438
  this.circuitBreaker.failures = 0;
418
439
  this.circuitBreaker.isOpen = false;
419
440
  this.circuitBreaker.lastFailureTime = 0;
@@ -433,6 +454,20 @@ var PathaoApiService = class _PathaoApiService {
433
454
  static fromConfig(config, options) {
434
455
  return new _PathaoApiService(config, options);
435
456
  }
457
+ // Named constructor for sandbox environment
458
+ static sandbox(credentials, options) {
459
+ return new _PathaoApiService(
460
+ { ...credentials, baseURL: "https://courier-api-sandbox.pathao.com" },
461
+ options
462
+ );
463
+ }
464
+ // Named constructor for production environment
465
+ static production(credentials, options) {
466
+ return new _PathaoApiService(
467
+ { ...credentials, baseURL: "https://api-hermes.pathao.com" },
468
+ options
469
+ );
470
+ }
436
471
  };
437
472
 
438
473
  // src/types.ts