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
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pathao Webhook Support
|
|
5
|
+
*
|
|
6
|
+
* Handles incoming webhook events from Pathao.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: The official Pathao API documentation does not describe a webhook
|
|
9
|
+
* specification. This implementation is based on observed webhook behaviour and
|
|
10
|
+
* community research. Treat it as best-effort until Pathao publishes official
|
|
11
|
+
* webhook docs.
|
|
12
|
+
*
|
|
13
|
+
* Signature mechanism: Pathao sends the raw shared secret in the
|
|
14
|
+
* `X-PATHAO-Signature` header. There is no HMAC — the header value IS the
|
|
15
|
+
* secret. A constant-time comparison is used to prevent timing attacks.
|
|
16
|
+
*
|
|
17
|
+
* @example — framework-agnostic
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {
|
|
20
|
+
* PathaoWebhookHandler,
|
|
21
|
+
* PathaoWebhookEvent,
|
|
22
|
+
* } from 'pathao-merchant-sdk/webhooks';
|
|
23
|
+
*
|
|
24
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
25
|
+
*
|
|
26
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
27
|
+
* console.log(payload.consignment_id, payload.collected_amount);
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // In your HTTP server — raw body as Buffer/string required:
|
|
31
|
+
* const event = handler.process(rawBody, req.headers);
|
|
32
|
+
* res.status(200).json({ received: true });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example — Express
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import express from 'express';
|
|
38
|
+
* import {
|
|
39
|
+
* PathaoWebhookHandler,
|
|
40
|
+
* PathaoWebhookEvent,
|
|
41
|
+
* } from 'pathao-merchant-sdk/webhooks';
|
|
42
|
+
*
|
|
43
|
+
* const app = express();
|
|
44
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
45
|
+
*
|
|
46
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });
|
|
47
|
+
*
|
|
48
|
+
* // Mount express.raw() BEFORE the webhook middleware
|
|
49
|
+
* app.post(
|
|
50
|
+
* '/webhooks/pathao',
|
|
51
|
+
* express.raw({ type: 'application/json' }),
|
|
52
|
+
* handler.expressMiddleware(),
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
declare class PathaoWebhookError extends Error {
|
|
58
|
+
constructor(message: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* All webhook event types emitted by Pathao.
|
|
62
|
+
* Values are the exact strings that appear in the `event` field of each payload.
|
|
63
|
+
*/
|
|
64
|
+
declare enum PathaoWebhookEvent {
|
|
65
|
+
ORDER_CREATED = "order.created",
|
|
66
|
+
ORDER_UPDATED = "order.updated",
|
|
67
|
+
ORDER_PICKUP_REQUESTED = "order.pickup-requested",
|
|
68
|
+
ORDER_ASSIGNED_FOR_PICKUP = "order.assigned-for-pickup",
|
|
69
|
+
ORDER_PICKED = "order.picked",
|
|
70
|
+
ORDER_PICKUP_FAILED = "order.pickup-failed",
|
|
71
|
+
ORDER_PICKUP_CANCELLED = "order.pickup-cancelled",
|
|
72
|
+
ORDER_AT_THE_SORTING_HUB = "order.at-the-sorting-hub",
|
|
73
|
+
ORDER_IN_TRANSIT = "order.in-transit",
|
|
74
|
+
ORDER_RECEIVED_AT_LAST_MILE_HUB = "order.received-at-last-mile-hub",
|
|
75
|
+
ORDER_ASSIGNED_FOR_DELIVERY = "order.assigned-for-delivery",
|
|
76
|
+
ORDER_DELIVERED = "order.delivered",
|
|
77
|
+
ORDER_PARTIAL_DELIVERY = "order.partial-delivery",
|
|
78
|
+
ORDER_RETURNED = "order.returned",
|
|
79
|
+
ORDER_DELIVERY_FAILED = "order.delivery-failed",
|
|
80
|
+
ORDER_ON_HOLD = "order.on-hold",
|
|
81
|
+
ORDER_PAID = "order.paid",
|
|
82
|
+
ORDER_PAID_RETURN = "order.paid-return",
|
|
83
|
+
ORDER_EXCHANGED = "order.exchanged",
|
|
84
|
+
STORE_CREATED = "store.created",
|
|
85
|
+
STORE_UPDATED = "store.updated"
|
|
86
|
+
}
|
|
87
|
+
/** Fields present on every webhook payload */
|
|
88
|
+
interface BaseWebhookPayload {
|
|
89
|
+
/** Dot-notation event type — matches a `PathaoWebhookEvent` value */
|
|
90
|
+
event: string;
|
|
91
|
+
/** ISO timestamp of when the state change occurred */
|
|
92
|
+
updated_at: string;
|
|
93
|
+
/** ISO timestamp of when this webhook was dispatched */
|
|
94
|
+
timestamp: string;
|
|
95
|
+
}
|
|
96
|
+
/** Fields shared by all order-related events */
|
|
97
|
+
interface OrderWebhookPayload extends BaseWebhookPayload {
|
|
98
|
+
consignment_id: string;
|
|
99
|
+
merchant_order_id?: string;
|
|
100
|
+
store_id: number;
|
|
101
|
+
delivery_fee?: number;
|
|
102
|
+
}
|
|
103
|
+
/** Fields shared by all store-related events */
|
|
104
|
+
interface StoreWebhookPayload extends BaseWebhookPayload {
|
|
105
|
+
store_id: number;
|
|
106
|
+
store_name: string;
|
|
107
|
+
store_address: string;
|
|
108
|
+
is_active: 0 | 1;
|
|
109
|
+
}
|
|
110
|
+
interface OrderCreatedPayload extends OrderWebhookPayload {
|
|
111
|
+
event: PathaoWebhookEvent.ORDER_CREATED;
|
|
112
|
+
delivery_fee: number;
|
|
113
|
+
}
|
|
114
|
+
interface OrderUpdatedPayload extends OrderWebhookPayload {
|
|
115
|
+
event: PathaoWebhookEvent.ORDER_UPDATED;
|
|
116
|
+
delivery_fee: number;
|
|
117
|
+
}
|
|
118
|
+
interface OrderPickupRequestedPayload extends OrderWebhookPayload {
|
|
119
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_REQUESTED;
|
|
120
|
+
delivery_fee: number;
|
|
121
|
+
}
|
|
122
|
+
interface OrderAssignedForPickupPayload extends OrderWebhookPayload {
|
|
123
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP;
|
|
124
|
+
}
|
|
125
|
+
interface OrderPickedPayload extends OrderWebhookPayload {
|
|
126
|
+
event: PathaoWebhookEvent.ORDER_PICKED;
|
|
127
|
+
}
|
|
128
|
+
interface OrderPickupFailedPayload extends OrderWebhookPayload {
|
|
129
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_FAILED;
|
|
130
|
+
}
|
|
131
|
+
interface OrderPickupCancelledPayload extends OrderWebhookPayload {
|
|
132
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_CANCELLED;
|
|
133
|
+
}
|
|
134
|
+
interface OrderAtSortingHubPayload extends OrderWebhookPayload {
|
|
135
|
+
event: PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB;
|
|
136
|
+
}
|
|
137
|
+
interface OrderInTransitPayload extends OrderWebhookPayload {
|
|
138
|
+
event: PathaoWebhookEvent.ORDER_IN_TRANSIT;
|
|
139
|
+
}
|
|
140
|
+
interface OrderAtLastMileHubPayload extends OrderWebhookPayload {
|
|
141
|
+
event: PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB;
|
|
142
|
+
}
|
|
143
|
+
interface OrderAssignedForDeliveryPayload extends OrderWebhookPayload {
|
|
144
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY;
|
|
145
|
+
}
|
|
146
|
+
interface OrderDeliveredPayload extends OrderWebhookPayload {
|
|
147
|
+
event: PathaoWebhookEvent.ORDER_DELIVERED;
|
|
148
|
+
collected_amount: number;
|
|
149
|
+
}
|
|
150
|
+
interface OrderPartialDeliveryPayload extends OrderWebhookPayload {
|
|
151
|
+
event: PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY;
|
|
152
|
+
collected_amount: number;
|
|
153
|
+
reason?: string;
|
|
154
|
+
}
|
|
155
|
+
interface OrderReturnedPayload extends OrderWebhookPayload {
|
|
156
|
+
event: PathaoWebhookEvent.ORDER_RETURNED;
|
|
157
|
+
reason?: string;
|
|
158
|
+
}
|
|
159
|
+
interface OrderDeliveryFailedPayload extends OrderWebhookPayload {
|
|
160
|
+
event: PathaoWebhookEvent.ORDER_DELIVERY_FAILED;
|
|
161
|
+
reason?: string;
|
|
162
|
+
}
|
|
163
|
+
interface OrderOnHoldPayload extends OrderWebhookPayload {
|
|
164
|
+
event: PathaoWebhookEvent.ORDER_ON_HOLD;
|
|
165
|
+
reason?: string;
|
|
166
|
+
}
|
|
167
|
+
interface OrderPaidPayload extends OrderWebhookPayload {
|
|
168
|
+
event: PathaoWebhookEvent.ORDER_PAID;
|
|
169
|
+
invoice_id: string;
|
|
170
|
+
}
|
|
171
|
+
interface OrderPaidReturnPayload extends OrderWebhookPayload {
|
|
172
|
+
event: PathaoWebhookEvent.ORDER_PAID_RETURN;
|
|
173
|
+
collected_amount: number;
|
|
174
|
+
reason?: string;
|
|
175
|
+
}
|
|
176
|
+
interface OrderExchangedPayload extends OrderWebhookPayload {
|
|
177
|
+
event: PathaoWebhookEvent.ORDER_EXCHANGED;
|
|
178
|
+
collected_amount: number;
|
|
179
|
+
reason?: string;
|
|
180
|
+
}
|
|
181
|
+
interface StoreCreatedPayload extends StoreWebhookPayload {
|
|
182
|
+
event: PathaoWebhookEvent.STORE_CREATED;
|
|
183
|
+
}
|
|
184
|
+
interface StoreUpdatedPayload extends StoreWebhookPayload {
|
|
185
|
+
event: PathaoWebhookEvent.STORE_UPDATED;
|
|
186
|
+
}
|
|
187
|
+
/** Union of all possible webhook payloads */
|
|
188
|
+
type PathaoWebhookPayload = OrderCreatedPayload | OrderUpdatedPayload | OrderPickupRequestedPayload | OrderAssignedForPickupPayload | OrderPickedPayload | OrderPickupFailedPayload | OrderPickupCancelledPayload | OrderAtSortingHubPayload | OrderInTransitPayload | OrderAtLastMileHubPayload | OrderAssignedForDeliveryPayload | OrderDeliveredPayload | OrderPartialDeliveryPayload | OrderReturnedPayload | OrderDeliveryFailedPayload | OrderOnHoldPayload | OrderPaidPayload | OrderPaidReturnPayload | OrderExchangedPayload | StoreCreatedPayload | StoreUpdatedPayload;
|
|
189
|
+
/** Maps each `PathaoWebhookEvent` to its specific payload type */
|
|
190
|
+
interface WebhookEventPayloadMap {
|
|
191
|
+
[PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;
|
|
192
|
+
[PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;
|
|
193
|
+
[PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;
|
|
194
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP]: OrderAssignedForPickupPayload;
|
|
195
|
+
[PathaoWebhookEvent.ORDER_PICKED]: OrderPickedPayload;
|
|
196
|
+
[PathaoWebhookEvent.ORDER_PICKUP_FAILED]: OrderPickupFailedPayload;
|
|
197
|
+
[PathaoWebhookEvent.ORDER_PICKUP_CANCELLED]: OrderPickupCancelledPayload;
|
|
198
|
+
[PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB]: OrderAtSortingHubPayload;
|
|
199
|
+
[PathaoWebhookEvent.ORDER_IN_TRANSIT]: OrderInTransitPayload;
|
|
200
|
+
[PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB]: OrderAtLastMileHubPayload;
|
|
201
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY]: OrderAssignedForDeliveryPayload;
|
|
202
|
+
[PathaoWebhookEvent.ORDER_DELIVERED]: OrderDeliveredPayload;
|
|
203
|
+
[PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY]: OrderPartialDeliveryPayload;
|
|
204
|
+
[PathaoWebhookEvent.ORDER_RETURNED]: OrderReturnedPayload;
|
|
205
|
+
[PathaoWebhookEvent.ORDER_DELIVERY_FAILED]: OrderDeliveryFailedPayload;
|
|
206
|
+
[PathaoWebhookEvent.ORDER_ON_HOLD]: OrderOnHoldPayload;
|
|
207
|
+
[PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;
|
|
208
|
+
[PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;
|
|
209
|
+
[PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;
|
|
210
|
+
[PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;
|
|
211
|
+
[PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;
|
|
212
|
+
}
|
|
213
|
+
/** Signature header name sent by Pathao on every webhook request */
|
|
214
|
+
declare const PATHAO_SIGNATURE_HEADER = "x-pathao-signature";
|
|
215
|
+
/**
|
|
216
|
+
* Verify the `X-PATHAO-Signature` header against the configured webhook secret.
|
|
217
|
+
*
|
|
218
|
+
* Uses a constant-time comparison to prevent timing attacks. The header value
|
|
219
|
+
* is the raw shared secret — Pathao does not hash the signature.
|
|
220
|
+
*
|
|
221
|
+
* @returns `true` if valid, `false` if missing or mismatched.
|
|
222
|
+
*/
|
|
223
|
+
declare function verifySignature(signature: string | undefined, webhookSecret: string): boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Verify the webhook signature and parse the raw body in one step.
|
|
226
|
+
*
|
|
227
|
+
* Throws `PathaoWebhookError` on signature failure or malformed JSON.
|
|
228
|
+
*
|
|
229
|
+
* @param rawBody Raw request body — do NOT pre-parse with `JSON.parse`
|
|
230
|
+
* @param headers Request headers object
|
|
231
|
+
* @param webhookSecret Your Pathao webhook integration secret
|
|
232
|
+
*/
|
|
233
|
+
declare function constructEvent(rawBody: Buffer | string, headers: Record<string, string | string[] | undefined>, webhookSecret: string): PathaoWebhookPayload;
|
|
234
|
+
type GenericHeaders = Record<string, string | string[] | undefined>;
|
|
235
|
+
/**
|
|
236
|
+
* Stateful webhook handler that verifies incoming requests, parses payloads,
|
|
237
|
+
* and dispatches events to typed listeners.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
242
|
+
*
|
|
243
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
244
|
+
* // payload is fully typed as OrderDeliveredPayload
|
|
245
|
+
* console.log(payload.consignment_id, payload.collected_amount);
|
|
246
|
+
* });
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
declare class PathaoWebhookHandler extends EventEmitter {
|
|
250
|
+
private readonly webhookSecret;
|
|
251
|
+
constructor(webhookSecret: string);
|
|
252
|
+
/** Listen for a specific Pathao event with a fully-typed payload callback. */
|
|
253
|
+
on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
254
|
+
/** Fires for every successfully verified event regardless of type. */
|
|
255
|
+
on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
256
|
+
/** Fires when verification or parsing fails. */
|
|
257
|
+
on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
258
|
+
/**
|
|
259
|
+
* Verify the signature, parse the body, and dispatch the event.
|
|
260
|
+
*
|
|
261
|
+
* Throws `PathaoWebhookError` on failure and also emits `'error'` so
|
|
262
|
+
* listeners can respond without a try/catch.
|
|
263
|
+
*
|
|
264
|
+
* @param rawBody Raw request body — must NOT be pre-parsed
|
|
265
|
+
* @param headers HTTP request headers
|
|
266
|
+
*/
|
|
267
|
+
process(rawBody: Buffer | string, headers: GenericHeaders): PathaoWebhookPayload;
|
|
268
|
+
/**
|
|
269
|
+
* Returns an Express-compatible middleware function.
|
|
270
|
+
*
|
|
271
|
+
* **Requires** `express.raw({ type: 'application/json' })` mounted on the
|
|
272
|
+
* same route before this middleware so the raw body Buffer is preserved.
|
|
273
|
+
*
|
|
274
|
+
* On success the parsed payload is attached to `req.pathaoWebhook`.
|
|
275
|
+
* On failure a `400` response is returned.
|
|
276
|
+
*
|
|
277
|
+
* ```typescript
|
|
278
|
+
* app.post(
|
|
279
|
+
* '/webhooks/pathao',
|
|
280
|
+
* express.raw({ type: 'application/json' }),
|
|
281
|
+
* handler.expressMiddleware(),
|
|
282
|
+
* );
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
expressMiddleware(): (req: {
|
|
286
|
+
body: Buffer | string;
|
|
287
|
+
headers: GenericHeaders;
|
|
288
|
+
pathaoWebhook?: PathaoWebhookPayload;
|
|
289
|
+
}, res: {
|
|
290
|
+
status: (code: number) => {
|
|
291
|
+
json: (body: unknown) => void;
|
|
292
|
+
};
|
|
293
|
+
}, next: (err?: unknown) => void) => void;
|
|
294
|
+
/**
|
|
295
|
+
* Returns a generic async handler for any framework (Fastify, Hono, plain
|
|
296
|
+
* `http.createServer`, etc.).
|
|
297
|
+
*
|
|
298
|
+
* Never rejects — always resolves with either `{ payload, error: null }` or
|
|
299
|
+
* `{ payload: null, error: PathaoWebhookError }`.
|
|
300
|
+
*
|
|
301
|
+
* ```typescript
|
|
302
|
+
* const handle = handler.middleware();
|
|
303
|
+
* const { payload, error } = await handle(rawBody, request.headers);
|
|
304
|
+
* if (error) { reply.status(400).send({ error: error.message }); return; }
|
|
305
|
+
* reply.send({ received: true });
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
middleware(): (rawBody: Buffer | string, headers: GenericHeaders) => Promise<{
|
|
309
|
+
payload: PathaoWebhookPayload;
|
|
310
|
+
error: null;
|
|
311
|
+
} | {
|
|
312
|
+
payload: null;
|
|
313
|
+
error: PathaoWebhookError;
|
|
314
|
+
}>;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export { type BaseWebhookPayload, type OrderAssignedForDeliveryPayload, type OrderAssignedForPickupPayload, type OrderAtLastMileHubPayload, type OrderAtSortingHubPayload, type OrderCreatedPayload, type OrderDeliveredPayload, type OrderDeliveryFailedPayload, type OrderExchangedPayload, type OrderInTransitPayload, type OrderOnHoldPayload, type OrderPaidPayload, type OrderPaidReturnPayload, type OrderPartialDeliveryPayload, type OrderPickedPayload, type OrderPickupCancelledPayload, type OrderPickupFailedPayload, type OrderPickupRequestedPayload, type OrderReturnedPayload, type OrderUpdatedPayload, type OrderWebhookPayload, PATHAO_SIGNATURE_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, type PathaoWebhookPayload, type StoreCreatedPayload, type StoreUpdatedPayload, type StoreWebhookPayload, type WebhookEventPayloadMap, constructEvent, verifySignature };
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pathao Webhook Support
|
|
5
|
+
*
|
|
6
|
+
* Handles incoming webhook events from Pathao.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: The official Pathao API documentation does not describe a webhook
|
|
9
|
+
* specification. This implementation is based on observed webhook behaviour and
|
|
10
|
+
* community research. Treat it as best-effort until Pathao publishes official
|
|
11
|
+
* webhook docs.
|
|
12
|
+
*
|
|
13
|
+
* Signature mechanism: Pathao sends the raw shared secret in the
|
|
14
|
+
* `X-PATHAO-Signature` header. There is no HMAC — the header value IS the
|
|
15
|
+
* secret. A constant-time comparison is used to prevent timing attacks.
|
|
16
|
+
*
|
|
17
|
+
* @example — framework-agnostic
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {
|
|
20
|
+
* PathaoWebhookHandler,
|
|
21
|
+
* PathaoWebhookEvent,
|
|
22
|
+
* } from 'pathao-merchant-sdk/webhooks';
|
|
23
|
+
*
|
|
24
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
25
|
+
*
|
|
26
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
27
|
+
* console.log(payload.consignment_id, payload.collected_amount);
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // In your HTTP server — raw body as Buffer/string required:
|
|
31
|
+
* const event = handler.process(rawBody, req.headers);
|
|
32
|
+
* res.status(200).json({ received: true });
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example — Express
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import express from 'express';
|
|
38
|
+
* import {
|
|
39
|
+
* PathaoWebhookHandler,
|
|
40
|
+
* PathaoWebhookEvent,
|
|
41
|
+
* } from 'pathao-merchant-sdk/webhooks';
|
|
42
|
+
*
|
|
43
|
+
* const app = express();
|
|
44
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
45
|
+
*
|
|
46
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });
|
|
47
|
+
*
|
|
48
|
+
* // Mount express.raw() BEFORE the webhook middleware
|
|
49
|
+
* app.post(
|
|
50
|
+
* '/webhooks/pathao',
|
|
51
|
+
* express.raw({ type: 'application/json' }),
|
|
52
|
+
* handler.expressMiddleware(),
|
|
53
|
+
* );
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
declare class PathaoWebhookError extends Error {
|
|
58
|
+
constructor(message: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* All webhook event types emitted by Pathao.
|
|
62
|
+
* Values are the exact strings that appear in the `event` field of each payload.
|
|
63
|
+
*/
|
|
64
|
+
declare enum PathaoWebhookEvent {
|
|
65
|
+
ORDER_CREATED = "order.created",
|
|
66
|
+
ORDER_UPDATED = "order.updated",
|
|
67
|
+
ORDER_PICKUP_REQUESTED = "order.pickup-requested",
|
|
68
|
+
ORDER_ASSIGNED_FOR_PICKUP = "order.assigned-for-pickup",
|
|
69
|
+
ORDER_PICKED = "order.picked",
|
|
70
|
+
ORDER_PICKUP_FAILED = "order.pickup-failed",
|
|
71
|
+
ORDER_PICKUP_CANCELLED = "order.pickup-cancelled",
|
|
72
|
+
ORDER_AT_THE_SORTING_HUB = "order.at-the-sorting-hub",
|
|
73
|
+
ORDER_IN_TRANSIT = "order.in-transit",
|
|
74
|
+
ORDER_RECEIVED_AT_LAST_MILE_HUB = "order.received-at-last-mile-hub",
|
|
75
|
+
ORDER_ASSIGNED_FOR_DELIVERY = "order.assigned-for-delivery",
|
|
76
|
+
ORDER_DELIVERED = "order.delivered",
|
|
77
|
+
ORDER_PARTIAL_DELIVERY = "order.partial-delivery",
|
|
78
|
+
ORDER_RETURNED = "order.returned",
|
|
79
|
+
ORDER_DELIVERY_FAILED = "order.delivery-failed",
|
|
80
|
+
ORDER_ON_HOLD = "order.on-hold",
|
|
81
|
+
ORDER_PAID = "order.paid",
|
|
82
|
+
ORDER_PAID_RETURN = "order.paid-return",
|
|
83
|
+
ORDER_EXCHANGED = "order.exchanged",
|
|
84
|
+
STORE_CREATED = "store.created",
|
|
85
|
+
STORE_UPDATED = "store.updated"
|
|
86
|
+
}
|
|
87
|
+
/** Fields present on every webhook payload */
|
|
88
|
+
interface BaseWebhookPayload {
|
|
89
|
+
/** Dot-notation event type — matches a `PathaoWebhookEvent` value */
|
|
90
|
+
event: string;
|
|
91
|
+
/** ISO timestamp of when the state change occurred */
|
|
92
|
+
updated_at: string;
|
|
93
|
+
/** ISO timestamp of when this webhook was dispatched */
|
|
94
|
+
timestamp: string;
|
|
95
|
+
}
|
|
96
|
+
/** Fields shared by all order-related events */
|
|
97
|
+
interface OrderWebhookPayload extends BaseWebhookPayload {
|
|
98
|
+
consignment_id: string;
|
|
99
|
+
merchant_order_id?: string;
|
|
100
|
+
store_id: number;
|
|
101
|
+
delivery_fee?: number;
|
|
102
|
+
}
|
|
103
|
+
/** Fields shared by all store-related events */
|
|
104
|
+
interface StoreWebhookPayload extends BaseWebhookPayload {
|
|
105
|
+
store_id: number;
|
|
106
|
+
store_name: string;
|
|
107
|
+
store_address: string;
|
|
108
|
+
is_active: 0 | 1;
|
|
109
|
+
}
|
|
110
|
+
interface OrderCreatedPayload extends OrderWebhookPayload {
|
|
111
|
+
event: PathaoWebhookEvent.ORDER_CREATED;
|
|
112
|
+
delivery_fee: number;
|
|
113
|
+
}
|
|
114
|
+
interface OrderUpdatedPayload extends OrderWebhookPayload {
|
|
115
|
+
event: PathaoWebhookEvent.ORDER_UPDATED;
|
|
116
|
+
delivery_fee: number;
|
|
117
|
+
}
|
|
118
|
+
interface OrderPickupRequestedPayload extends OrderWebhookPayload {
|
|
119
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_REQUESTED;
|
|
120
|
+
delivery_fee: number;
|
|
121
|
+
}
|
|
122
|
+
interface OrderAssignedForPickupPayload extends OrderWebhookPayload {
|
|
123
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP;
|
|
124
|
+
}
|
|
125
|
+
interface OrderPickedPayload extends OrderWebhookPayload {
|
|
126
|
+
event: PathaoWebhookEvent.ORDER_PICKED;
|
|
127
|
+
}
|
|
128
|
+
interface OrderPickupFailedPayload extends OrderWebhookPayload {
|
|
129
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_FAILED;
|
|
130
|
+
}
|
|
131
|
+
interface OrderPickupCancelledPayload extends OrderWebhookPayload {
|
|
132
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_CANCELLED;
|
|
133
|
+
}
|
|
134
|
+
interface OrderAtSortingHubPayload extends OrderWebhookPayload {
|
|
135
|
+
event: PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB;
|
|
136
|
+
}
|
|
137
|
+
interface OrderInTransitPayload extends OrderWebhookPayload {
|
|
138
|
+
event: PathaoWebhookEvent.ORDER_IN_TRANSIT;
|
|
139
|
+
}
|
|
140
|
+
interface OrderAtLastMileHubPayload extends OrderWebhookPayload {
|
|
141
|
+
event: PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB;
|
|
142
|
+
}
|
|
143
|
+
interface OrderAssignedForDeliveryPayload extends OrderWebhookPayload {
|
|
144
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY;
|
|
145
|
+
}
|
|
146
|
+
interface OrderDeliveredPayload extends OrderWebhookPayload {
|
|
147
|
+
event: PathaoWebhookEvent.ORDER_DELIVERED;
|
|
148
|
+
collected_amount: number;
|
|
149
|
+
}
|
|
150
|
+
interface OrderPartialDeliveryPayload extends OrderWebhookPayload {
|
|
151
|
+
event: PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY;
|
|
152
|
+
collected_amount: number;
|
|
153
|
+
reason?: string;
|
|
154
|
+
}
|
|
155
|
+
interface OrderReturnedPayload extends OrderWebhookPayload {
|
|
156
|
+
event: PathaoWebhookEvent.ORDER_RETURNED;
|
|
157
|
+
reason?: string;
|
|
158
|
+
}
|
|
159
|
+
interface OrderDeliveryFailedPayload extends OrderWebhookPayload {
|
|
160
|
+
event: PathaoWebhookEvent.ORDER_DELIVERY_FAILED;
|
|
161
|
+
reason?: string;
|
|
162
|
+
}
|
|
163
|
+
interface OrderOnHoldPayload extends OrderWebhookPayload {
|
|
164
|
+
event: PathaoWebhookEvent.ORDER_ON_HOLD;
|
|
165
|
+
reason?: string;
|
|
166
|
+
}
|
|
167
|
+
interface OrderPaidPayload extends OrderWebhookPayload {
|
|
168
|
+
event: PathaoWebhookEvent.ORDER_PAID;
|
|
169
|
+
invoice_id: string;
|
|
170
|
+
}
|
|
171
|
+
interface OrderPaidReturnPayload extends OrderWebhookPayload {
|
|
172
|
+
event: PathaoWebhookEvent.ORDER_PAID_RETURN;
|
|
173
|
+
collected_amount: number;
|
|
174
|
+
reason?: string;
|
|
175
|
+
}
|
|
176
|
+
interface OrderExchangedPayload extends OrderWebhookPayload {
|
|
177
|
+
event: PathaoWebhookEvent.ORDER_EXCHANGED;
|
|
178
|
+
collected_amount: number;
|
|
179
|
+
reason?: string;
|
|
180
|
+
}
|
|
181
|
+
interface StoreCreatedPayload extends StoreWebhookPayload {
|
|
182
|
+
event: PathaoWebhookEvent.STORE_CREATED;
|
|
183
|
+
}
|
|
184
|
+
interface StoreUpdatedPayload extends StoreWebhookPayload {
|
|
185
|
+
event: PathaoWebhookEvent.STORE_UPDATED;
|
|
186
|
+
}
|
|
187
|
+
/** Union of all possible webhook payloads */
|
|
188
|
+
type PathaoWebhookPayload = OrderCreatedPayload | OrderUpdatedPayload | OrderPickupRequestedPayload | OrderAssignedForPickupPayload | OrderPickedPayload | OrderPickupFailedPayload | OrderPickupCancelledPayload | OrderAtSortingHubPayload | OrderInTransitPayload | OrderAtLastMileHubPayload | OrderAssignedForDeliveryPayload | OrderDeliveredPayload | OrderPartialDeliveryPayload | OrderReturnedPayload | OrderDeliveryFailedPayload | OrderOnHoldPayload | OrderPaidPayload | OrderPaidReturnPayload | OrderExchangedPayload | StoreCreatedPayload | StoreUpdatedPayload;
|
|
189
|
+
/** Maps each `PathaoWebhookEvent` to its specific payload type */
|
|
190
|
+
interface WebhookEventPayloadMap {
|
|
191
|
+
[PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;
|
|
192
|
+
[PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;
|
|
193
|
+
[PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;
|
|
194
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP]: OrderAssignedForPickupPayload;
|
|
195
|
+
[PathaoWebhookEvent.ORDER_PICKED]: OrderPickedPayload;
|
|
196
|
+
[PathaoWebhookEvent.ORDER_PICKUP_FAILED]: OrderPickupFailedPayload;
|
|
197
|
+
[PathaoWebhookEvent.ORDER_PICKUP_CANCELLED]: OrderPickupCancelledPayload;
|
|
198
|
+
[PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB]: OrderAtSortingHubPayload;
|
|
199
|
+
[PathaoWebhookEvent.ORDER_IN_TRANSIT]: OrderInTransitPayload;
|
|
200
|
+
[PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB]: OrderAtLastMileHubPayload;
|
|
201
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY]: OrderAssignedForDeliveryPayload;
|
|
202
|
+
[PathaoWebhookEvent.ORDER_DELIVERED]: OrderDeliveredPayload;
|
|
203
|
+
[PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY]: OrderPartialDeliveryPayload;
|
|
204
|
+
[PathaoWebhookEvent.ORDER_RETURNED]: OrderReturnedPayload;
|
|
205
|
+
[PathaoWebhookEvent.ORDER_DELIVERY_FAILED]: OrderDeliveryFailedPayload;
|
|
206
|
+
[PathaoWebhookEvent.ORDER_ON_HOLD]: OrderOnHoldPayload;
|
|
207
|
+
[PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;
|
|
208
|
+
[PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;
|
|
209
|
+
[PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;
|
|
210
|
+
[PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;
|
|
211
|
+
[PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;
|
|
212
|
+
}
|
|
213
|
+
/** Signature header name sent by Pathao on every webhook request */
|
|
214
|
+
declare const PATHAO_SIGNATURE_HEADER = "x-pathao-signature";
|
|
215
|
+
/**
|
|
216
|
+
* Verify the `X-PATHAO-Signature` header against the configured webhook secret.
|
|
217
|
+
*
|
|
218
|
+
* Uses a constant-time comparison to prevent timing attacks. The header value
|
|
219
|
+
* is the raw shared secret — Pathao does not hash the signature.
|
|
220
|
+
*
|
|
221
|
+
* @returns `true` if valid, `false` if missing or mismatched.
|
|
222
|
+
*/
|
|
223
|
+
declare function verifySignature(signature: string | undefined, webhookSecret: string): boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Verify the webhook signature and parse the raw body in one step.
|
|
226
|
+
*
|
|
227
|
+
* Throws `PathaoWebhookError` on signature failure or malformed JSON.
|
|
228
|
+
*
|
|
229
|
+
* @param rawBody Raw request body — do NOT pre-parse with `JSON.parse`
|
|
230
|
+
* @param headers Request headers object
|
|
231
|
+
* @param webhookSecret Your Pathao webhook integration secret
|
|
232
|
+
*/
|
|
233
|
+
declare function constructEvent(rawBody: Buffer | string, headers: Record<string, string | string[] | undefined>, webhookSecret: string): PathaoWebhookPayload;
|
|
234
|
+
type GenericHeaders = Record<string, string | string[] | undefined>;
|
|
235
|
+
/**
|
|
236
|
+
* Stateful webhook handler that verifies incoming requests, parses payloads,
|
|
237
|
+
* and dispatches events to typed listeners.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
242
|
+
*
|
|
243
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
244
|
+
* // payload is fully typed as OrderDeliveredPayload
|
|
245
|
+
* console.log(payload.consignment_id, payload.collected_amount);
|
|
246
|
+
* });
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
declare class PathaoWebhookHandler extends EventEmitter {
|
|
250
|
+
private readonly webhookSecret;
|
|
251
|
+
constructor(webhookSecret: string);
|
|
252
|
+
/** Listen for a specific Pathao event with a fully-typed payload callback. */
|
|
253
|
+
on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
254
|
+
/** Fires for every successfully verified event regardless of type. */
|
|
255
|
+
on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
256
|
+
/** Fires when verification or parsing fails. */
|
|
257
|
+
on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
258
|
+
/**
|
|
259
|
+
* Verify the signature, parse the body, and dispatch the event.
|
|
260
|
+
*
|
|
261
|
+
* Throws `PathaoWebhookError` on failure and also emits `'error'` so
|
|
262
|
+
* listeners can respond without a try/catch.
|
|
263
|
+
*
|
|
264
|
+
* @param rawBody Raw request body — must NOT be pre-parsed
|
|
265
|
+
* @param headers HTTP request headers
|
|
266
|
+
*/
|
|
267
|
+
process(rawBody: Buffer | string, headers: GenericHeaders): PathaoWebhookPayload;
|
|
268
|
+
/**
|
|
269
|
+
* Returns an Express-compatible middleware function.
|
|
270
|
+
*
|
|
271
|
+
* **Requires** `express.raw({ type: 'application/json' })` mounted on the
|
|
272
|
+
* same route before this middleware so the raw body Buffer is preserved.
|
|
273
|
+
*
|
|
274
|
+
* On success the parsed payload is attached to `req.pathaoWebhook`.
|
|
275
|
+
* On failure a `400` response is returned.
|
|
276
|
+
*
|
|
277
|
+
* ```typescript
|
|
278
|
+
* app.post(
|
|
279
|
+
* '/webhooks/pathao',
|
|
280
|
+
* express.raw({ type: 'application/json' }),
|
|
281
|
+
* handler.expressMiddleware(),
|
|
282
|
+
* );
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
expressMiddleware(): (req: {
|
|
286
|
+
body: Buffer | string;
|
|
287
|
+
headers: GenericHeaders;
|
|
288
|
+
pathaoWebhook?: PathaoWebhookPayload;
|
|
289
|
+
}, res: {
|
|
290
|
+
status: (code: number) => {
|
|
291
|
+
json: (body: unknown) => void;
|
|
292
|
+
};
|
|
293
|
+
}, next: (err?: unknown) => void) => void;
|
|
294
|
+
/**
|
|
295
|
+
* Returns a generic async handler for any framework (Fastify, Hono, plain
|
|
296
|
+
* `http.createServer`, etc.).
|
|
297
|
+
*
|
|
298
|
+
* Never rejects — always resolves with either `{ payload, error: null }` or
|
|
299
|
+
* `{ payload: null, error: PathaoWebhookError }`.
|
|
300
|
+
*
|
|
301
|
+
* ```typescript
|
|
302
|
+
* const handle = handler.middleware();
|
|
303
|
+
* const { payload, error } = await handle(rawBody, request.headers);
|
|
304
|
+
* if (error) { reply.status(400).send({ error: error.message }); return; }
|
|
305
|
+
* reply.send({ received: true });
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
middleware(): (rawBody: Buffer | string, headers: GenericHeaders) => Promise<{
|
|
309
|
+
payload: PathaoWebhookPayload;
|
|
310
|
+
error: null;
|
|
311
|
+
} | {
|
|
312
|
+
payload: null;
|
|
313
|
+
error: PathaoWebhookError;
|
|
314
|
+
}>;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export { type BaseWebhookPayload, type OrderAssignedForDeliveryPayload, type OrderAssignedForPickupPayload, type OrderAtLastMileHubPayload, type OrderAtSortingHubPayload, type OrderCreatedPayload, type OrderDeliveredPayload, type OrderDeliveryFailedPayload, type OrderExchangedPayload, type OrderInTransitPayload, type OrderOnHoldPayload, type OrderPaidPayload, type OrderPaidReturnPayload, type OrderPartialDeliveryPayload, type OrderPickedPayload, type OrderPickupCancelledPayload, type OrderPickupFailedPayload, type OrderPickupRequestedPayload, type OrderReturnedPayload, type OrderUpdatedPayload, type OrderWebhookPayload, PATHAO_SIGNATURE_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, type PathaoWebhookPayload, type StoreCreatedPayload, type StoreUpdatedPayload, type StoreWebhookPayload, type WebhookEventPayloadMap, constructEvent, verifySignature };
|