pathao-merchant-sdk 2.3.1 → 3.0.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.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared order lifecycle for Pathao webhook events and order-status slugs.
3
+ *
4
+ * Both describe the same parcel journey. `toLifecycleStatus` maps either one
5
+ * to a small stable vocabulary, so consumers don't each keep a map of spelling
6
+ * variants ("Pickup Requested", "pickup-requested", "order.pickup-requested").
7
+ * Zero dependencies: safe to import from the webhooks entry point.
8
+ */
9
+ type PathaoLifecycleStatus = 'created' | 'picked_up' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'partial' | 'on_hold'
10
+ /** On its way back. Not back yet: don't restock on this. */
11
+ | 'returning'
12
+ /** Back with the merchant. */
13
+ | 'returned' | 'cancelled';
14
+ /** Pathao's gateway allows 60 requests per rolling minute, with no Retry-After on its 429. */
15
+ declare const PATHAO_RATE_LIMIT_PER_MINUTE = 60;
16
+ /** getOrderStatus only finds orders for roughly this many days after creation. */
17
+ declare const PATHAO_STATUS_RETENTION_DAYS = 90;
18
+ /**
19
+ * Map a webhook event (`order.delivered`) or an order-status slug / display
20
+ * string (`Pickup Requested`, `pickup_requested`) to a lifecycle status.
21
+ * Returns `'unknown'` for anything that isn't a lifecycle change
22
+ * (`order.updated`, `order.paid`, store events) or that this SDK hasn't seen.
23
+ */
24
+ declare function toLifecycleStatus(value: string): PathaoLifecycleStatus | 'unknown';
25
+ /** Delivered, partially delivered, back with the merchant, or cancelled. */
26
+ declare function isFinalLifecycleStatus(status: PathaoLifecycleStatus | 'unknown'): boolean;
27
+
28
+ export { PATHAO_RATE_LIMIT_PER_MINUTE as P, PATHAO_STATUS_RETENTION_DAYS as a, type PathaoLifecycleStatus as b, isFinalLifecycleStatus as i, toLifecycleStatus as t };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared order lifecycle for Pathao webhook events and order-status slugs.
3
+ *
4
+ * Both describe the same parcel journey. `toLifecycleStatus` maps either one
5
+ * to a small stable vocabulary, so consumers don't each keep a map of spelling
6
+ * variants ("Pickup Requested", "pickup-requested", "order.pickup-requested").
7
+ * Zero dependencies: safe to import from the webhooks entry point.
8
+ */
9
+ type PathaoLifecycleStatus = 'created' | 'picked_up' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'partial' | 'on_hold'
10
+ /** On its way back. Not back yet: don't restock on this. */
11
+ | 'returning'
12
+ /** Back with the merchant. */
13
+ | 'returned' | 'cancelled';
14
+ /** Pathao's gateway allows 60 requests per rolling minute, with no Retry-After on its 429. */
15
+ declare const PATHAO_RATE_LIMIT_PER_MINUTE = 60;
16
+ /** getOrderStatus only finds orders for roughly this many days after creation. */
17
+ declare const PATHAO_STATUS_RETENTION_DAYS = 90;
18
+ /**
19
+ * Map a webhook event (`order.delivered`) or an order-status slug / display
20
+ * string (`Pickup Requested`, `pickup_requested`) to a lifecycle status.
21
+ * Returns `'unknown'` for anything that isn't a lifecycle change
22
+ * (`order.updated`, `order.paid`, store events) or that this SDK hasn't seen.
23
+ */
24
+ declare function toLifecycleStatus(value: string): PathaoLifecycleStatus | 'unknown';
25
+ /** Delivered, partially delivered, back with the merchant, or cancelled. */
26
+ declare function isFinalLifecycleStatus(status: PathaoLifecycleStatus | 'unknown'): boolean;
27
+
28
+ export { PATHAO_RATE_LIMIT_PER_MINUTE as P, PATHAO_STATUS_RETENTION_DAYS as a, type PathaoLifecycleStatus as b, isFinalLifecycleStatus as i, toLifecycleStatus as t };
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from 'events';
2
+ export { b as PathaoLifecycleStatus, i as isFinalLifecycleStatus, t as toLifecycleStatus } from './status-xIApOisZ.mjs';
2
3
 
3
4
  /**
4
5
  * Pathao Webhook Support
@@ -6,9 +7,12 @@ import { EventEmitter } from 'events';
6
7
  * Handles incoming webhook events from Pathao.
7
8
  *
8
9
  * 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.
10
+ * - Pathao does NOT sign incoming requests. Its webhook secret is one fixed value
11
+ * shared by all merchants and published in Pathao's own open-source plugin, so
12
+ * it proves nothing about who sent a request. Treat every payload as untrusted:
13
+ * re-fetch the order with \`PathaoApiService.getOrderStatus()\` before acting on it.
14
+ * - Pathao requires you to echo that secret in the
15
+ * \`X-Pathao-Merchant-Webhook-Integration-Secret\` header of EVERY response.
12
16
  * - This SDK automatically handles the \`webhook_integration\` handshake event, which
13
17
  * expects a 202 status code and the secret header.
14
18
  *
@@ -87,6 +91,12 @@ interface BaseWebhookPayload {
87
91
  /** Fields shared by all order-related events */
