pathao-merchant-sdk 2.0.2 → 2.2.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 +29 -13
- package/dist/index.d.ts +29 -13
- package/dist/index.js +98 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -35
- package/dist/index.mjs.map +1 -1
- package/dist/webhooks.d.mts +312 -0
- package/dist/webhooks.d.ts +312 -0
- package/dist/webhooks.js +193 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/webhooks.mjs +187 -0
- package/dist/webhooks.mjs.map +1 -0
- package/package.json +10 -3
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pathao Webhook Support
|
|
5
|
+
*
|
|
6
|
+
* Handles incoming webhook events from Pathao.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT INTEGRATION DETAILS:
|
|
9
|
+
* - Pathao does NOT sign incoming requests.
|
|
10
|
+
* - Instead, Pathao requires you to prove ownership by echoing your webhook secret
|
|
11
|
+
* in the \`X-Pathao-Merchant-Webhook-Integration-Secret\` header of EVERY response.
|
|
12
|
+
* - This SDK automatically handles the \`webhook_integration\` handshake event, which
|
|
13
|
+
* expects a 202 status code and the secret header.
|
|
14
|
+
*
|
|
15
|
+
* @example — Express
|
|
16
|
+
* \`\`\`typescript
|
|
17
|
+
* import express from 'express';
|
|
18
|
+
* import {
|
|
19
|
+
* PathaoWebhookHandler,
|
|
20
|
+
* PathaoWebhookEvent,
|
|
21
|
+
* } from 'pathao-merchant-sdk/webhooks';
|
|
22
|
+
*
|
|
23
|
+
* const app = express();
|
|
24
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
25
|
+
*
|
|
26
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });
|
|
27
|
+
*
|
|
28
|
+
* // Mount express.json() BEFORE the webhook middleware
|
|
29
|
+
* app.post(
|
|
30
|
+
* '/webhooks/pathao',
|
|
31
|
+
* express.json(),
|
|
32
|
+
* handler.expressMiddleware(),
|
|
33
|
+
* (req, res) => {
|
|
34
|
+
* // The middleware already sets the required secret header.
|
|
35
|
+
* // You just need to return a 200 OK for standard events.
|
|
36
|
+
* res.status(200).send('OK');
|
|
37
|
+
* }
|
|
38
|
+
* );
|
|
39
|
+
* \`\`\`
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
declare class PathaoWebhookError extends Error {
|
|
43
|
+
constructor(message: string);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* All webhook event types emitted by Pathao.
|
|
47
|
+
* Values are the exact strings that appear in the \`event\` field of each payload.
|
|
48
|
+
*/
|
|
49
|
+
declare enum PathaoWebhookEvent {
|
|
50
|
+
WEBHOOK_INTEGRATION = "webhook_integration",
|
|
51
|
+
ORDER_CREATED = "order.created",
|
|
52
|
+
ORDER_UPDATED = "order.updated",
|
|
53
|
+
ORDER_PICKUP_REQUESTED = "order.pickup-requested",
|
|
54
|
+
ORDER_ASSIGNED_FOR_PICKUP = "order.assigned-for-pickup",
|
|
55
|
+
ORDER_PICKED = "order.picked",
|
|
56
|
+
ORDER_PICKUP_FAILED = "order.pickup-failed",
|
|
57
|
+
ORDER_PICKUP_CANCELLED = "order.pickup-cancelled",
|
|
58
|
+
ORDER_AT_THE_SORTING_HUB = "order.at-the-sorting-hub",
|
|
59
|
+
ORDER_IN_TRANSIT = "order.in-transit",
|
|
60
|
+
ORDER_RECEIVED_AT_LAST_MILE_HUB = "order.received-at-last-mile-hub",
|
|
61
|
+
ORDER_ASSIGNED_FOR_DELIVERY = "order.assigned-for-delivery",
|
|
62
|
+
ORDER_DELIVERED = "order.delivered",
|
|
63
|
+
ORDER_PARTIAL_DELIVERY = "order.partial-delivery",
|
|
64
|
+
ORDER_RETURNED = "order.returned",
|
|
65
|
+
ORDER_DELIVERY_FAILED = "order.delivery-failed",
|
|
66
|
+
ORDER_ON_HOLD = "order.on-hold",
|
|
67
|
+
ORDER_PAID = "order.paid",
|
|
68
|
+
ORDER_PAID_RETURN = "order.paid-return",
|
|
69
|
+
ORDER_EXCHANGED = "order.exchanged",
|
|
70
|
+
STORE_CREATED = "store.created",
|
|
71
|
+
STORE_UPDATED = "store.updated"
|
|
72
|
+
}
|
|
73
|
+
interface WebhookIntegrationPayload {
|
|
74
|
+
event: PathaoWebhookEvent.WEBHOOK_INTEGRATION;
|
|
75
|
+
}
|
|
76
|
+
/** Fields present on every normal webhook payload */
|
|
77
|
+
interface BaseWebhookPayload {
|
|
78
|
+
event: string;
|
|
79
|
+
/** Format: MySQL datetime YYYY-MM-DD HH:MM:SS (no timezone indicator) */
|
|
80
|
+
updated_at: string;
|
|
81
|
+
/** Format: ISO 8601 timestamp */
|
|
82
|
+
timestamp: string;
|
|
83
|
+
}
|
|
84
|
+
/** Fields shared by all order-related events */
|
|
85
|
+
interface OrderWebhookPayload extends BaseWebhookPayload {
|
|
86
|
+
consignment_id: string;
|
|
87
|
+
merchant_order_id?: string;
|
|
88
|
+
store_id: number;
|
|
89
|
+
delivery_fee?: number;
|
|
90
|
+
}
|
|
91
|
+
/** Fields shared by all store-related events */
|
|
92
|
+
interface StoreWebhookPayload extends BaseWebhookPayload {
|
|
93
|
+
store_id: number;
|
|
94
|
+
store_name: string;
|
|
95
|
+
store_address: string;
|
|
96
|
+
is_active: 0 | 1;
|
|
97
|
+
}
|
|
98
|
+
interface OrderCreatedPayload extends OrderWebhookPayload {
|
|
99
|
+
event: PathaoWebhookEvent.ORDER_CREATED;
|
|
100
|
+
delivery_fee: number;
|
|
101
|
+
}
|
|
102
|
+
interface OrderUpdatedPayload extends OrderWebhookPayload {
|
|
103
|
+
event: PathaoWebhookEvent.ORDER_UPDATED;
|
|
104
|
+
delivery_fee: number;
|
|
105
|
+
}
|
|
106
|
+
interface OrderPickupRequestedPayload extends OrderWebhookPayload {
|
|
107
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_REQUESTED;
|
|
108
|
+
delivery_fee: number;
|
|
109
|
+
}
|
|
110
|
+
interface OrderAssignedForPickupPayload extends OrderWebhookPayload {
|
|
111
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP;
|
|
112
|
+
}
|
|
113
|
+
interface OrderPickedPayload extends OrderWebhookPayload {
|
|
114
|
+
event: PathaoWebhookEvent.ORDER_PICKED;
|
|
115
|
+
}
|
|
116
|
+
interface OrderPickupFailedPayload extends OrderWebhookPayload {
|
|
117
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_FAILED;
|
|
118
|
+
}
|
|
119
|
+
interface OrderPickupCancelledPayload extends OrderWebhookPayload {
|
|
120
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_CANCELLED;
|
|
121
|
+
}
|
|
122
|
+
interface OrderAtSortingHubPayload extends OrderWebhookPayload {
|
|
123
|
+
event: PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB;
|
|
124
|
+
}
|
|
125
|
+
interface OrderInTransitPayload extends OrderWebhookPayload {
|
|
126
|
+
event: PathaoWebhookEvent.ORDER_IN_TRANSIT;
|
|
127
|
+
}
|
|
128
|
+
interface OrderAtLastMileHubPayload extends OrderWebhookPayload {
|
|
129
|
+
event: PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB;
|
|
130
|
+
}
|
|
131
|
+
interface OrderAssignedForDeliveryPayload extends OrderWebhookPayload {
|
|
132
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY;
|
|
133
|
+
}
|
|
134
|
+
interface OrderDeliveredPayload extends OrderWebhookPayload {
|
|
135
|
+
event: PathaoWebhookEvent.ORDER_DELIVERED;
|
|
136
|
+
collected_amount: number;
|
|
137
|
+
}
|
|
138
|
+
interface OrderPartialDeliveryPayload extends OrderWebhookPayload {
|
|
139
|
+
event: PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY;
|
|
140
|
+
collected_amount: number;
|
|
141
|
+
reason?: string;
|
|
142
|
+
}
|
|
143
|
+
interface OrderReturnedPayload extends OrderWebhookPayload {
|
|
144
|
+
event: PathaoWebhookEvent.ORDER_RETURNED;
|
|
145
|
+
reason?: string;
|
|
146
|
+
}
|
|
147
|
+
interface OrderDeliveryFailedPayload extends OrderWebhookPayload {
|
|
148
|
+
event: PathaoWebhookEvent.ORDER_DELIVERY_FAILED;
|
|
149
|
+
reason?: string;
|
|
150
|
+
}
|
|
151
|
+
interface OrderOnHoldPayload extends OrderWebhookPayload {
|
|
152
|
+
event: PathaoWebhookEvent.ORDER_ON_HOLD;
|
|
153
|
+
reason?: string;
|
|
154
|
+
}
|
|
155
|
+
interface OrderPaidPayload extends OrderWebhookPayload {
|
|
156
|
+
event: PathaoWebhookEvent.ORDER_PAID;
|
|
157
|
+
invoice_id: string;
|
|
158
|
+
}
|
|
159
|
+
interface OrderPaidReturnPayload extends OrderWebhookPayload {
|
|
160
|
+
event: PathaoWebhookEvent.ORDER_PAID_RETURN;
|
|
161
|
+
collected_amount: number;
|
|
162
|
+
reason?: string;
|
|
163
|
+
}
|
|
164
|
+
interface OrderExchangedPayload extends OrderWebhookPayload {
|
|
165
|
+
event: PathaoWebhookEvent.ORDER_EXCHANGED;
|
|
166
|
+
collected_amount: number;
|
|
167
|
+
reason?: string;
|
|
168
|
+
}
|
|
169
|
+
interface StoreCreatedPayload extends StoreWebhookPayload {
|
|
170
|
+
event: PathaoWebhookEvent.STORE_CREATED;
|
|
171
|
+
}
|
|
172
|
+
interface StoreUpdatedPayload extends StoreWebhookPayload {
|
|
173
|
+
event: PathaoWebhookEvent.STORE_UPDATED;
|
|
174
|
+
}
|
|
175
|
+
/** Union of all possible webhook payloads */
|
|
176
|
+
type PathaoWebhookPayload = WebhookIntegrationPayload | OrderCreatedPayload | OrderUpdatedPayload | OrderPickupRequestedPayload | OrderAssignedForPickupPayload | OrderPickedPayload | OrderPickupFailedPayload | OrderPickupCancelledPayload | OrderAtSortingHubPayload | OrderInTransitPayload | OrderAtLastMileHubPayload | OrderAssignedForDeliveryPayload | OrderDeliveredPayload | OrderPartialDeliveryPayload | OrderReturnedPayload | OrderDeliveryFailedPayload | OrderOnHoldPayload | OrderPaidPayload | OrderPaidReturnPayload | OrderExchangedPayload | StoreCreatedPayload | StoreUpdatedPayload;
|
|
177
|
+
/** Maps each \`PathaoWebhookEvent\` to its specific payload type */
|
|
178
|
+
interface WebhookEventPayloadMap {
|
|
179
|
+
[PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;
|
|
180
|
+
[PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;
|
|
181
|
+
[PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;
|
|
182
|
+
[PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;
|
|
183
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP]: OrderAssignedForPickupPayload;
|
|
184
|
+
[PathaoWebhookEvent.ORDER_PICKED]: OrderPickedPayload;
|
|
185
|
+
[PathaoWebhookEvent.ORDER_PICKUP_FAILED]: OrderPickupFailedPayload;
|
|
186
|
+
[PathaoWebhookEvent.ORDER_PICKUP_CANCELLED]: OrderPickupCancelledPayload;
|
|
187
|
+
[PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB]: OrderAtSortingHubPayload;
|
|
188
|
+
[PathaoWebhookEvent.ORDER_IN_TRANSIT]: OrderInTransitPayload;
|
|
189
|
+
[PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB]: OrderAtLastMileHubPayload;
|
|
190
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY]: OrderAssignedForDeliveryPayload;
|
|
191
|
+
[PathaoWebhookEvent.ORDER_DELIVERED]: OrderDeliveredPayload;
|
|
192
|
+
[PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY]: OrderPartialDeliveryPayload;
|
|
193
|
+
[PathaoWebhookEvent.ORDER_RETURNED]: OrderReturnedPayload;
|
|
194
|
+
[PathaoWebhookEvent.ORDER_DELIVERY_FAILED]: OrderDeliveryFailedPayload;
|
|
195
|
+
[PathaoWebhookEvent.ORDER_ON_HOLD]: OrderOnHoldPayload;
|
|
196
|
+
[PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;
|
|
197
|
+
[PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;
|
|
198
|
+
[PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;
|
|
199
|
+
[PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;
|
|
200
|
+
[PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;
|
|
201
|
+
}
|
|
202
|
+
/** Required response header used to authorize your endpoint with Pathao */
|
|
203
|
+
declare const PATHAO_SECRET_HEADER = "x-pathao-merchant-webhook-integration-secret";
|
|
204
|
+
/**
|
|
205
|
+
* Parses the raw body into a webhook payload.
|
|
206
|
+
* Pathao does not sign inbound requests, so this just ensures it is valid JSON with an event field.
|
|
207
|
+
*
|
|
208
|
+
* Throws \`PathaoWebhookError\` on malformed JSON or missing event.
|
|
209
|
+
*
|
|
210
|
+
* @param rawBody Raw request body or parsed object
|
|
211
|
+
*/
|
|
212
|
+
declare function constructEvent(rawBody: Buffer | string | object): PathaoWebhookPayload;
|
|
213
|
+
type WebhookResponseInstructions = {
|
|
214
|
+
statusCode: number;
|
|
215
|
+
headers: Record<string, string>;
|
|
216
|
+
payload: PathaoWebhookPayload | null;
|
|
217
|
+
error: PathaoWebhookError | null;
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Stateful webhook handler that parses payloads, provides response instructions,
|
|
221
|
+
* and dispatches events to typed listeners.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* \`\`\`typescript
|
|
225
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
226
|
+
*
|
|
227
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
228
|
+
* // payload is fully typed as OrderDeliveredPayload
|
|
229
|
+
* console.log(payload.consignment_id, payload.collected_amount);
|
|
230
|
+
* });
|
|
231
|
+
* \`\`\`
|
|
232
|
+
*/
|
|
233
|
+
declare class PathaoWebhookHandler extends EventEmitter {
|
|
234
|
+
private readonly webhookSecret;
|
|
235
|
+
constructor(webhookSecret: string);
|
|
236
|
+
/** Listen for a specific Pathao event with a fully-typed payload callback. */
|
|
237
|
+
on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
238
|
+
/** Fires for every successfully parsed event regardless of type. */
|
|
239
|
+
on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
240
|
+
/** Fires when parsing fails. */
|
|
241
|
+
on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
242
|
+
/** Listen once for a specific Pathao event. */
|
|
243
|
+
once<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
244
|
+
once(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
245
|
+
once(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
246
|
+
/**
|
|
247
|
+
* Parse the body and dispatch the event.
|
|
248
|
+
*
|
|
249
|
+
* Throws \`PathaoWebhookError\` on failure and also emits \`'error'\` so
|
|
250
|
+
* listeners can respond without a try/catch.
|
|
251
|
+
*
|
|
252
|
+
* @param rawBody Raw request body or matched json object
|
|
253
|
+
*/
|
|
254
|
+
process(rawBody: Buffer | string | object): PathaoWebhookPayload;
|
|
255
|
+
/**
|
|
256
|
+
* Returns an Express-compatible middleware function.
|
|
257
|
+
*
|
|
258
|
+
* Automatically sets the required \`X-Pathao-Merchant-Webhook-Integration-Secret\` header.
|
|
259
|
+
* Automatically responds with 202 for the \`webhook_integration\` handshake.
|
|
260
|
+
* For standard events, attaches the payload to \`req.pathaoWebhook\` and calls \`next()\`.
|
|
261
|
+
* On error, calls \`next(err)\`.
|
|
262
|
+
*
|
|
263
|
+
* \`\`\`typescript
|
|
264
|
+
* app.post(
|
|
265
|
+
* '/webhooks/pathao',
|
|
266
|
+
* express.json(),
|
|
267
|
+
* handler.expressMiddleware(),
|
|
268
|
+
* (req, res) => res.sendStatus(200) // You must send 200 for other events
|
|
269
|
+
* );
|
|
270
|
+
* \`\`\`
|
|
271
|
+
*/
|
|
272
|
+
expressMiddleware(): (req: {
|
|
273
|
+
body: Buffer | string | object;
|
|
274
|
+
pathaoWebhook?: PathaoWebhookPayload;
|
|
275
|
+
}, res: {
|
|
276
|
+
setHeader: (name: string, value: string) => void;
|
|
277
|
+
status: (code: number) => {
|
|
278
|
+
send: () => void;
|
|
279
|
+
};
|
|
280
|
+
}, next: (err?: unknown) => void) => void;
|
|
281
|
+
/**
|
|
282
|
+
* Returns response instructions for any framework (Fastify, Hono, etc.).
|
|
283
|
+
*
|
|
284
|
+
* Never throws — always resolves with a \`WebhookResponseInstructions\` object
|
|
285
|
+
* that tells you which status code and headers to return, along with the payload/error.
|
|
286
|
+
*
|
|
287
|
+
* \`\`\`typescript
|
|
288
|
+
* const handle = handler.middleware();
|
|
289
|
+
* const instructions = await handle(request.body);
|
|
290
|
+
*
|
|
291
|
+
* // Apply the required headers (the secret header)
|
|
292
|
+
* for (const [key, value] of Object.entries(instructions.headers)) {
|
|
293
|
+
* reply.header(key, value);
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* if (instructions.error) {
|
|
297
|
+
* return reply.status(instructions.statusCode).send({ error: instructions.error.message });
|
|
298
|
+
* }
|
|
299
|
+
*
|
|
300
|
+
* // If it was the handshake, we should just return 202 as instructed
|
|
301
|
+
* if (instructions.payload?.event === 'webhook_integration') {
|
|
302
|
+
* return reply.status(instructions.statusCode).send();
|
|
303
|
+
* }
|
|
304
|
+
*
|
|
305
|
+
* // Process your real webhook
|
|
306
|
+
* return reply.status(instructions.statusCode).send({ received: true });
|
|
307
|
+
* \`\`\`
|
|
308
|
+
*/
|
|
309
|
+
middleware(): (rawBody: Buffer | string | object) => Promise<WebhookResponseInstructions>;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
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_SECRET_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, type PathaoWebhookPayload, type StoreCreatedPayload, type StoreUpdatedPayload, type StoreWebhookPayload, type WebhookEventPayloadMap, type WebhookIntegrationPayload, type WebhookResponseInstructions, constructEvent };
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pathao Webhook Support
|
|
5
|
+
*
|
|
6
|
+
* Handles incoming webhook events from Pathao.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT INTEGRATION DETAILS:
|
|
9
|
+
* - Pathao does NOT sign incoming requests.
|
|
10
|
+
* - Instead, Pathao requires you to prove ownership by echoing your webhook secret
|
|
11
|
+
* in the \`X-Pathao-Merchant-Webhook-Integration-Secret\` header of EVERY response.
|
|
12
|
+
* - This SDK automatically handles the \`webhook_integration\` handshake event, which
|
|
13
|
+
* expects a 202 status code and the secret header.
|
|
14
|
+
*
|
|
15
|
+
* @example — Express
|
|
16
|
+
* \`\`\`typescript
|
|
17
|
+
* import express from 'express';
|
|
18
|
+
* import {
|
|
19
|
+
* PathaoWebhookHandler,
|
|
20
|
+
* PathaoWebhookEvent,
|
|
21
|
+
* } from 'pathao-merchant-sdk/webhooks';
|
|
22
|
+
*
|
|
23
|
+
* const app = express();
|
|
24
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
25
|
+
*
|
|
26
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });
|
|
27
|
+
*
|
|
28
|
+
* // Mount express.json() BEFORE the webhook middleware
|
|
29
|
+
* app.post(
|
|
30
|
+
* '/webhooks/pathao',
|
|
31
|
+
* express.json(),
|
|
32
|
+
* handler.expressMiddleware(),
|
|
33
|
+
* (req, res) => {
|
|
34
|
+
* // The middleware already sets the required secret header.
|
|
35
|
+
* // You just need to return a 200 OK for standard events.
|
|
36
|
+
* res.status(200).send('OK');
|
|
37
|
+
* }
|
|
38
|
+
* );
|
|
39
|
+
* \`\`\`
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
declare class PathaoWebhookError extends Error {
|
|
43
|
+
constructor(message: string);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* All webhook event types emitted by Pathao.
|
|
47
|
+
* Values are the exact strings that appear in the \`event\` field of each payload.
|
|
48
|
+
*/
|
|
49
|
+
declare enum PathaoWebhookEvent {
|
|
50
|
+
WEBHOOK_INTEGRATION = "webhook_integration",
|
|
51
|
+
ORDER_CREATED = "order.created",
|
|
52
|
+
ORDER_UPDATED = "order.updated",
|
|
53
|
+
ORDER_PICKUP_REQUESTED = "order.pickup-requested",
|
|
54
|
+
ORDER_ASSIGNED_FOR_PICKUP = "order.assigned-for-pickup",
|
|
55
|
+
ORDER_PICKED = "order.picked",
|
|
56
|
+
ORDER_PICKUP_FAILED = "order.pickup-failed",
|
|
57
|
+
ORDER_PICKUP_CANCELLED = "order.pickup-cancelled",
|
|
58
|
+
ORDER_AT_THE_SORTING_HUB = "order.at-the-sorting-hub",
|
|
59
|
+
ORDER_IN_TRANSIT = "order.in-transit",
|
|
60
|
+
ORDER_RECEIVED_AT_LAST_MILE_HUB = "order.received-at-last-mile-hub",
|
|
61
|
+
ORDER_ASSIGNED_FOR_DELIVERY = "order.assigned-for-delivery",
|
|
62
|
+
ORDER_DELIVERED = "order.delivered",
|
|
63
|
+
ORDER_PARTIAL_DELIVERY = "order.partial-delivery",
|
|
64
|
+
ORDER_RETURNED = "order.returned",
|
|
65
|
+
ORDER_DELIVERY_FAILED = "order.delivery-failed",
|
|
66
|
+
ORDER_ON_HOLD = "order.on-hold",
|
|
67
|
+
ORDER_PAID = "order.paid",
|
|
68
|
+
ORDER_PAID_RETURN = "order.paid-return",
|
|
69
|
+
ORDER_EXCHANGED = "order.exchanged",
|
|
70
|
+
STORE_CREATED = "store.created",
|
|
71
|
+
STORE_UPDATED = "store.updated"
|
|
72
|
+
}
|
|
73
|
+
interface WebhookIntegrationPayload {
|
|
74
|
+
event: PathaoWebhookEvent.WEBHOOK_INTEGRATION;
|
|
75
|
+
}
|
|
76
|
+
/** Fields present on every normal webhook payload */
|
|
77
|
+
interface BaseWebhookPayload {
|
|
78
|
+
event: string;
|
|
79
|
+
/** Format: MySQL datetime YYYY-MM-DD HH:MM:SS (no timezone indicator) */
|
|
80
|
+
updated_at: string;
|
|
81
|
+
/** Format: ISO 8601 timestamp */
|
|
82
|
+
timestamp: string;
|
|
83
|
+
}
|
|
84
|
+
/** Fields shared by all order-related events */
|
|
85
|
+
interface OrderWebhookPayload extends BaseWebhookPayload {
|
|
86
|
+
consignment_id: string;
|
|
87
|
+
merchant_order_id?: string;
|
|
88
|
+
store_id: number;
|
|
89
|
+
delivery_fee?: number;
|
|
90
|
+
}
|
|
91
|
+
/** Fields shared by all store-related events */
|
|
92
|
+
interface StoreWebhookPayload extends BaseWebhookPayload {
|
|
93
|
+
store_id: number;
|
|
94
|
+
store_name: string;
|
|
95
|
+
store_address: string;
|
|
96
|
+
is_active: 0 | 1;
|
|
97
|
+
}
|
|
98
|
+
interface OrderCreatedPayload extends OrderWebhookPayload {
|
|
99
|
+
event: PathaoWebhookEvent.ORDER_CREATED;
|
|
100
|
+
delivery_fee: number;
|
|
101
|
+
}
|
|
102
|
+
interface OrderUpdatedPayload extends OrderWebhookPayload {
|
|
103
|
+
event: PathaoWebhookEvent.ORDER_UPDATED;
|
|
104
|
+
delivery_fee: number;
|
|
105
|
+
}
|
|
106
|
+
interface OrderPickupRequestedPayload extends OrderWebhookPayload {
|
|
107
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_REQUESTED;
|
|
108
|
+
delivery_fee: number;
|
|
109
|
+
}
|
|
110
|
+
interface OrderAssignedForPickupPayload extends OrderWebhookPayload {
|
|
111
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP;
|
|
112
|
+
}
|
|
113
|
+
interface OrderPickedPayload extends OrderWebhookPayload {
|
|
114
|
+
event: PathaoWebhookEvent.ORDER_PICKED;
|
|
115
|
+
}
|
|
116
|
+
interface OrderPickupFailedPayload extends OrderWebhookPayload {
|
|
117
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_FAILED;
|
|
118
|
+
}
|
|
119
|
+
interface OrderPickupCancelledPayload extends OrderWebhookPayload {
|
|
120
|
+
event: PathaoWebhookEvent.ORDER_PICKUP_CANCELLED;
|
|
121
|
+
}
|
|
122
|
+
interface OrderAtSortingHubPayload extends OrderWebhookPayload {
|
|
123
|
+
event: PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB;
|
|
124
|
+
}
|
|
125
|
+
interface OrderInTransitPayload extends OrderWebhookPayload {
|
|
126
|
+
event: PathaoWebhookEvent.ORDER_IN_TRANSIT;
|
|
127
|
+
}
|
|
128
|
+
interface OrderAtLastMileHubPayload extends OrderWebhookPayload {
|
|
129
|
+
event: PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB;
|
|
130
|
+
}
|
|
131
|
+
interface OrderAssignedForDeliveryPayload extends OrderWebhookPayload {
|
|
132
|
+
event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY;
|
|
133
|
+
}
|
|
134
|
+
interface OrderDeliveredPayload extends OrderWebhookPayload {
|
|
135
|
+
event: PathaoWebhookEvent.ORDER_DELIVERED;
|
|
136
|
+
collected_amount: number;
|
|
137
|
+
}
|
|
138
|
+
interface OrderPartialDeliveryPayload extends OrderWebhookPayload {
|
|
139
|
+
event: PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY;
|
|
140
|
+
collected_amount: number;
|
|
141
|
+
reason?: string;
|
|
142
|
+
}
|
|
143
|
+
interface OrderReturnedPayload extends OrderWebhookPayload {
|
|
144
|
+
event: PathaoWebhookEvent.ORDER_RETURNED;
|
|
145
|
+
reason?: string;
|
|
146
|
+
}
|
|
147
|
+
interface OrderDeliveryFailedPayload extends OrderWebhookPayload {
|
|
148
|
+
event: PathaoWebhookEvent.ORDER_DELIVERY_FAILED;
|
|
149
|
+
reason?: string;
|
|
150
|
+
}
|
|
151
|
+
interface OrderOnHoldPayload extends OrderWebhookPayload {
|
|
152
|
+
event: PathaoWebhookEvent.ORDER_ON_HOLD;
|
|
153
|
+
reason?: string;
|
|
154
|
+
}
|
|
155
|
+
interface OrderPaidPayload extends OrderWebhookPayload {
|
|
156
|
+
event: PathaoWebhookEvent.ORDER_PAID;
|
|
157
|
+
invoice_id: string;
|
|
158
|
+
}
|
|
159
|
+
interface OrderPaidReturnPayload extends OrderWebhookPayload {
|
|
160
|
+
event: PathaoWebhookEvent.ORDER_PAID_RETURN;
|
|
161
|
+
collected_amount: number;
|
|
162
|
+
reason?: string;
|
|
163
|
+
}
|
|
164
|
+
interface OrderExchangedPayload extends OrderWebhookPayload {
|
|
165
|
+
event: PathaoWebhookEvent.ORDER_EXCHANGED;
|
|
166
|
+
collected_amount: number;
|
|
167
|
+
reason?: string;
|
|
168
|
+
}
|
|
169
|
+
interface StoreCreatedPayload extends StoreWebhookPayload {
|
|
170
|
+
event: PathaoWebhookEvent.STORE_CREATED;
|
|
171
|
+
}
|
|
172
|
+
interface StoreUpdatedPayload extends StoreWebhookPayload {
|
|
173
|
+
event: PathaoWebhookEvent.STORE_UPDATED;
|
|
174
|
+
}
|
|
175
|
+
/** Union of all possible webhook payloads */
|
|
176
|
+
type PathaoWebhookPayload = WebhookIntegrationPayload | OrderCreatedPayload | OrderUpdatedPayload | OrderPickupRequestedPayload | OrderAssignedForPickupPayload | OrderPickedPayload | OrderPickupFailedPayload | OrderPickupCancelledPayload | OrderAtSortingHubPayload | OrderInTransitPayload | OrderAtLastMileHubPayload | OrderAssignedForDeliveryPayload | OrderDeliveredPayload | OrderPartialDeliveryPayload | OrderReturnedPayload | OrderDeliveryFailedPayload | OrderOnHoldPayload | OrderPaidPayload | OrderPaidReturnPayload | OrderExchangedPayload | StoreCreatedPayload | StoreUpdatedPayload;
|
|
177
|
+
/** Maps each \`PathaoWebhookEvent\` to its specific payload type */
|
|
178
|
+
interface WebhookEventPayloadMap {
|
|
179
|
+
[PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;
|
|
180
|
+
[PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;
|
|
181
|
+
[PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;
|
|
182
|
+
[PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;
|
|
183
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP]: OrderAssignedForPickupPayload;
|
|
184
|
+
[PathaoWebhookEvent.ORDER_PICKED]: OrderPickedPayload;
|
|
185
|
+
[PathaoWebhookEvent.ORDER_PICKUP_FAILED]: OrderPickupFailedPayload;
|
|
186
|
+
[PathaoWebhookEvent.ORDER_PICKUP_CANCELLED]: OrderPickupCancelledPayload;
|
|
187
|
+
[PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB]: OrderAtSortingHubPayload;
|
|
188
|
+
[PathaoWebhookEvent.ORDER_IN_TRANSIT]: OrderInTransitPayload;
|
|
189
|
+
[PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB]: OrderAtLastMileHubPayload;
|
|
190
|
+
[PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY]: OrderAssignedForDeliveryPayload;
|
|
191
|
+
[PathaoWebhookEvent.ORDER_DELIVERED]: OrderDeliveredPayload;
|
|
192
|
+
[PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY]: OrderPartialDeliveryPayload;
|
|
193
|
+
[PathaoWebhookEvent.ORDER_RETURNED]: OrderReturnedPayload;
|
|
194
|
+
[PathaoWebhookEvent.ORDER_DELIVERY_FAILED]: OrderDeliveryFailedPayload;
|
|
195
|
+
[PathaoWebhookEvent.ORDER_ON_HOLD]: OrderOnHoldPayload;
|
|
196
|
+
[PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;
|
|
197
|
+
[PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;
|
|
198
|
+
[PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;
|
|
199
|
+
[PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;
|
|
200
|
+
[PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;
|
|
201
|
+
}
|
|
202
|
+
/** Required response header used to authorize your endpoint with Pathao */
|
|
203
|
+
declare const PATHAO_SECRET_HEADER = "x-pathao-merchant-webhook-integration-secret";
|
|
204
|
+
/**
|
|
205
|
+
* Parses the raw body into a webhook payload.
|
|
206
|
+
* Pathao does not sign inbound requests, so this just ensures it is valid JSON with an event field.
|
|
207
|
+
*
|
|
208
|
+
* Throws \`PathaoWebhookError\` on malformed JSON or missing event.
|
|
209
|
+
*
|
|
210
|
+
* @param rawBody Raw request body or parsed object
|
|
211
|
+
*/
|
|
212
|
+
declare function constructEvent(rawBody: Buffer | string | object): PathaoWebhookPayload;
|
|
213
|
+
type WebhookResponseInstructions = {
|
|
214
|
+
statusCode: number;
|
|
215
|
+
headers: Record<string, string>;
|
|
216
|
+
payload: PathaoWebhookPayload | null;
|
|
217
|
+
error: PathaoWebhookError | null;
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Stateful webhook handler that parses payloads, provides response instructions,
|
|
221
|
+
* and dispatches events to typed listeners.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* \`\`\`typescript
|
|
225
|
+
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
226
|
+
*
|
|
227
|
+
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
228
|
+
* // payload is fully typed as OrderDeliveredPayload
|
|
229
|
+
* console.log(payload.consignment_id, payload.collected_amount);
|
|
230
|
+
* });
|
|
231
|
+
* \`\`\`
|
|
232
|
+
*/
|
|
233
|
+
declare class PathaoWebhookHandler extends EventEmitter {
|
|
234
|
+
private readonly webhookSecret;
|
|
235
|
+
constructor(webhookSecret: string);
|
|
236
|
+
/** Listen for a specific Pathao event with a fully-typed payload callback. */
|
|
237
|
+
on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
238
|
+
/** Fires for every successfully parsed event regardless of type. */
|
|
239
|
+
on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
240
|
+
/** Fires when parsing fails. */
|
|
241
|
+
on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
242
|
+
/** Listen once for a specific Pathao event. */
|
|
243
|
+
once<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
244
|
+
once(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
245
|
+
once(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
246
|
+
/**
|
|
247
|
+
* Parse the body and dispatch the event.
|
|
248
|
+
*
|
|
249
|
+
* Throws \`PathaoWebhookError\` on failure and also emits \`'error'\` so
|
|
250
|
+
* listeners can respond without a try/catch.
|
|
251
|
+
*
|
|
252
|
+
* @param rawBody Raw request body or matched json object
|
|
253
|
+
*/
|
|
254
|
+
process(rawBody: Buffer | string | object): PathaoWebhookPayload;
|
|
255
|
+
/**
|
|
256
|
+
* Returns an Express-compatible middleware function.
|
|
257
|
+
*
|
|
258
|
+
* Automatically sets the required \`X-Pathao-Merchant-Webhook-Integration-Secret\` header.
|
|
259
|
+
* Automatically responds with 202 for the \`webhook_integration\` handshake.
|
|
260
|
+
* For standard events, attaches the payload to \`req.pathaoWebhook\` and calls \`next()\`.
|
|
261
|
+
* On error, calls \`next(err)\`.
|
|
262
|
+
*
|
|
263
|
+
* \`\`\`typescript
|
|
264
|
+
* app.post(
|
|
265
|
+
* '/webhooks/pathao',
|
|
266
|
+
* express.json(),
|
|
267
|
+
* handler.expressMiddleware(),
|
|
268
|
+
* (req, res) => res.sendStatus(200) // You must send 200 for other events
|
|
269
|
+
* );
|
|
270
|
+
* \`\`\`
|
|
271
|
+
*/
|
|
272
|
+
expressMiddleware(): (req: {
|
|
273
|
+
body: Buffer | string | object;
|
|
274
|
+
pathaoWebhook?: PathaoWebhookPayload;
|
|
275
|
+
}, res: {
|
|
276
|
+
setHeader: (name: string, value: string) => void;
|
|
277
|
+
status: (code: number) => {
|
|
278
|
+
send: () => void;
|
|
279
|
+
};
|
|
280
|
+
}, next: (err?: unknown) => void) => void;
|
|
281
|
+
/**
|
|
282
|
+
* Returns response instructions for any framework (Fastify, Hono, etc.).
|
|
283
|
+
*
|
|
284
|
+
* Never throws — always resolves with a \`WebhookResponseInstructions\` object
|
|
285
|
+
* that tells you which status code and headers to return, along with the payload/error.
|
|
286
|
+
*
|
|
287
|
+
* \`\`\`typescript
|
|
288
|
+
* const handle = handler.middleware();
|
|
289
|
+
* const instructions = await handle(request.body);
|
|
290
|
+
*
|
|
291
|
+
* // Apply the required headers (the secret header)
|
|
292
|
+
* for (const [key, value] of Object.entries(instructions.headers)) {
|
|
293
|
+
* reply.header(key, value);
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* if (instructions.error) {
|
|
297
|
+
* return reply.status(instructions.statusCode).send({ error: instructions.error.message });
|
|
298
|
+
* }
|
|
299
|
+
*
|
|
300
|
+
* // If it was the handshake, we should just return 202 as instructed
|
|
301
|
+
* if (instructions.payload?.event === 'webhook_integration') {
|
|
302
|
+
* return reply.status(instructions.statusCode).send();
|
|
303
|
+
* }
|
|
304
|
+
*
|
|
305
|
+
* // Process your real webhook
|
|
306
|
+
* return reply.status(instructions.statusCode).send({ received: true });
|
|
307
|
+
* \`\`\`
|
|
308
|
+
*/
|
|
309
|
+
middleware(): (rawBody: Buffer | string | object) => Promise<WebhookResponseInstructions>;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
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_SECRET_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, type PathaoWebhookPayload, type StoreCreatedPayload, type StoreUpdatedPayload, type StoreWebhookPayload, type WebhookEventPayloadMap, type WebhookIntegrationPayload, type WebhookResponseInstructions, constructEvent };
|