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 +185 -0
- package/dist/index.d.mts +27 -12
- package/dist/index.d.ts +27 -12
- package/dist/index.js +63 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -28
- package/dist/index.mjs.map +1 -1
- package/dist/webhooks.d.mts +317 -0
- package/dist/webhooks.d.ts +317 -0
- package/dist/webhooks.js +185 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/webhooks.mjs +178 -0
- package/dist/webhooks.mjs.map +1 -0
- package/package.json +8 -2
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|