88
92
  interface OrderWebhookPayload extends BaseWebhookPayload {
89
93
  consignment_id: string;
94
+ /**
95
+ * Status label, when Pathao includes one (its own WooCommerce plugin reads
96
+ * this before falling back to the event name). Display-style, e.g.
97
+ * "Delivered"; pass it to \`toLifecycleStatus\`.
98
+ */
99
+ order_status?: string;
90
100
  merchant_order_id?: string;
91
101
  store_id: number;
92
102
  delivery_fee?: number;
@@ -172,6 +182,12 @@ interface OrderExchangedPayload extends OrderWebhookPayload {
172
182
  /** Shared fields for the three return-journey events */
173
183
  interface ReturnOrderWebhookPayload extends BaseWebhookPayload {
174
184
  consignment_id: string;
185
+ /**
186
+ * Status label, when Pathao includes one (its own WooCommerce plugin reads
187
+ * this before falling back to the event name). Display-style, e.g.
188
+ * "Delivered"; pass it to \`toLifecycleStatus\`.
189
+ */
190
+ order_status?: string;
175
191
  return_consignment_id: string;
176
192
  merchant_order_id?: string;
177
193
  store_id: number;
@@ -196,6 +212,15 @@ interface StoreUpdatedPayload extends StoreWebhookPayload {
196
212
  }
197
213
  /** Union of all possible webhook payloads */
198
214
  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;
215
+ /**
216
+ * A payload whose \`event\` isn't one of \`PathaoWebhookEvent\`. Emitted as
217
+ * \`'unknown'\`, never under its own name, so a sender can't fire reserved
218
+ * EventEmitter events such as \`'error'\` or \`'newListener'\`.
219
+ */
220
+ interface UnknownWebhookPayload {
221
+ event: string;
222
+ [key: string]: unknown;
223
+ }
199
224
  /** Maps each \`PathaoWebhookEvent\` to its specific payload type */
200
225
  interface WebhookEventPayloadMap {
201
226
  [PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;
@@ -262,11 +287,14 @@ declare class PathaoWebhookHandler extends EventEmitter {
262
287
  on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
263
288
  /** Fires for every successfully parsed event regardless of type. */
264
289
  on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
290
+ /** Fires for payloads whose \`event\` isn't a known \`PathaoWebhookEvent\`. */
291
+ on(event: 'unknown', listener: (payload: UnknownWebhookPayload) => void): this;
265
292
  /** Fires when parsing fails. */
266
293
  on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
267
294
  /** Listen once for a specific Pathao event. */
268
295
  once<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
269
296
  once(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
297
+ once(event: 'unknown', listener: (payload: UnknownWebhookPayload) => void): this;
270
298
  once(event: 'error', listener: (error: PathaoWebhookError) => void): this;
271
299
  /**
272
300
  * Parse the body and dispatch the event.
@@ -334,4 +362,4 @@ declare class PathaoWebhookHandler extends EventEmitter {
334
362
  middleware(): (rawBody: Buffer | string | object) => Promise<WebhookResponseInstructions>;
335
363
  }
336
364
 
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 };
365
+ 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 UnknownWebhookPayload, type WebhookEventPayloadMap, type WebhookIntegrationPayload, type WebhookResponseInstructions, constructEvent };
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from 'events';
2
+ export { b as PathaoLifecycleStatus, i as isFinalLifecycleStatus, t as toLifecycleStatus } from './status-xIApOisZ.js';
2
3
 
3
4
  /**
4
5
  * Pathao Webhook Support
@@ -6,9 +7,12 @@ import { EventEmitter } from 'events';
6
7
  * Handles incoming webhook events from Pathao.
7
8
  *
8
9
  * 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.
10
+ * - Pathao does NOT sign incoming requests. Its webhook secret is one fixed value
11
+ * shared by all merchants and published in Pathao's own open-source plugin, so
12
+ * it proves nothing about who sent a request. Treat every payload as untrusted:
13
+ * re-fetch the order with \`PathaoApiService.getOrderStatus()\` before acting on it.
14
+ * - Pathao requires you to echo that secret in the
15
+ * \`X-Pathao-Merchant-Webhook-Integration-Secret\` header of EVERY response.
12
16
  * - This SDK automatically handles the \`webhook_integration\` handshake event, which
13
17
  * expects a 202 status code and the secret header.
14
18
  *
@@ -87,6 +91,12 @@ interface BaseWebhookPayload {
87
91
  /** Fields shared by all order-related events */
88
92
  interface OrderWebhookPayload extends BaseWebhookPayload {
89
93
  consignment_id: string;
94
+ /**
95
+ * Status label, when Pathao includes one (its own WooCommerce plugin reads
96
+ * this before falling back to the event name). Display-style, e.g.
97
+ * "Delivered"; pass it to \`toLifecycleStatus\`.
98
+ */
99
+ order_status?: string;
90
100
  merchant_order_id?: string;
91
101
  store_id: number;
92
102
  delivery_fee?: number;
@@ -172,6 +182,12 @@ interface OrderExchangedPayload extends OrderWebhookPayload {
172
182
  /** Shared fields for the three return-journey events */
173
183
  interface ReturnOrderWebhookPayload extends BaseWebhookPayload {
174
184
  consignment_id: string;
185
+ /**
186
+ * Status label, when Pathao includes one (its own WooCommerce plugin reads
187
+ * this before falling back to the event name). Display-style, e.g.
188
+ * "Delivered"; pass it to \`toLifecycleStatus\`.
189
+ */
190
+ order_status?: string;
175
191
  return_consignment_id: string;
176
192
  merchant_order_id?: string;
177
193
  store_id: number;
@@ -196,6 +212,15 @@ interface StoreUpdatedPayload extends StoreWebhookPayload {
196
212
  }
197
213
  /** Union of all possible webhook payloads */
198
214
  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;
215
+ /**
216
+ * A payload whose \`event\` isn't one of \`PathaoWebhookEvent\`. Emitted as
217
+ * \`'unknown'\`, never under its own name, so a sender can't fire reserved
218
+ * EventEmitter events such as \`'error'\` or \`'newListener'\`.
219
+ */
220
+ interface UnknownWebhookPayload {
221
+ event: string;
222
+ [key: string]: unknown;
223
+ }
199
224
  /** Maps each \`PathaoWebhookEvent\` to its specific payload type */
200
225
  interface WebhookEventPayloadMap {
201
226
  [PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;
@@ -262,11 +287,14 @@ declare class PathaoWebhookHandler extends EventEmitter {
262
287
  on<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
263
288
  /** Fires for every successfully parsed event regardless of type. */
264
289
  on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
290
+ /** Fires for payloads whose \`event\` isn't a known \`PathaoWebhookEvent\`. */
291
+ on(event: 'unknown', listener: (payload: UnknownWebhookPayload) => void): this;
265
292
  /** Fires when parsing fails. */
266
293
  on(event: 'error', listener: (error: PathaoWebhookError) => void): this;
267
294
  /** Listen once for a specific Pathao event. */
268
295
  once<E extends PathaoWebhookEvent>(event: E, listener: (payload: WebhookEventPayloadMap[E]) => void): this;
269
296
  once(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;
297
+ once(event: 'unknown', listener: (payload: UnknownWebhookPayload) => void): this;
270
298
  once(event: 'error', listener: (error: PathaoWebhookError) => void): this;
271
299
  /**
272
300
  * Parse the body and dispatch the event.
@@ -334,4 +362,4 @@ declare class PathaoWebhookHandler extends EventEmitter {
334
362
  middleware(): (rawBody: Buffer | string | object) => Promise<WebhookResponseInstructions>;
335
363
  }
336
364
 
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 };
365
+ 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 UnknownWebhookPayload, type WebhookEventPayloadMap, type WebhookIntegrationPayload, type WebhookResponseInstructions, constructEvent };
package/dist/webhooks.js CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  var events = require('events');
4
4
 
5
+ // src/webhooks.ts
6
+
7
+ // src/status.ts
8
+ var LIFECYCLE = {
9
+ "pending": "created",
10
+ "created": "created",
11
+ "order-created": "created",
12
+ "pickup-requested": "created",
13
+ "assigned-for-pickup": "created",
14
+ "picked": "picked_up",
15
+ "pickup-failed": "on_hold",
16
+ "pickup-cancelled": "cancelled",
17
+ "at-the-sorting-hub": "in_transit",
18
+ "in-transit": "in_transit",
19
+ "received-at-last-mile-hub": "in_transit",
20
+ "assigned-for-delivery": "out_for_delivery",
21
+ "delivered": "delivered",
22
+ "partial-delivery": "partial",
23
+ "delivery-failed": "on_hold",
24
+ "on-hold": "on_hold",
25
+ // Marked for return; the parcel only comes back with returned-to-merchant.
26
+ "returned": "returning",
27
+ "return": "returning",
28
+ "return-id-created": "returning",
29
+ "return-in-transit": "returning",
30
+ "paid-return": "returning",
31
+ "exchanged": "returning",
32
+ "exchange": "returning",
33
+ "returned-to-merchant": "returned"
34
+ };
35
+ function toLifecycleStatus(value) {
36
+ const key = value.trim().toLowerCase().replace(/^order\./, "").replace(/[\s_]+/g, "-");
37
+ return LIFECYCLE[key] ?? "unknown";
38
+ }
39
+ function isFinalLifecycleStatus(status) {
40
+ return status === "delivered" || status === "partial" || status === "returned" || status === "cancelled";
41
+ }
42
+
5
43
  // src/webhooks.ts
6
44
  var PathaoWebhookError = class extends Error {
7
45
  constructor(message) {
@@ -37,6 +75,7 @@ var PathaoWebhookEvent = /* @__PURE__ */ ((PathaoWebhookEvent2) => {
37
75
  PathaoWebhookEvent2["STORE_UPDATED"] = "store.updated";
38
76
  return PathaoWebhookEvent2;
39
77
  })(PathaoWebhookEvent || {});
78
+ var KNOWN_EVENTS = new Set(Object.values(PathaoWebhookEvent));
40
79
  var PATHAO_SECRET_HEADER = "x-pathao-merchant-webhook-integration-secret";
41
80
  function constructEvent(rawBody) {
42
81
  let parsed;
@@ -50,7 +89,7 @@ function constructEvent(rawBody) {
50
89
  throw new PathaoWebhookError("Webhook payload is not valid JSON.");
51
90
  }
52
91
  }
53
- if (!parsed || typeof parsed !== "object" || !("event" in parsed)) {
92
+ if (!parsed || typeof parsed !== "object" || typeof parsed.event !== "string") {
54
93
  throw new PathaoWebhookError(
55
94
  "Webhook payload is missing the required 'event' field."
56
95
  );
@@ -97,7 +136,7 @@ var PathaoWebhookHandler = class extends events.EventEmitter {
97
136
  }
98
137
  throw webhookErr;
99
138
  }
100
- this.emit(payload.event, payload);
139
+ this.emit(KNOWN_EVENTS.has(payload.event) ? payload.event : "unknown", payload);
101
140
  this.emit("webhook", payload);
102
141
  return payload;
103
142
  }
@@ -194,5 +233,7 @@ exports.PathaoWebhookError = PathaoWebhookError;
194
233
  exports.PathaoWebhookEvent = PathaoWebhookEvent;
195
234
  exports.PathaoWebhookHandler = PathaoWebhookHandler;
196
235
  exports.constructEvent = constructEvent;
236
+ exports.isFinalLifecycleStatus = isFinalLifecycleStatus;
237
+ exports.toLifecycleStatus = toLifecycleStatus;
197
238
  //# sourceMappingURL=webhooks.js.map
198
239
  //# sourceMappingURL=webhooks.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/webhooks.ts"],"names":["PathaoWebhookEvent","EventEmitter"],"mappings":"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","file":"webhooks.js","sourcesContent":["/**\n * Pathao Webhook Support\n *\n * Handles incoming webhook events from Pathao.\n *\n * IMPORTANT INTEGRATION DETAILS:\n * - Pathao does NOT sign incoming requests.\n * - Instead, Pathao requires you to prove ownership by echoing your webhook secret\n * in the \\`X-Pathao-Merchant-Webhook-Integration-Secret\\` header of EVERY response.\n * - This SDK automatically handles the \\`webhook_integration\\` handshake event, which\n * expects a 202 status code and the secret header.\n *\n * @example — Express\n * \\`\\`\\`typescript\n * import express from 'express';\n * import {\n * PathaoWebhookHandler,\n * PathaoWebhookEvent,\n * } from 'pathao-merchant-sdk/webhooks';\n *\n * const app = express();\n * const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);\n *\n * handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });\n *\n * // Mount express.json() BEFORE the webhook middleware\n * app.post(\n * '/webhooks/pathao',\n * express.json(),\n * handler.expressMiddleware(),\n * (req, res) => {\n * // The middleware already sets the required secret header.\n * // You just need to return a 200 OK for standard events.\n * res.status(200).send('OK');\n * }\n * );\n * \\`\\`\\`\n */\n\nimport { EventEmitter } from 'events';\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nexport class PathaoWebhookError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PathaoWebhookError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Event types\n// ---------------------------------------------------------------------------\n\n/**\n * All webhook event types emitted by Pathao.\n * Values are the exact strings that appear in the \\`event\\` field of each payload.\n */\nexport enum PathaoWebhookEvent {\n WEBHOOK_INTEGRATION = 'webhook_integration',\n ORDER_CREATED = 'order.created',\n ORDER_UPDATED = 'order.updated',\n ORDER_PICKUP_REQUESTED = 'order.pickup-requested',\n ORDER_ASSIGNED_FOR_PICKUP = 'order.assigned-for-pickup',\n ORDER_PICKED = 'order.picked',\n ORDER_PICKUP_FAILED = 'order.pickup-failed',\n ORDER_PICKUP_CANCELLED = 'order.pickup-cancelled',\n ORDER_AT_THE_SORTING_HUB = 'order.at-the-sorting-hub',\n ORDER_IN_TRANSIT = 'order.in-transit',\n ORDER_RECEIVED_AT_LAST_MILE_HUB = 'order.received-at-last-mile-hub',\n ORDER_ASSIGNED_FOR_DELIVERY = 'order.assigned-for-delivery',\n ORDER_DELIVERED = 'order.delivered',\n ORDER_PARTIAL_DELIVERY = 'order.partial-delivery',\n ORDER_RETURNED = 'order.returned',\n ORDER_DELIVERY_FAILED = 'order.delivery-failed',\n ORDER_ON_HOLD = 'order.on-hold',\n ORDER_PAID = 'order.paid',\n ORDER_PAID_RETURN = 'order.paid-return',\n ORDER_EXCHANGED = 'order.exchanged',\n ORDER_RETURN_ID_CREATED = 'order.return-id-created',\n ORDER_RETURN_IN_TRANSIT = 'order.return-in-transit',\n ORDER_RETURNED_TO_MERCHANT = 'order.returned-to-merchant',\n STORE_CREATED = 'store.created',\n STORE_UPDATED = 'store.updated',\n}\n\n// ---------------------------------------------------------------------------\n// Payload types\n// ---------------------------------------------------------------------------\n\nexport interface WebhookIntegrationPayload {\n event: PathaoWebhookEvent.WEBHOOK_INTEGRATION;\n}\n\n/** Fields present on every normal webhook payload */\nexport interface BaseWebhookPayload {\n event: string;\n /** Format: MySQL datetime YYYY-MM-DD HH:MM:SS (no timezone indicator) */\n updated_at: string;\n /** Format: ISO 8601 timestamp */\n timestamp: string;\n}\n\n/** Fields shared by all order-related events */\nexport interface OrderWebhookPayload extends BaseWebhookPayload {\n consignment_id: string;\n merchant_order_id?: string;\n store_id: number;\n delivery_fee?: number;\n}\n\n/** Fields shared by all store-related events */\nexport interface StoreWebhookPayload extends BaseWebhookPayload {\n store_id: number;\n store_name: string;\n store_address: string;\n is_active: 0 | 1;\n}\n\n// Per-event payload types ────────────────────────────────────────────────────\n\nexport interface OrderCreatedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_CREATED;\n delivery_fee: number;\n}\n\nexport interface OrderUpdatedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_UPDATED;\n delivery_fee: number;\n}\n\nexport interface OrderPickupRequestedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKUP_REQUESTED;\n delivery_fee: number;\n}\n\nexport interface OrderAssignedForPickupPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP;\n}\n\nexport interface OrderPickedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKED;\n}\n\nexport interface OrderPickupFailedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKUP_FAILED;\n}\n\nexport interface OrderPickupCancelledPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKUP_CANCELLED;\n}\n\nexport interface OrderAtSortingHubPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB;\n}\n\nexport interface OrderInTransitPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_IN_TRANSIT;\n}\n\nexport interface OrderAtLastMileHubPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB;\n}\n\nexport interface OrderAssignedForDeliveryPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY;\n}\n\nexport interface OrderDeliveredPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_DELIVERED;\n collected_amount: number;\n}\n\nexport interface OrderPartialDeliveryPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY;\n collected_amount: number;\n reason?: string;\n}\n\nexport interface OrderReturnedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURNED;\n reason?: string;\n}\n\nexport interface OrderDeliveryFailedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_DELIVERY_FAILED;\n reason?: string;\n}\n\nexport interface OrderOnHoldPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_ON_HOLD;\n reason?: string;\n}\n\nexport interface OrderPaidPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PAID;\n invoice_id: string;\n}\n\nexport interface OrderPaidReturnPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PAID_RETURN;\n collected_amount: number;\n reason?: string;\n}\n\nexport interface OrderExchangedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_EXCHANGED;\n collected_amount: number;\n reason?: string;\n}\n\n/** Shared fields for the three return-journey events */\nexport interface ReturnOrderWebhookPayload extends BaseWebhookPayload {\n consignment_id: string;\n return_consignment_id: string;\n merchant_order_id?: string;\n store_id: number;\n collected_amount: number;\n return_type: 'return' | 'paid-return' | 'exchange' | 'partial-delivery';\n reason?: string;\n}\n\nexport interface OrderReturnIdCreatedPayload extends ReturnOrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURN_ID_CREATED;\n}\n\nexport interface OrderReturnInTransitPayload extends ReturnOrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURN_IN_TRANSIT;\n}\n\nexport interface OrderReturnedToMerchantPayload extends ReturnOrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURNED_TO_MERCHANT;\n}\n\nexport interface StoreCreatedPayload extends StoreWebhookPayload {\n event: PathaoWebhookEvent.STORE_CREATED;\n}\n\nexport interface StoreUpdatedPayload extends StoreWebhookPayload {\n event: PathaoWebhookEvent.STORE_UPDATED;\n}\n\n/** Union of all possible webhook payloads */\nexport type PathaoWebhookPayload =\n | WebhookIntegrationPayload\n | OrderCreatedPayload\n | OrderUpdatedPayload\n | OrderPickupRequestedPayload\n | OrderAssignedForPickupPayload\n | OrderPickedPayload\n | OrderPickupFailedPayload\n | OrderPickupCancelledPayload\n | OrderAtSortingHubPayload\n | OrderInTransitPayload\n | OrderAtLastMileHubPayload\n | OrderAssignedForDeliveryPayload\n | OrderDeliveredPayload\n | OrderPartialDeliveryPayload\n | OrderReturnedPayload\n | OrderDeliveryFailedPayload\n | OrderOnHoldPayload\n | OrderPaidPayload\n | OrderPaidReturnPayload\n | OrderExchangedPayload\n | OrderReturnIdCreatedPayload\n | OrderReturnInTransitPayload\n | OrderReturnedToMerchantPayload\n | StoreCreatedPayload\n | StoreUpdatedPayload;\n\n/** Maps each \\`PathaoWebhookEvent\\` to its specific payload type */\nexport interface WebhookEventPayloadMap {\n [PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;\n [PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;\n [PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;\n [PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;\n [PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP]: OrderAssignedForPickupPayload;\n [PathaoWebhookEvent.ORDER_PICKED]: OrderPickedPayload;\n [PathaoWebhookEvent.ORDER_PICKUP_FAILED]: OrderPickupFailedPayload;\n [PathaoWebhookEvent.ORDER_PICKUP_CANCELLED]: OrderPickupCancelledPayload;\n [PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB]: OrderAtSortingHubPayload;\n [PathaoWebhookEvent.ORDER_IN_TRANSIT]: OrderInTransitPayload;\n [PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB]: OrderAtLastMileHubPayload;\n [PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY]: OrderAssignedForDeliveryPayload;\n [PathaoWebhookEvent.ORDER_DELIVERED]: OrderDeliveredPayload;\n [PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY]: OrderPartialDeliveryPayload;\n [PathaoWebhookEvent.ORDER_RETURNED]: OrderReturnedPayload;\n [PathaoWebhookEvent.ORDER_DELIVERY_FAILED]: OrderDeliveryFailedPayload;\n [PathaoWebhookEvent.ORDER_ON_HOLD]: OrderOnHoldPayload;\n [PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;\n [PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;\n [PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;\n [PathaoWebhookEvent.ORDER_RETURN_ID_CREATED]: OrderReturnIdCreatedPayload;\n [PathaoWebhookEvent.ORDER_RETURN_IN_TRANSIT]: OrderReturnInTransitPayload;\n [PathaoWebhookEvent.ORDER_RETURNED_TO_MERCHANT]: OrderReturnedToMerchantPayload;\n [PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;\n [PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;\n}\n\n// ---------------------------------------------------------------------------\n// Core functions\n// ---------------------------------------------------------------------------\n\n/** Required response header used to authorize your endpoint with Pathao */\nexport const PATHAO_SECRET_HEADER =\n 'x-pathao-merchant-webhook-integration-secret';\n\n/**\n * Parses the raw body into a webhook payload.\n * Pathao does not sign inbound requests, so this just ensures it is valid JSON with an event field.\n *\n * Throws \\`PathaoWebhookError\\` on malformed JSON or missing event.\n *\n * @param rawBody Raw request body or parsed object\n */\nexport function constructEvent(\n rawBody: Buffer | string | object,\n): PathaoWebhookPayload {\n let parsed: unknown;\n if (typeof rawBody === 'object' && !Buffer.isBuffer(rawBody)) {\n parsed = rawBody;\n } else {\n const bodyStr = Buffer.isBuffer(rawBody)\n ? rawBody.toString('utf8')\n : rawBody;\n try {\n parsed = JSON.parse(bodyStr);\n } catch {\n throw new PathaoWebhookError('Webhook payload is not valid JSON.');\n }\n }\n\n if (!parsed || typeof parsed !== 'object' || !('event' in parsed)) {\n throw new PathaoWebhookError(\n \"Webhook payload is missing the required 'event' field.\",\n );\n }\n\n return parsed as PathaoWebhookPayload;\n}\n\n// ---------------------------------------------------------------------------\n// PathaoWebhookHandler\n// ---------------------------------------------------------------------------\n\nexport type WebhookResponseInstructions = {\n statusCode: number;\n headers: Record<string, string>;\n payload: PathaoWebhookPayload | null;\n error: PathaoWebhookError | null;\n};\n\n/**\n * Stateful webhook handler that parses payloads, provides response instructions,\n * and dispatches events to typed listeners.\n *\n * @example\n * \\`\\`\\`typescript\n * const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);\n *\n * handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {\n * // payload is fully typed as OrderDeliveredPayload\n * console.log(payload.consignment_id, payload.collected_amount);\n * });\n * \\`\\`\\`\n */\nexport class PathaoWebhookHandler extends EventEmitter {\n private readonly webhookSecret: string;\n\n constructor(webhookSecret: string) {\n super();\n if (!webhookSecret) {\n throw new PathaoWebhookError(\n 'webhookSecret is required to create a PathaoWebhookHandler.',\n );\n }\n this.webhookSecret = webhookSecret;\n }\n\n // Typed on() overloads ──────────────────────────────────────────────────\n\n /** Listen for a specific Pathao event with a fully-typed payload callback. */\n on<E extends PathaoWebhookEvent>(\n event: E,\n listener: (payload: WebhookEventPayloadMap[E]) => void,\n ): this;\n /** Fires for every successfully parsed event regardless of type. */\n on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;\n /** Fires when parsing fails. */\n on(event: 'error', listener: (error: PathaoWebhookError) => void): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string | symbol, listener: (...args: any[]) => void): this {\n return super.on(event, listener);\n }\n\n /** Listen once for a specific Pathao event. */\n once<E extends PathaoWebhookEvent>(\n event: E,\n listener: (payload: WebhookEventPayloadMap[E]) => void,\n ): this;\n once(\n event: 'webhook',\n listener: (payload: PathaoWebhookPayload) => void,\n ): this;\n once(event: 'error', listener: (error: PathaoWebhookError) => void): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n once(event: string | symbol, listener: (...args: any[]) => void): this {\n return super.once(event, listener);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Parse the body and dispatch the event.\n *\n * Throws \\`PathaoWebhookError\\` on failure and also emits \\`'error'\\` so\n * listeners can respond without a try/catch.\n *\n * @param rawBody Raw request body or matched json object\n */\n process(rawBody: Buffer | string | object): PathaoWebhookPayload {\n let payload: PathaoWebhookPayload;\n\n try {\n payload = constructEvent(rawBody);\n } catch (err) {\n const webhookErr =\n err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(\n err instanceof Error ? err.message : 'Unknown error',\n );\n if (this.listenerCount('error') > 0) {\n this.emit('error', webhookErr);\n }\n throw webhookErr;\n }\n\n this.emit(payload.event, payload);\n this.emit('webhook', payload);\n\n return payload;\n }\n\n /**\n * Returns an Express-compatible middleware function.\n *\n * Automatically sets the required \\`X-Pathao-Merchant-Webhook-Integration-Secret\\` header.\n * Automatically responds with 202 for the \\`webhook_integration\\` handshake.\n * For standard events, attaches the payload to \\`req.pathaoWebhook\\` and calls \\`next()\\`.\n * On error, calls \\`next(err)\\`.\n *\n * \\`\\`\\`typescript\n * app.post(\n * '/webhooks/pathao',\n * express.json(),\n * handler.expressMiddleware(),\n * (req, res) => res.sendStatus(200) // You must send 200 for other events\n * );\n * \\`\\`\\`\n */\n expressMiddleware() {\n return (\n req: {\n body: Buffer | string | object;\n pathaoWebhook?: PathaoWebhookPayload;\n },\n res: {\n setHeader: (name: string, value: string) => void;\n status: (code: number) => { send: () => void };\n },\n next: (err?: unknown) => void,\n ): void => {\n try {\n res.setHeader(PATHAO_SECRET_HEADER, this.webhookSecret);\n\n const payload = this.process(req.body);\n req.pathaoWebhook = payload;\n\n if (payload.event === PathaoWebhookEvent.WEBHOOK_INTEGRATION) {\n res.status(202).send();\n return;\n }\n\n next();\n } catch (err) {\n next(err);\n }\n };\n }\n\n /**\n * Returns response instructions for any framework (Fastify, Hono, etc.).\n *\n * Never throws — always resolves with a \\`WebhookResponseInstructions\\` object\n * that tells you which status code and headers to return, along with the payload/error.\n *\n * \\`\\`\\`typescript\n * const handle = handler.middleware();\n * const instructions = await handle(request.body);\n *\n * // Apply the required headers (the secret header)\n * for (const [key, value] of Object.entries(instructions.headers)) {\n * reply.header(key, value);\n * }\n *\n * if (instructions.error) {\n * return reply.status(instructions.statusCode).send({ error: instructions.error.message });\n * }\n *\n * // If it was the handshake, we should just return 202 as instructed\n * if (instructions.payload?.event === 'webhook_integration') {\n * return reply.status(instructions.statusCode).send();\n * }\n *\n * // Process your real webhook\n * return reply.status(instructions.statusCode).send({ received: true });\n * \\`\\`\\`\n */\n middleware() {\n return async (\n rawBody: Buffer | string | object,\n ): Promise<WebhookResponseInstructions> => {\n const headers = { [PATHAO_SECRET_HEADER]: this.webhookSecret };\n\n try {\n const payload = this.process(rawBody);\n const isHandshake =\n payload.event === PathaoWebhookEvent.WEBHOOK_INTEGRATION;\n return {\n statusCode: isHandshake ? 202 : 200,\n headers,\n payload,\n error: null,\n };\n } catch (err) {\n const webhookErr =\n err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(\n err instanceof Error ? err.message : 'Unknown error',\n );\n return {\n statusCode: 400,\n headers,\n payload: null,\n error: webhookErr,\n };\n }\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/status.ts","../src/webhooks.ts"],"names":["PathaoWebhookEvent","EventEmitter"],"mappings":"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","file":"webhooks.js","sourcesContent":["/**\n * Shared order lifecycle for Pathao webhook events and order-status slugs.\n *\n * Both describe the same parcel journey. `toLifecycleStatus` maps either one\n * to a small stable vocabulary, so consumers don't each keep a map of spelling\n * variants (\"Pickup Requested\", \"pickup-requested\", \"order.pickup-requested\").\n * Zero dependencies: safe to import from the webhooks entry point.\n */\n\nexport type PathaoLifecycleStatus =\n | 'created'\n | 'picked_up'\n | 'in_transit'\n | 'out_for_delivery'\n | 'delivered'\n | 'partial'\n | 'on_hold'\n /** On its way back. Not back yet: don't restock on this. */\n | 'returning'\n /** Back with the merchant. */\n | 'returned'\n | 'cancelled';\n\n/** Pathao's gateway allows 60 requests per rolling minute, with no Retry-After on its 429. */\nexport const PATHAO_RATE_LIMIT_PER_MINUTE = 60;\n\n/** getOrderStatus only finds orders for roughly this many days after creation. */\nexport const PATHAO_STATUS_RETENTION_DAYS = 90;\n\n// Keys are webhook event names without \"order.\", plus the kebab-cased status\n// labels Pathao uses elsewhere: order_status_slug values seen in production\n// (\"Pending\", \"In Transit\", \"Return\") and the labels in Pathao's own\n// WooCommerce plugin (\"Order_Created\", \"Return\", \"exchange\").\nconst LIFECYCLE: Readonly<Record<string, PathaoLifecycleStatus>> = {\n 'pending': 'created',\n 'created': 'created',\n 'order-created': 'created',\n 'pickup-requested': 'created',\n 'assigned-for-pickup': 'created',\n 'picked': 'picked_up',\n 'pickup-failed': 'on_hold',\n 'pickup-cancelled': 'cancelled',\n 'at-the-sorting-hub': 'in_transit',\n 'in-transit': 'in_transit',\n 'received-at-last-mile-hub': 'in_transit',\n 'assigned-for-delivery': 'out_for_delivery',\n 'delivered': 'delivered',\n 'partial-delivery': 'partial',\n 'delivery-failed': 'on_hold',\n 'on-hold': 'on_hold',\n // Marked for return; the parcel only comes back with returned-to-merchant.\n 'returned': 'returning',\n 'return': 'returning',\n 'return-id-created': 'returning',\n 'return-in-transit': 'returning',\n 'paid-return': 'returning',\n 'exchanged': 'returning',\n 'exchange': 'returning',\n 'returned-to-merchant': 'returned',\n};\n\n/**\n * Map a webhook event (`order.delivered`) or an order-status slug / display\n * string (`Pickup Requested`, `pickup_requested`) to a lifecycle status.\n * Returns `'unknown'` for anything that isn't a lifecycle change\n * (`order.updated`, `order.paid`, store events) or that this SDK hasn't seen.\n */\nexport function toLifecycleStatus(value: string): PathaoLifecycleStatus | 'unknown' {\n const key = value\n .trim()\n .toLowerCase()\n .replace(/^order\\./, '')\n .replace(/[\\s_]+/g, '-');\n return LIFECYCLE[key] ?? 'unknown';\n}\n\n/** Delivered, partially delivered, back with the merchant, or cancelled. */\nexport function isFinalLifecycleStatus(status: PathaoLifecycleStatus | 'unknown'): boolean {\n return status === 'delivered' || status === 'partial' || status === 'returned' || status === 'cancelled';\n}\n","/**\n * Pathao Webhook Support\n *\n * Handles incoming webhook events from Pathao.\n *\n * IMPORTANT INTEGRATION DETAILS:\n * - Pathao does NOT sign incoming requests. Its webhook secret is one fixed value\n * shared by all merchants and published in Pathao's own open-source plugin, so\n * it proves nothing about who sent a request. Treat every payload as untrusted:\n * re-fetch the order with \\`PathaoApiService.getOrderStatus()\\` before acting on it.\n * - Pathao requires you to echo that secret in the\n * \\`X-Pathao-Merchant-Webhook-Integration-Secret\\` header of EVERY response.\n * - This SDK automatically handles the \\`webhook_integration\\` handshake event, which\n * expects a 202 status code and the secret header.\n *\n * @example — Express\n * \\`\\`\\`typescript\n * import express from 'express';\n * import {\n * PathaoWebhookHandler,\n * PathaoWebhookEvent,\n * } from 'pathao-merchant-sdk/webhooks';\n *\n * const app = express();\n * const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);\n *\n * handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => { ... });\n *\n * // Mount express.json() BEFORE the webhook middleware\n * app.post(\n * '/webhooks/pathao',\n * express.json(),\n * handler.expressMiddleware(),\n * (req, res) => {\n * // The middleware already sets the required secret header.\n * // You just need to return a 200 OK for standard events.\n * res.status(200).send('OK');\n * }\n * );\n * \\`\\`\\`\n */\n\nimport { EventEmitter } from 'events';\n\nexport { toLifecycleStatus, isFinalLifecycleStatus } from './status';\nexport type { PathaoLifecycleStatus } from './status';\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nexport class PathaoWebhookError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PathaoWebhookError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Event types\n// ---------------------------------------------------------------------------\n\n/**\n * All webhook event types emitted by Pathao.\n * Values are the exact strings that appear in the \\`event\\` field of each payload.\n */\nexport enum PathaoWebhookEvent {\n WEBHOOK_INTEGRATION = 'webhook_integration',\n ORDER_CREATED = 'order.created',\n ORDER_UPDATED = 'order.updated',\n ORDER_PICKUP_REQUESTED = 'order.pickup-requested',\n ORDER_ASSIGNED_FOR_PICKUP = 'order.assigned-for-pickup',\n ORDER_PICKED = 'order.picked',\n ORDER_PICKUP_FAILED = 'order.pickup-failed',\n ORDER_PICKUP_CANCELLED = 'order.pickup-cancelled',\n ORDER_AT_THE_SORTING_HUB = 'order.at-the-sorting-hub',\n ORDER_IN_TRANSIT = 'order.in-transit',\n ORDER_RECEIVED_AT_LAST_MILE_HUB = 'order.received-at-last-mile-hub',\n ORDER_ASSIGNED_FOR_DELIVERY = 'order.assigned-for-delivery',\n ORDER_DELIVERED = 'order.delivered',\n ORDER_PARTIAL_DELIVERY = 'order.partial-delivery',\n ORDER_RETURNED = 'order.returned',\n ORDER_DELIVERY_FAILED = 'order.delivery-failed',\n ORDER_ON_HOLD = 'order.on-hold',\n ORDER_PAID = 'order.paid',\n ORDER_PAID_RETURN = 'order.paid-return',\n ORDER_EXCHANGED = 'order.exchanged',\n ORDER_RETURN_ID_CREATED = 'order.return-id-created',\n ORDER_RETURN_IN_TRANSIT = 'order.return-in-transit',\n ORDER_RETURNED_TO_MERCHANT = 'order.returned-to-merchant',\n STORE_CREATED = 'store.created',\n STORE_UPDATED = 'store.updated',\n}\n\n// ---------------------------------------------------------------------------\n// Payload types\n// ---------------------------------------------------------------------------\n\nexport interface WebhookIntegrationPayload {\n event: PathaoWebhookEvent.WEBHOOK_INTEGRATION;\n}\n\n/** Fields present on every normal webhook payload */\nexport interface BaseWebhookPayload {\n event: string;\n /** Format: MySQL datetime YYYY-MM-DD HH:MM:SS (no timezone indicator) */\n updated_at: string;\n /** Format: ISO 8601 timestamp */\n timestamp: string;\n}\n\n/** Fields shared by all order-related events */\nexport interface OrderWebhookPayload extends BaseWebhookPayload {\n consignment_id: string;\n /**\n * Status label, when Pathao includes one (its own WooCommerce plugin reads\n * this before falling back to the event name). Display-style, e.g.\n * \"Delivered\"; pass it to \\`toLifecycleStatus\\`.\n */\n order_status?: string;\n merchant_order_id?: string;\n store_id: number;\n delivery_fee?: number;\n}\n\n/** Fields shared by all store-related events */\nexport interface StoreWebhookPayload extends BaseWebhookPayload {\n store_id: number;\n store_name: string;\n store_address: string;\n is_active: 0 | 1;\n}\n\n// Per-event payload types ────────────────────────────────────────────────────\n\nexport interface OrderCreatedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_CREATED;\n delivery_fee: number;\n}\n\nexport interface OrderUpdatedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_UPDATED;\n delivery_fee: number;\n}\n\nexport interface OrderPickupRequestedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKUP_REQUESTED;\n delivery_fee: number;\n}\n\nexport interface OrderAssignedForPickupPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP;\n}\n\nexport interface OrderPickedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKED;\n}\n\nexport interface OrderPickupFailedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKUP_FAILED;\n}\n\nexport interface OrderPickupCancelledPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PICKUP_CANCELLED;\n}\n\nexport interface OrderAtSortingHubPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB;\n}\n\nexport interface OrderInTransitPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_IN_TRANSIT;\n}\n\nexport interface OrderAtLastMileHubPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB;\n}\n\nexport interface OrderAssignedForDeliveryPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY;\n}\n\nexport interface OrderDeliveredPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_DELIVERED;\n collected_amount: number;\n}\n\nexport interface OrderPartialDeliveryPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY;\n collected_amount: number;\n reason?: string;\n}\n\nexport interface OrderReturnedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURNED;\n reason?: string;\n}\n\nexport interface OrderDeliveryFailedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_DELIVERY_FAILED;\n reason?: string;\n}\n\nexport interface OrderOnHoldPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_ON_HOLD;\n reason?: string;\n}\n\nexport interface OrderPaidPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PAID;\n invoice_id: string;\n}\n\nexport interface OrderPaidReturnPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_PAID_RETURN;\n collected_amount: number;\n reason?: string;\n}\n\nexport interface OrderExchangedPayload extends OrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_EXCHANGED;\n collected_amount: number;\n reason?: string;\n}\n\n/** Shared fields for the three return-journey events */\nexport interface ReturnOrderWebhookPayload extends BaseWebhookPayload {\n consignment_id: string;\n /**\n * Status label, when Pathao includes one (its own WooCommerce plugin reads\n * this before falling back to the event name). Display-style, e.g.\n * \"Delivered\"; pass it to \\`toLifecycleStatus\\`.\n */\n order_status?: string;\n return_consignment_id: string;\n merchant_order_id?: string;\n store_id: number;\n collected_amount: number;\n return_type: 'return' | 'paid-return' | 'exchange' | 'partial-delivery';\n reason?: string;\n}\n\nexport interface OrderReturnIdCreatedPayload extends ReturnOrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURN_ID_CREATED;\n}\n\nexport interface OrderReturnInTransitPayload extends ReturnOrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURN_IN_TRANSIT;\n}\n\nexport interface OrderReturnedToMerchantPayload extends ReturnOrderWebhookPayload {\n event: PathaoWebhookEvent.ORDER_RETURNED_TO_MERCHANT;\n}\n\nexport interface StoreCreatedPayload extends StoreWebhookPayload {\n event: PathaoWebhookEvent.STORE_CREATED;\n}\n\nexport interface StoreUpdatedPayload extends StoreWebhookPayload {\n event: PathaoWebhookEvent.STORE_UPDATED;\n}\n\n/** Union of all possible webhook payloads */\nexport type PathaoWebhookPayload =\n | WebhookIntegrationPayload\n | OrderCreatedPayload\n | OrderUpdatedPayload\n | OrderPickupRequestedPayload\n | OrderAssignedForPickupPayload\n | OrderPickedPayload\n | OrderPickupFailedPayload\n | OrderPickupCancelledPayload\n | OrderAtSortingHubPayload\n | OrderInTransitPayload\n | OrderAtLastMileHubPayload\n | OrderAssignedForDeliveryPayload\n | OrderDeliveredPayload\n | OrderPartialDeliveryPayload\n | OrderReturnedPayload\n | OrderDeliveryFailedPayload\n | OrderOnHoldPayload\n | OrderPaidPayload\n | OrderPaidReturnPayload\n | OrderExchangedPayload\n | OrderReturnIdCreatedPayload\n | OrderReturnInTransitPayload\n | OrderReturnedToMerchantPayload\n | StoreCreatedPayload\n | StoreUpdatedPayload;\n\n/**\n * A payload whose \\`event\\` isn't one of \\`PathaoWebhookEvent\\`. Emitted as\n * \\`'unknown'\\`, never under its own name, so a sender can't fire reserved\n * EventEmitter events such as \\`'error'\\` or \\`'newListener'\\`.\n */\nexport interface UnknownWebhookPayload {\n event: string;\n [key: string]: unknown;\n}\n\nconst KNOWN_EVENTS: ReadonlySet<string> = new Set(Object.values(PathaoWebhookEvent));\n\n/** Maps each \\`PathaoWebhookEvent\\` to its specific payload type */\nexport interface WebhookEventPayloadMap {\n [PathaoWebhookEvent.WEBHOOK_INTEGRATION]: WebhookIntegrationPayload;\n [PathaoWebhookEvent.ORDER_CREATED]: OrderCreatedPayload;\n [PathaoWebhookEvent.ORDER_UPDATED]: OrderUpdatedPayload;\n [PathaoWebhookEvent.ORDER_PICKUP_REQUESTED]: OrderPickupRequestedPayload;\n [PathaoWebhookEvent.ORDER_ASSIGNED_FOR_PICKUP]: OrderAssignedForPickupPayload;\n [PathaoWebhookEvent.ORDER_PICKED]: OrderPickedPayload;\n [PathaoWebhookEvent.ORDER_PICKUP_FAILED]: OrderPickupFailedPayload;\n [PathaoWebhookEvent.ORDER_PICKUP_CANCELLED]: OrderPickupCancelledPayload;\n [PathaoWebhookEvent.ORDER_AT_THE_SORTING_HUB]: OrderAtSortingHubPayload;\n [PathaoWebhookEvent.ORDER_IN_TRANSIT]: OrderInTransitPayload;\n [PathaoWebhookEvent.ORDER_RECEIVED_AT_LAST_MILE_HUB]: OrderAtLastMileHubPayload;\n [PathaoWebhookEvent.ORDER_ASSIGNED_FOR_DELIVERY]: OrderAssignedForDeliveryPayload;\n [PathaoWebhookEvent.ORDER_DELIVERED]: OrderDeliveredPayload;\n [PathaoWebhookEvent.ORDER_PARTIAL_DELIVERY]: OrderPartialDeliveryPayload;\n [PathaoWebhookEvent.ORDER_RETURNED]: OrderReturnedPayload;\n [PathaoWebhookEvent.ORDER_DELIVERY_FAILED]: OrderDeliveryFailedPayload;\n [PathaoWebhookEvent.ORDER_ON_HOLD]: OrderOnHoldPayload;\n [PathaoWebhookEvent.ORDER_PAID]: OrderPaidPayload;\n [PathaoWebhookEvent.ORDER_PAID_RETURN]: OrderPaidReturnPayload;\n [PathaoWebhookEvent.ORDER_EXCHANGED]: OrderExchangedPayload;\n [PathaoWebhookEvent.ORDER_RETURN_ID_CREATED]: OrderReturnIdCreatedPayload;\n [PathaoWebhookEvent.ORDER_RETURN_IN_TRANSIT]: OrderReturnInTransitPayload;\n [PathaoWebhookEvent.ORDER_RETURNED_TO_MERCHANT]: OrderReturnedToMerchantPayload;\n [PathaoWebhookEvent.STORE_CREATED]: StoreCreatedPayload;\n [PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;\n}\n\n// ---------------------------------------------------------------------------\n// Core functions\n// ---------------------------------------------------------------------------\n\n/** Required response header used to authorize your endpoint with Pathao */\nexport const PATHAO_SECRET_HEADER =\n 'x-pathao-merchant-webhook-integration-secret';\n\n/**\n * Parses the raw body into a webhook payload.\n * Pathao does not sign inbound requests, so this just ensures it is valid JSON with an event field.\n *\n * Throws \\`PathaoWebhookError\\` on malformed JSON or missing event.\n *\n * @param rawBody Raw request body or parsed object\n */\nexport function constructEvent(\n rawBody: Buffer | string | object,\n): PathaoWebhookPayload {\n let parsed: unknown;\n if (typeof rawBody === 'object' && !Buffer.isBuffer(rawBody)) {\n parsed = rawBody;\n } else {\n const bodyStr = Buffer.isBuffer(rawBody)\n ? rawBody.toString('utf8')\n : rawBody;\n try {\n parsed = JSON.parse(bodyStr);\n } catch {\n throw new PathaoWebhookError('Webhook payload is not valid JSON.');\n }\n }\n\n if (\n !parsed ||\n typeof parsed !== 'object' ||\n typeof (parsed as { event?: unknown }).event !== 'string'\n ) {\n throw new PathaoWebhookError(\n \"Webhook payload is missing the required 'event' field.\",\n );\n }\n\n return parsed as PathaoWebhookPayload;\n}\n\n// ---------------------------------------------------------------------------\n// PathaoWebhookHandler\n// ---------------------------------------------------------------------------\n\nexport type WebhookResponseInstructions = {\n statusCode: number;\n headers: Record<string, string>;\n payload: PathaoWebhookPayload | null;\n error: PathaoWebhookError | null;\n};\n\n/**\n * Stateful webhook handler that parses payloads, provides response instructions,\n * and dispatches events to typed listeners.\n *\n * @example\n * \\`\\`\\`typescript\n * const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);\n *\n * handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {\n * // payload is fully typed as OrderDeliveredPayload\n * console.log(payload.consignment_id, payload.collected_amount);\n * });\n * \\`\\`\\`\n */\nexport class PathaoWebhookHandler extends EventEmitter {\n private readonly webhookSecret: string;\n\n constructor(webhookSecret: string) {\n super();\n if (!webhookSecret) {\n throw new PathaoWebhookError(\n 'webhookSecret is required to create a PathaoWebhookHandler.',\n );\n }\n this.webhookSecret = webhookSecret;\n }\n\n // Typed on() overloads ──────────────────────────────────────────────────\n\n /** Listen for a specific Pathao event with a fully-typed payload callback. */\n on<E extends PathaoWebhookEvent>(\n event: E,\n listener: (payload: WebhookEventPayloadMap[E]) => void,\n ): this;\n /** Fires for every successfully parsed event regardless of type. */\n on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;\n /** Fires for payloads whose \\`event\\` isn't a known \\`PathaoWebhookEvent\\`. */\n on(event: 'unknown', listener: (payload: UnknownWebhookPayload) => void): this;\n /** Fires when parsing fails. */\n on(event: 'error', listener: (error: PathaoWebhookError) => void): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on(event: string | symbol, listener: (...args: any[]) => void): this {\n return super.on(event, listener);\n }\n\n /** Listen once for a specific Pathao event. */\n once<E extends PathaoWebhookEvent>(\n event: E,\n listener: (payload: WebhookEventPayloadMap[E]) => void,\n ): this;\n once(\n event: 'webhook',\n listener: (payload: PathaoWebhookPayload) => void,\n ): this;\n once(\n event: 'unknown',\n listener: (payload: UnknownWebhookPayload) => void,\n ): this;\n once(event: 'error', listener: (error: PathaoWebhookError) => void): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n once(event: string | symbol, listener: (...args: any[]) => void): this {\n return super.once(event, listener);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Parse the body and dispatch the event.\n *\n * Throws \\`PathaoWebhookError\\` on failure and also emits \\`'error'\\` so\n * listeners can respond without a try/catch.\n *\n * @param rawBody Raw request body or matched json object\n */\n process(rawBody: Buffer | string | object): PathaoWebhookPayload {\n let payload: PathaoWebhookPayload;\n\n try {\n payload = constructEvent(rawBody);\n } catch (err) {\n const webhookErr =\n err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(\n err instanceof Error ? err.message : 'Unknown error',\n );\n if (this.listenerCount('error') > 0) {\n this.emit('error', webhookErr);\n }\n throw webhookErr;\n }\n\n // The body is unauthenticated, so its event name must never pick an\n // arbitrary EventEmitter event ('error', 'newListener', 'webhook', ...).\n this.emit(KNOWN_EVENTS.has(payload.event) ? payload.event : 'unknown', payload);\n this.emit('webhook', payload);\n\n return payload;\n }\n\n /**\n * Returns an Express-compatible middleware function.\n *\n * Automatically sets the required \\`X-Pathao-Merchant-Webhook-Integration-Secret\\` header.\n * Automatically responds with 202 for the \\`webhook_integration\\` handshake.\n * For standard events, attaches the payload to \\`req.pathaoWebhook\\` and calls \\`next()\\`.\n * On error, calls \\`next(err)\\`.\n *\n * \\`\\`\\`typescript\n * app.post(\n * '/webhooks/pathao',\n * express.json(),\n * handler.expressMiddleware(),\n * (req, res) => res.sendStatus(200) // You must send 200 for other events\n * );\n * \\`\\`\\`\n */\n expressMiddleware() {\n return (\n req: {\n body: Buffer | string | object;\n pathaoWebhook?: PathaoWebhookPayload;\n },\n res: {\n setHeader: (name: string, value: string) => void;\n status: (code: number) => { send: () => void };\n },\n next: (err?: unknown) => void,\n ): void => {\n try {\n res.setHeader(PATHAO_SECRET_HEADER, this.webhookSecret);\n\n const payload = this.process(req.body);\n req.pathaoWebhook = payload;\n\n if (payload.event === PathaoWebhookEvent.WEBHOOK_INTEGRATION) {\n res.status(202).send();\n return;\n }\n\n next();\n } catch (err) {\n next(err);\n }\n };\n }\n\n /**\n * Returns response instructions for any framework (Fastify, Hono, etc.).\n *\n * Never throws — always resolves with a \\`WebhookResponseInstructions\\` object\n * that tells you which status code and headers to return, along with the payload/error.\n *\n * \\`\\`\\`typescript\n * const handle = handler.middleware();\n * const instructions = await handle(request.body);\n *\n * // Apply the required headers (the secret header)\n * for (const [key, value] of Object.entries(instructions.headers)) {\n * reply.header(key, value);\n * }\n *\n * if (instructions.error) {\n * return reply.status(instructions.statusCode).send({ error: instructions.error.message });\n * }\n *\n * // If it was the handshake, we should just return 202 as instructed\n * if (instructions.payload?.event === 'webhook_integration') {\n * return reply.status(instructions.statusCode).send();\n * }\n *\n * // Process your real webhook\n * return reply.status(instructions.statusCode).send({ received: true });\n * \\`\\`\\`\n */\n middleware() {\n return async (\n rawBody: Buffer | string | object,\n ): Promise<WebhookResponseInstructions> => {\n const headers = { [PATHAO_SECRET_HEADER]: this.webhookSecret };\n\n try {\n const payload = this.process(rawBody);\n const isHandshake =\n payload.event === PathaoWebhookEvent.WEBHOOK_INTEGRATION;\n return {\n statusCode: isHandshake ? 202 : 200,\n headers,\n payload,\n error: null,\n };\n } catch (err) {\n const webhookErr =\n err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(\n err instanceof Error ? err.message : 'Unknown error',\n );\n return {\n statusCode: 400,\n headers,\n payload: null,\n error: webhookErr,\n };\n }\n };\n }\n}\n"]}
package/dist/webhooks.mjs CHANGED
@@ -1,5 +1,43 @@
1
1
  import { EventEmitter } from 'events';
2
2
 
3
+ // src/webhooks.ts
4
+
5
+ // src/status.ts
6
+ var LIFECYCLE = {
7
+ "pending": "created",
8
+ "created": "created",
9
+ "order-created": "created",
10
+ "pickup-requested": "created",
11
+ "assigned-for-pickup": "created",
12
+ "picked": "picked_up",
13
+ "pickup-failed": "on_hold",
14
+ "pickup-cancelled": "cancelled",
15
+ "at-the-sorting-hub": "in_transit",
16
+ "in-transit": "in_transit",
17
+ "received-at-last-mile-hub": "in_transit",
18
+ "assigned-for-delivery": "out_for_delivery",
19
+ "delivered": "delivered",
20
+ "partial-delivery": "partial",
21
+ "delivery-failed": "on_hold",
22
+ "on-hold": "on_hold",
23
+ // Marked for return; the parcel only comes back with returned-to-merchant.
24
+ "returned": "returning",
25
+ "return": "returning",
26
+ "return-id-created": "returning",
27
+ "return-in-transit": "returning",
28
+ "paid-return": "returning",
29
+ "exchanged": "returning",
30
+ "exchange": "returning",
31
+ "returned-to-merchant": "returned"
32
+ };
33
+ function toLifecycleStatus(value) {
34
+ const key = value.trim().toLowerCase().replace(/^order\./, "").replace(/[\s_]+/g, "-");
35
+ return LIFECYCLE[key] ?? "unknown";
36
+ }
37
+ function isFinalLifecycleStatus(status) {
38
+ return status === "delivered" || status === "partial" || status === "returned" || status === "cancelled";
39
+ }
40
+
3
41
  // src/webhooks.ts
4
42
  var PathaoWebhookError = class extends Error {
5
43
  constructor(message) {
@@ -35,6 +73,7 @@ var PathaoWebhookEvent = /* @__PURE__ */ ((PathaoWebhookEvent2) => {
35
73
  PathaoWebhookEvent2["STORE_UPDATED"] = "store.updated";
36
74
  return PathaoWebhookEvent2;
37
75
  })(PathaoWebhookEvent || {});
76
+ var KNOWN_EVENTS = new Set(Object.values(PathaoWebhookEvent));
38
77
  var PATHAO_SECRET_HEADER = "x-pathao-merchant-webhook-integration-secret";
39
78
  function constructEvent(rawBody) {
40
79
  let parsed;
@@ -48,7 +87,7 @@ function constructEvent(rawBody) {
48
87
  throw new PathaoWebhookError("Webhook payload is not valid JSON.");
49
88
  }
50
89
  }
51
- if (!parsed || typeof parsed !== "object" || !("event" in parsed)) {
90
+ if (!parsed || typeof parsed !== "object" || typeof parsed.event !== "string") {
52
91
  throw new PathaoWebhookError(
53
92
  "Webhook payload is missing the required 'event' field."
54
93
  );
@@ -95,7 +134,7 @@ var PathaoWebhookHandler = class extends EventEmitter {
95
134
  }
96
135
  throw webhookErr;
97
136
  }
98
- this.emit(payload.event, payload);
137
+ this.emit(KNOWN_EVENTS.has(payload.event) ? payload.event : "unknown", payload);
99
138
  this.emit("webhook", payload);
100
139
  return payload;
101
140
  }
@@ -187,6 +226,6 @@ var PathaoWebhookHandler = class extends EventEmitter {
187
226
  }
188
227
  };
189
228
 
190
- export { PATHAO_SECRET_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, constructEvent };
229
+ export { PATHAO_SECRET_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, constructEvent, isFinalLifecycleStatus, toLifecycleStatus };
191
230
  //# sourceMappingURL=webhooks.mjs.map
192
231
  //# sourceMappingURL=webhooks.mjs.map