pathao-merchant-sdk 2.1.0 → 2.3.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/CHANGELOG.md +144 -68
- package/README.md +397 -471
- package/dist/index.d.mts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -7
- package/dist/index.mjs.map +1 -1
- package/dist/webhooks.d.mts +113 -93
- package/dist/webhooks.d.ts +113 -93
- package/dist/webhooks.js +87 -74
- package/dist/webhooks.js.map +1 -1
- package/dist/webhooks.mjs +87 -73
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +5 -9
package/dist/webhooks.d.ts
CHANGED
|
@@ -5,35 +5,15 @@ import { EventEmitter } from 'events';
|
|
|
5
5
|
*
|
|
6
6
|
* Handles incoming webhook events from Pathao.
|
|
7
7
|
*
|
|
8
|
-
* IMPORTANT
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
* ```
|
|
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.
|
|
34
14
|
*
|
|
35
15
|
* @example — Express
|
|
36
|
-
*
|
|
16
|
+
* \`\`\`typescript
|
|
37
17
|
* import express from 'express';
|
|
38
18
|
* import {
|
|
39
19
|
* PathaoWebhookHandler,
|
|
@@ -45,13 +25,18 @@ import { EventEmitter } from 'events';
|
|
|
45
25
|
*
|
|
46
26
|
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });
|
|
47
27
|
*
|
|
48
|
-
* // Mount express.
|
|
28
|
+
* // Mount express.json() BEFORE the webhook middleware
|
|
49
29
|
* app.post(
|
|
50
30
|
* '/webhooks/pathao',
|
|
51
|
-
* express.
|
|
31
|
+
* express.json(),
|
|
52
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
|
+
* }
|
|
53
38
|
* );
|
|
54
|
-
*
|
|
39
|
+
* \`\`\`
|
|
55
40
|
*/
|
|
56
41
|
|
|
57
42
|
declare class PathaoWebhookError extends Error {
|
|
@@ -59,9 +44,10 @@ declare class PathaoWebhookError extends Error {
|
|
|
59
44
|
}
|
|
60
45
|
/**
|
|
61
46
|
* All webhook event types emitted by Pathao.
|
|
62
|
-
* Values are the exact strings that appear in the
|
|
47
|
+
* Values are the exact strings that appear in the \`event\` field of each payload.
|
|
63
48
|
*/
|
|
64
49
|
declare enum PathaoWebhookEvent {
|
|
50
|
+
WEBHOOK_INTEGRATION = "webhook_integration",
|
|
65
51
|
ORDER_CREATED = "order.created",
|
|
66
52
|
ORDER_UPDATED = "order.updated",
|
|
67
53
|
ORDER_PICKUP_REQUESTED = "order.pickup-requested",
|
|
@@ -81,16 +67,21 @@ declare enum PathaoWebhookEvent {
|
|
|
81
67
|
ORDER_PAID = "order.paid",
|
|
82
68
|
ORDER_PAID_RETURN = "order.paid-return",
|
|
83
69
|
ORDER_EXCHANGED = "order.exchanged",
|
|
70
|
+
ORDER_RETURN_ID_CREATED = "order.return-id-created",
|
|
71
|
+
ORDER_RETURN_IN_TRANSIT = "order.return-in-transit",
|
|
72
|
+
ORDER_RETURNED_TO_MERCHANT = "order.returned-to-merchant",
|
|
84
73
|
STORE_CREATED = "store.created",
|
|
85
74
|
STORE_UPDATED = "store.updated"
|
|
86
75
|
}
|
|
87
|
-
|
|
76
|
+
interface WebhookIntegrationPayload {
|
|
77
|
+
event: PathaoWebhookEvent.WEBHOOK_INTEGRATION;
|
|
78
|
+
}
|
|
79
|
+
/** Fields present on every normal webhook payload */
|
|
88
80
|
interface BaseWebhookPayload {
|
|
89
|
-
/** Dot-notation event type — matches a `PathaoWebhookEvent` value */
|
|
90
81
|
event: string;
|
|
91
|
-
/**
|
|
82
|
+
/** Format: MySQL datetime YYYY-MM-DD HH:MM:SS (no timezone indicator) */
|
|
92
83
|
updated_at: string;
|
|
93
|
-
/** ISO timestamp
|
|
84
|
+
/** Format: ISO 8601 timestamp */
|
|
94
85
|
timestamp: string;
|
|
95
86
|
}
|
|
96
87
|
/** Fields shared by all order-related events */
|
|
@@ -178,6 +169,25 @@ interface OrderExchangedPayload extends OrderWebhookPayload {
|
|
|
178
169
|
collected_amount: number;
|
|
179
170
|
reason?: string;
|
|
180
171
|
}
|
|
172
|
+
/** Shared fields for the three return-journey events */
|
|
173
|
+
interface ReturnOrderWebhookPayload extends BaseWebhookPayload {
|
|
174
|
+
consignment_id: string;
|
|
175
|
+
return_consignment_id: string;
|
|
176
|
+
merchant_order_id?: string;
|
|
177
|
+
store_id: number;
|
|
178
|
+
collected_amount: number;
|
|
179
|
+
return_type: 'return' | 'paid-return' | 'exchange' | 'partial-delivery';
|
|
180
|
+
reason?: string;
|
|
181
|
+
}
|
|
182
|
+
interface OrderReturnIdCreatedPayload extends ReturnOrderWebhookPayload {
|
|
183
|
+
event: PathaoWebhookEvent.ORDER_RETURN_ID_CREATED;
|
|
184
|
+
}
|
|
185
|
+
interface OrderReturnInTransitPayload extends ReturnOrderWebhookPayload {
|
|
186
|
+
event: PathaoWebhookEvent.ORDER_RETURN_IN_TRANSIT;
|
|
187
|
+
}
|
|
188
|
+
interface OrderReturnedToMerchantPayload extends ReturnOrderWebhookPayload {
|
|
189
|
+
event: PathaoWebhookEvent.ORDER_RETURNED_TO_MERCHANT;
|
|
190
|
+
}
|
|
181
191
|
interface StoreCreatedPayload extends StoreWebhookPayload {
|
|
182
192
|
event: PathaoWebhookEvent.STORE_CREATED;
|
|
183
193
|
}
|
|
@@ -185,9 +195,10 @@ interface StoreUpdatedPayload extends StoreWebhookPayload {
|
|
|
185
195
|
event: PathaoWebhookEvent.STORE_UPDATED;
|
|
186
196
|
}
|
|
187
197
|
/** 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
|
|
198
|
+
type PathaoWebhookPayload = WebhookIntegrationPayload | OrderCreatedPayload | OrderUpdatedPayload | OrderPickupRequestedPayload | OrderAssignedForPickupPayload | OrderPickedPayload | OrderPickupFailedPayload | OrderPickupCancelledPayload | OrderAtSortingHubPayload | OrderInTransitPayload | OrderAtLastMileHubPayload | OrderAssignedForDeliveryPayload | OrderDeliveredPayload | OrderPartialDeliveryPayload | OrderReturnedPayload | OrderDeliveryFailedPayload | OrderOnHoldPayload | OrderPaidPayload | OrderPaidReturnPayload | OrderExchangedPayload | OrderReturnIdCreatedPayload | OrderReturnInTransitPayload | OrderReturnedToMerchantPayload | StoreCreatedPayload | StoreUpdatedPayload;
|
|
199
|
+
/** Maps each \`PathaoWebhookEvent\` to its specific payload type */
|
|
190
200
|
interface WebhookEventPayloadMap {
|
|
201
|
+
[PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;
|
|
191
202
|
[PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;
|
|
192
203
|
[PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;
|
|
193
204
|
[PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;
|
|
@@ -207,111 +218,120 @@ interface WebhookEventPayloadMap {
|
|
|
207
218
|
[PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;
|
|
208
219
|
[PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;
|
|
209
220
|
[PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;
|
|
221
|
+
[PathaoWebhookEvent.ORDER_RETURN_ID_CREATED]: OrderReturnIdCreatedPayload;
|
|
222
|
+
[PathaoWebhookEvent.ORDER_RETURN_IN_TRANSIT]: OrderReturnInTransitPayload;
|
|
223
|
+
[PathaoWebhookEvent.ORDER_RETURNED_TO_MERCHANT]: OrderReturnedToMerchantPayload;
|
|
210
224
|
[PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;
|
|
211
225
|
[PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;
|
|
212
226
|
}
|
|
213
|
-
/**
|
|
214
|
-
declare const
|
|
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;
|
|
227
|
+
/** Required response header used to authorize your endpoint with Pathao */
|
|
228
|
+
declare const PATHAO_SECRET_HEADER = "x-pathao-merchant-webhook-integration-secret";
|
|
224
229
|
/**
|
|
225
|
-
*
|
|
230
|
+
* Parses the raw body into a webhook payload.
|
|
231
|
+
* Pathao does not sign inbound requests, so this just ensures it is valid JSON with an event field.
|
|
226
232
|
*
|
|
227
|
-
* Throws
|
|
233
|
+
* Throws \`PathaoWebhookError\` on malformed JSON or missing event.
|
|
228
234
|
*
|
|
229
|
-
* @param rawBody Raw request body
|
|
230
|
-
* @param headers Request headers object
|
|
231
|
-
* @param webhookSecret Your Pathao webhook integration secret
|
|
235
|
+
* @param rawBody Raw request body or parsed object
|
|
232
236
|
*/
|
|
233
|
-
declare function constructEvent(rawBody: Buffer | string
|
|
234
|
-
type
|
|
237
|
+
declare function constructEvent(rawBody: Buffer | string | object): PathaoWebhookPayload;
|
|
238
|
+
type WebhookResponseInstructions = {
|
|
239
|
+
statusCode: number;
|
|
240
|
+
headers: Record<string, string>;
|
|
241
|
+
payload: PathaoWebhookPayload | null;
|
|
242
|
+
error: PathaoWebhookError | null;
|
|
243
|
+
};
|
|
235
244
|
/**
|
|
236
|
-
* Stateful webhook handler that
|
|
245
|
+
* Stateful webhook handler that parses payloads, provides response instructions,
|
|
237
246
|
* and dispatches events to typed listeners.
|
|
238
247
|
*
|
|
239
248
|
* @example
|
|
240
|
-
*
|
|
249
|
+
* \`\`\`typescript
|
|
241
250
|
* const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);
|
|
242
251
|
*
|
|
243
252
|
* handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {
|
|
244
253
|
* // payload is fully typed as OrderDeliveredPayload
|
|
245
254
|
* console.log(payload.consignment_id, payload.collected_amount);
|
|
246
255
|
* });
|
|
247
|
-
*
|
|
256
|
+
* \`\`\`
|
|
248
257
|
*/
|
|
249
258
|
declare class PathaoWebhookHandler extends EventEmitter {
|
|
250
259
|
private readonly webhookSecret;
|
|
251
260
|
constructor(webhookSecret: string);
|
|
252
261
|
/** Listen for a specific Pathao event with a fully-typed payload callback. */
|
|
253
262
|
on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
254
|
-
/** Fires for every successfully
|
|
263
|
+
/** Fires for every successfully parsed event regardless of type. */
|
|
255
264
|
on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
256
|
-
/** Fires when
|
|
265
|
+
/** Fires when parsing fails. */
|
|
257
266
|
on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
267
|
+
/** Listen once for a specific Pathao event. */
|
|
268
|
+
once<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
|
|
269
|
+
once(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
|
|
270
|
+
once(event: 'error', listener: (error: PathaoWebhookError) => void): this;
|
|
258
271
|
/**
|
|
259
|
-
*
|
|
272
|
+
* Parse the body and dispatch the event.
|
|
260
273
|
*
|
|
261
|
-
* Throws
|
|
274
|
+
* Throws \`PathaoWebhookError\` on failure and also emits \`'error'\` so
|
|
262
275
|
* listeners can respond without a try/catch.
|
|
263
276
|
*
|
|
264
|
-
* @param rawBody Raw request body
|
|
265
|
-
* @param headers HTTP request headers
|
|
277
|
+
* @param rawBody Raw request body or matched json object
|
|
266
278
|
*/
|
|
267
|
-
process(rawBody: Buffer | string
|
|
279
|
+
process(rawBody: Buffer | string | object): PathaoWebhookPayload;
|
|
268
280
|
/**
|
|
269
281
|
* Returns an Express-compatible middleware function.
|
|
270
282
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* On
|
|
275
|
-
* On failure a `400` response is returned.
|
|
283
|
+
* Automatically sets the required \`X-Pathao-Merchant-Webhook-Integration-Secret\` header.
|
|
284
|
+
* Automatically responds with 202 for the \`webhook_integration\` handshake.
|
|
285
|
+
* For standard events, attaches the payload to \`req.pathaoWebhook\` and calls \`next()\`.
|
|
286
|
+
* On error, calls \`next(err)\`.
|
|
276
287
|
*
|
|
277
|
-
*
|
|
288
|
+
* \`\`\`typescript
|
|
278
289
|
* app.post(
|
|
279
290
|
* '/webhooks/pathao',
|
|
280
|
-
* express.
|
|
291
|
+
* express.json(),
|
|
281
292
|
* handler.expressMiddleware(),
|
|
293
|
+
* (req, res) => res.sendStatus(200) // You must send 200 for other events
|
|
282
294
|
* );
|
|
283
|
-
*
|
|
295
|
+
* \`\`\`
|
|
284
296
|
*/
|
|
285
297
|
expressMiddleware(): (req: {
|
|
286
|
-
body: Buffer | string;
|
|
287
|
-
headers: GenericHeaders;
|
|
298
|
+
body: Buffer | string | object;
|
|
288
299
|
pathaoWebhook?: PathaoWebhookPayload;
|
|
289
300
|
}, res: {
|
|
301
|
+
setHeader: (name: string, value: string) => void;
|
|
290
302
|
status: (code: number) => {
|
|
291
|
-
|
|
303
|
+
send: () => void;
|
|
292
304
|
};
|
|
293
305
|
}, next: (err?: unknown) => void) => void;
|
|
294
306
|
/**
|
|
295
|
-
* Returns
|
|
296
|
-
* `http.createServer`, etc.).
|
|
307
|
+
* Returns response instructions for any framework (Fastify, Hono, etc.).
|
|
297
308
|
*
|
|
298
|
-
* Never
|
|
299
|
-
*
|
|
309
|
+
* Never throws — always resolves with a \`WebhookResponseInstructions\` object
|
|
310
|
+
* that tells you which status code and headers to return, along with the payload/error.
|
|
300
311
|
*
|
|
301
|
-
*
|
|
312
|
+
* \`\`\`typescript
|
|
302
313
|
* const handle = handler.middleware();
|
|
303
|
-
* const
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
314
|
+
* const instructions = await handle(request.body);
|
|
315
|
+
*
|
|
316
|
+
* // Apply the required headers (the secret header)
|
|
317
|
+
* for (const [key, value] of Object.entries(instructions.headers)) {
|
|
318
|
+
* reply.header(key, value);
|
|
319
|
+
* }
|
|
320
|
+
*
|
|
321
|
+
* if (instructions.error) {
|
|
322
|
+
* return reply.status(instructions.statusCode).send({ error: instructions.error.message });
|
|
323
|
+
* }
|
|
324
|
+
*
|
|
325
|
+
* // If it was the handshake, we should just return 202 as instructed
|
|
326
|
+
* if (instructions.payload?.event === 'webhook_integration') {
|
|
327
|
+
* return reply.status(instructions.statusCode).send();
|
|
328
|
+
* }
|
|
329
|
+
*
|
|
330
|
+
* // Process your real webhook
|
|
331
|
+
* return reply.status(instructions.statusCode).send({ received: true });
|
|
332
|
+
* \`\`\`
|
|
307
333
|
*/
|
|
308
|
-
middleware(): (rawBody: Buffer | string
|
|
309
|
-
payload: PathaoWebhookPayload;
|
|
310
|
-
error: null;
|
|
311
|
-
} | {
|
|
312
|
-
payload: null;
|
|
313
|
-
error: PathaoWebhookError;
|
|
314
|
-
}>;
|
|
334
|
+
middleware(): (rawBody: Buffer | string | object) => Promise<WebhookResponseInstructions>;
|
|
315
335
|
}
|
|
316
336
|
|
|
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,
|
|
337
|
+
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 OrderReturnIdCreatedPayload, type OrderReturnInTransitPayload, type OrderReturnedPayload, type OrderReturnedToMerchantPayload, type OrderUpdatedPayload, type OrderWebhookPayload, PATHAO_SECRET_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, type PathaoWebhookPayload, type ReturnOrderWebhookPayload, type StoreCreatedPayload, type StoreUpdatedPayload, type StoreWebhookPayload, type WebhookEventPayloadMap, type WebhookIntegrationPayload, type WebhookResponseInstructions, constructEvent };
|
package/dist/webhooks.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var crypto = require('crypto');
|
|
4
3
|
var events = require('events');
|
|
5
4
|
|
|
6
5
|
// src/webhooks.ts
|
|
@@ -11,6 +10,7 @@ var PathaoWebhookError = class extends Error {
|
|
|
11
10
|
}
|
|
12
11
|
};
|
|
13
12
|
var PathaoWebhookEvent = /* @__PURE__ */ ((PathaoWebhookEvent2) => {
|
|
13
|
+
PathaoWebhookEvent2["WEBHOOK_INTEGRATION"] = "webhook_integration";
|
|
14
14
|
PathaoWebhookEvent2["ORDER_CREATED"] = "order.created";
|
|
15
15
|
PathaoWebhookEvent2["ORDER_UPDATED"] = "order.updated";
|
|
16
16
|
PathaoWebhookEvent2["ORDER_PICKUP_REQUESTED"] = "order.pickup-requested";
|
|
@@ -30,44 +30,29 @@ var PathaoWebhookEvent = /* @__PURE__ */ ((PathaoWebhookEvent2) => {
|
|
|
30
30
|
PathaoWebhookEvent2["ORDER_PAID"] = "order.paid";
|
|
31
31
|
PathaoWebhookEvent2["ORDER_PAID_RETURN"] = "order.paid-return";
|
|
32
32
|
PathaoWebhookEvent2["ORDER_EXCHANGED"] = "order.exchanged";
|
|
33
|
+
PathaoWebhookEvent2["ORDER_RETURN_ID_CREATED"] = "order.return-id-created";
|
|
34
|
+
PathaoWebhookEvent2["ORDER_RETURN_IN_TRANSIT"] = "order.return-in-transit";
|
|
35
|
+
PathaoWebhookEvent2["ORDER_RETURNED_TO_MERCHANT"] = "order.returned-to-merchant";
|
|
33
36
|
PathaoWebhookEvent2["STORE_CREATED"] = "store.created";
|
|
34
37
|
PathaoWebhookEvent2["STORE_UPDATED"] = "store.updated";
|
|
35
38
|
return PathaoWebhookEvent2;
|
|
36
39
|
})(PathaoWebhookEvent || {});
|
|
37
|
-
var
|
|
38
|
-
function
|
|
39
|
-
if (!signature || !webhookSecret) return false;
|
|
40
|
-
try {
|
|
41
|
-
const sigBuf = Buffer.from(signature, "utf8");
|
|
42
|
-
const secretBuf = Buffer.from(webhookSecret, "utf8");
|
|
43
|
-
if (sigBuf.length !== secretBuf.length) return false;
|
|
44
|
-
return crypto.timingSafeEqual(sigBuf, secretBuf);
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function constructEvent(rawBody, headers, webhookSecret) {
|
|
50
|
-
const signature = extractHeader(headers, PATHAO_SIGNATURE_HEADER);
|
|
51
|
-
if (!signature) {
|
|
52
|
-
throw new PathaoWebhookError(
|
|
53
|
-
`Missing ${PATHAO_SIGNATURE_HEADER} header. Ensure Pathao is sending the signature.`
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
if (!verifySignature(signature, webhookSecret)) {
|
|
57
|
-
throw new PathaoWebhookError(
|
|
58
|
-
"Invalid webhook signature. Check that your webhookSecret matches the Pathao integration secret."
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
const bodyStr = Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : rawBody;
|
|
40
|
+
var PATHAO_SECRET_HEADER = "x-pathao-merchant-webhook-integration-secret";
|
|
41
|
+
function constructEvent(rawBody) {
|
|
62
42
|
let parsed;
|
|
63
|
-
|
|
64
|
-
parsed =
|
|
65
|
-
}
|
|
66
|
-
|
|
43
|
+
if (typeof rawBody === "object" && !Buffer.isBuffer(rawBody)) {
|
|
44
|
+
parsed = rawBody;
|
|
45
|
+
} else {
|
|
46
|
+
const bodyStr = Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : rawBody;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(bodyStr);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new PathaoWebhookError("Webhook payload is not valid JSON.");
|
|
51
|
+
}
|
|
67
52
|
}
|
|
68
53
|
if (!parsed || typeof parsed !== "object" || !("event" in parsed)) {
|
|
69
54
|
throw new PathaoWebhookError(
|
|
70
|
-
"Webhook payload is missing the required
|
|
55
|
+
"Webhook payload is missing the required 'event' field."
|
|
71
56
|
);
|
|
72
57
|
}
|
|
73
58
|
return parsed;
|
|
@@ -86,23 +71,30 @@ var PathaoWebhookHandler = class extends events.EventEmitter {
|
|
|
86
71
|
on(event, listener) {
|
|
87
72
|
return super.on(event, listener);
|
|
88
73
|
}
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
once(event, listener) {
|
|
76
|
+
return super.once(event, listener);
|
|
77
|
+
}
|
|
89
78
|
// ─────────────────────────────────────────────────────────────────────────
|
|
90
79
|
/**
|
|
91
|
-
*
|
|
80
|
+
* Parse the body and dispatch the event.
|
|
92
81
|
*
|
|
93
|
-
* Throws
|
|
82
|
+
* Throws \`PathaoWebhookError\` on failure and also emits \`'error'\` so
|
|
94
83
|
* listeners can respond without a try/catch.
|
|
95
84
|
*
|
|
96
|
-
* @param rawBody Raw request body
|
|
97
|
-
* @param headers HTTP request headers
|
|
85
|
+
* @param rawBody Raw request body or matched json object
|
|
98
86
|
*/
|
|
99
|
-
process(rawBody
|
|
87
|
+
process(rawBody) {
|
|
100
88
|
let payload;
|
|
101
89
|
try {
|
|
102
|
-
payload = constructEvent(rawBody
|
|
90
|
+
payload = constructEvent(rawBody);
|
|
103
91
|
} catch (err) {
|
|
104
|
-
const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(
|
|
105
|
-
|
|
92
|
+
const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(
|
|
93
|
+
err instanceof Error ? err.message : "Unknown error"
|
|
94
|
+
);
|
|
95
|
+
if (this.listenerCount("error") > 0) {
|
|
96
|
+
this.emit("error", webhookErr);
|
|
97
|
+
}
|
|
106
98
|
throw webhookErr;
|
|
107
99
|
}
|
|
108
100
|
this.emit(payload.event, payload);
|
|
@@ -112,74 +104,95 @@ var PathaoWebhookHandler = class extends events.EventEmitter {
|
|
|
112
104
|
/**
|
|
113
105
|
* Returns an Express-compatible middleware function.
|
|
114
106
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* On
|
|
119
|
-
* On failure a `400` response is returned.
|
|
107
|
+
* Automatically sets the required \`X-Pathao-Merchant-Webhook-Integration-Secret\` header.
|
|
108
|
+
* Automatically responds with 202 for the \`webhook_integration\` handshake.
|
|
109
|
+
* For standard events, attaches the payload to \`req.pathaoWebhook\` and calls \`next()\`.
|
|
110
|
+
* On error, calls \`next(err)\`.
|
|
120
111
|
*
|
|
121
|
-
*
|
|
112
|
+
* \`\`\`typescript
|
|
122
113
|
* app.post(
|
|
123
114
|
* '/webhooks/pathao',
|
|
124
|
-
* express.
|
|
115
|
+
* express.json(),
|
|
125
116
|
* handler.expressMiddleware(),
|
|
117
|
+
* (req, res) => res.sendStatus(200) // You must send 200 for other events
|
|
126
118
|
* );
|
|
127
|
-
*
|
|
119
|
+
* \`\`\`
|
|
128
120
|
*/
|
|
129
121
|
expressMiddleware() {
|
|
130
122
|
return (req, res, next) => {
|
|
131
123
|
try {
|
|
132
|
-
|
|
124
|
+
res.setHeader(PATHAO_SECRET_HEADER, this.webhookSecret);
|
|
125
|
+
const payload = this.process(req.body);
|
|
126
|
+
req.pathaoWebhook = payload;
|
|
127
|
+
if (payload.event === "webhook_integration" /* WEBHOOK_INTEGRATION */) {
|
|
128
|
+
res.status(202).send();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
133
131
|
next();
|
|
134
132
|
} catch (err) {
|
|
135
|
-
|
|
136
|
-
error: err instanceof Error ? err.message : "Webhook processing failed"
|
|
137
|
-
});
|
|
133
|
+
next(err);
|
|
138
134
|
}
|
|
139
135
|
};
|
|
140
136
|
}
|
|
141
137
|
/**
|
|
142
|
-
* Returns
|
|
143
|
-
* `http.createServer`, etc.).
|
|
138
|
+
* Returns response instructions for any framework (Fastify, Hono, etc.).
|
|
144
139
|
*
|
|
145
|
-
* Never
|
|
146
|
-
*
|
|
140
|
+
* Never throws — always resolves with a \`WebhookResponseInstructions\` object
|
|
141
|
+
* that tells you which status code and headers to return, along with the payload/error.
|
|
147
142
|
*
|
|
148
|
-
*
|
|
143
|
+
* \`\`\`typescript
|
|
149
144
|
* const handle = handler.middleware();
|
|
150
|
-
* const
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
145
|
+
* const instructions = await handle(request.body);
|
|
146
|
+
*
|
|
147
|
+
* // Apply the required headers (the secret header)
|
|
148
|
+
* for (const [key, value] of Object.entries(instructions.headers)) {
|
|
149
|
+
* reply.header(key, value);
|
|
150
|
+
* }
|
|
151
|
+
*
|
|
152
|
+
* if (instructions.error) {
|
|
153
|
+
* return reply.status(instructions.statusCode).send({ error: instructions.error.message });
|
|
154
|
+
* }
|
|
155
|
+
*
|
|
156
|
+
* // If it was the handshake, we should just return 202 as instructed
|
|
157
|
+
* if (instructions.payload?.event === 'webhook_integration') {
|
|
158
|
+
* return reply.status(instructions.statusCode).send();
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* // Process your real webhook
|
|
162
|
+
* return reply.status(instructions.statusCode).send({ received: true });
|
|
163
|
+
* \`\`\`
|
|
154
164
|
*/
|
|
155
165
|
middleware() {
|
|
156
|
-
return async (rawBody
|
|
166
|
+
return async (rawBody) => {
|
|
167
|
+
const headers = { [PATHAO_SECRET_HEADER]: this.webhookSecret };
|
|
157
168
|
try {
|
|
158
|
-
const payload = this.process(rawBody
|
|
159
|
-
|
|
169
|
+
const payload = this.process(rawBody);
|
|
170
|
+
const isHandshake = payload.event === "webhook_integration" /* WEBHOOK_INTEGRATION */;
|
|
171
|
+
return {
|
|
172
|
+
statusCode: isHandshake ? 202 : 200,
|
|
173
|
+
headers,
|
|
174
|
+
payload,
|
|
175
|
+
error: null
|
|
176
|
+
};
|
|
160
177
|
} catch (err) {
|
|
161
178
|
const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(
|
|
162
179
|
err instanceof Error ? err.message : "Unknown error"
|
|
163
180
|
);
|
|
164
|
-
return {
|
|
181
|
+
return {
|
|
182
|
+
statusCode: 400,
|
|
183
|
+
headers,
|
|
184
|
+
payload: null,
|
|
185
|
+
error: webhookErr
|
|
186
|
+
};
|
|
165
187
|
}
|
|
166
188
|
};
|
|
167
189
|
}
|
|
168
190
|
};
|
|
169
|
-
function extractHeader(headers, name) {
|
|
170
|
-
const lower = name.toLowerCase();
|
|
171
|
-
const key = Object.keys(headers).find((k) => k.toLowerCase() === lower);
|
|
172
|
-
if (!key) return void 0;
|
|
173
|
-
const value = headers[key];
|
|
174
|
-
if (Array.isArray(value)) return value[0];
|
|
175
|
-
return value;
|
|
176
|
-
}
|
|
177
191
|
|
|
178
|
-
exports.
|
|
192
|
+
exports.PATHAO_SECRET_HEADER = PATHAO_SECRET_HEADER;
|
|
179
193
|
exports.PathaoWebhookError = PathaoWebhookError;
|
|
180
194
|
exports.PathaoWebhookEvent = PathaoWebhookEvent;
|
|
181
195
|
exports.PathaoWebhookHandler = PathaoWebhookHandler;
|
|
182
196
|
exports.constructEvent = constructEvent;
|
|
183
|
-
exports.verifySignature = verifySignature;
|
|
184
197
|
//# sourceMappingURL=webhooks.js.map
|
|
185
198
|
//# sourceMappingURL=webhooks.js.map
|