pathao-merchant-sdk 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var events = require('events');
5
+
6
+ // src/webhooks.ts
7
+ var PathaoWebhookError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "PathaoWebhookError";
11
+ }
12
+ };
13
+ var PathaoWebhookEvent = /* @__PURE__ */ ((PathaoWebhookEvent2) => {
14
+ PathaoWebhookEvent2["ORDER_CREATED"] = "order.created";
15
+ PathaoWebhookEvent2["ORDER_UPDATED"] = "order.updated";
16
+ PathaoWebhookEvent2["ORDER_PICKUP_REQUESTED"] = "order.pickup-requested";
17
+ PathaoWebhookEvent2["ORDER_ASSIGNED_FOR_PICKUP"] = "order.assigned-for-pickup";
18
+ PathaoWebhookEvent2["ORDER_PICKED"] = "order.picked";
19
+ PathaoWebhookEvent2["ORDER_PICKUP_FAILED"] = "order.pickup-failed";
20
+ PathaoWebhookEvent2["ORDER_PICKUP_CANCELLED"] = "order.pickup-cancelled";
21
+ PathaoWebhookEvent2["ORDER_AT_THE_SORTING_HUB"] = "order.at-the-sorting-hub";
22
+ PathaoWebhookEvent2["ORDER_IN_TRANSIT"] = "order.in-transit";
23
+ PathaoWebhookEvent2["ORDER_RECEIVED_AT_LAST_MILE_HUB"] = "order.received-at-last-mile-hub";
24
+ PathaoWebhookEvent2["ORDER_ASSIGNED_FOR_DELIVERY"] = "order.assigned-for-delivery";
25
+ PathaoWebhookEvent2["ORDER_DELIVERED"] = "order.delivered";
26
+ PathaoWebhookEvent2["ORDER_PARTIAL_DELIVERY"] = "order.partial-delivery";
27
+ PathaoWebhookEvent2["ORDER_RETURNED"] = "order.returned";
28
+ PathaoWebhookEvent2["ORDER_DELIVERY_FAILED"] = "order.delivery-failed";
29
+ PathaoWebhookEvent2["ORDER_ON_HOLD"] = "order.on-hold";
30
+ PathaoWebhookEvent2["ORDER_PAID"] = "order.paid";
31
+ PathaoWebhookEvent2["ORDER_PAID_RETURN"] = "order.paid-return";
32
+ PathaoWebhookEvent2["ORDER_EXCHANGED"] = "order.exchanged";
33
+ PathaoWebhookEvent2["STORE_CREATED"] = "store.created";
34
+ PathaoWebhookEvent2["STORE_UPDATED"] = "store.updated";
35
+ return PathaoWebhookEvent2;
36
+ })(PathaoWebhookEvent || {});
37
+ var PATHAO_SIGNATURE_HEADER = "x-pathao-signature";
38
+ function verifySignature(signature, webhookSecret) {
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;
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(bodyStr);
65
+ } catch {
66
+ throw new PathaoWebhookError("Webhook payload is not valid JSON.");
67
+ }
68
+ if (!parsed || typeof parsed !== "object" || !("event" in parsed)) {
69
+ throw new PathaoWebhookError(
70
+ "Webhook payload is missing the required `event` field."
71
+ );
72
+ }
73
+ return parsed;
74
+ }
75
+ var PathaoWebhookHandler = class extends events.EventEmitter {
76
+ constructor(webhookSecret) {
77
+ super();
78
+ if (!webhookSecret) {
79
+ throw new PathaoWebhookError(
80
+ "webhookSecret is required to create a PathaoWebhookHandler."
81
+ );
82
+ }
83
+ this.webhookSecret = webhookSecret;
84
+ }
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ on(event, listener) {
87
+ return super.on(event, listener);
88
+ }
89
+ // ─────────────────────────────────────────────────────────────────────────
90
+ /**
91
+ * Verify the signature, parse the body, and dispatch the event.
92
+ *
93
+ * Throws `PathaoWebhookError` on failure and also emits `'error'` so
94
+ * listeners can respond without a try/catch.
95
+ *
96
+ * @param rawBody Raw request body — must NOT be pre-parsed
97
+ * @param headers HTTP request headers
98
+ */
99
+ process(rawBody, headers) {
100
+ let payload;
101
+ try {
102
+ payload = constructEvent(rawBody, headers, this.webhookSecret);
103
+ } catch (err) {
104
+ const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(err instanceof Error ? err.message : "Unknown error");
105
+ this.emit("error", webhookErr);
106
+ throw webhookErr;
107
+ }
108
+ this.emit(payload.event, payload);
109
+ this.emit("webhook", payload);
110
+ return payload;
111
+ }
112
+ /**
113
+ * Returns an Express-compatible middleware function.
114
+ *
115
+ * **Requires** `express.raw({ type: 'application/json' })` mounted on the
116
+ * same route before this middleware so the raw body Buffer is preserved.
117
+ *
118
+ * On success the parsed payload is attached to `req.pathaoWebhook`.
119
+ * On failure a `400` response is returned.
120
+ *
121
+ * ```typescript
122
+ * app.post(
123
+ * '/webhooks/pathao',
124
+ * express.raw({ type: 'application/json' }),
125
+ * handler.expressMiddleware(),
126
+ * );
127
+ * ```
128
+ */
129
+ expressMiddleware() {
130
+ return (req, res, next) => {
131
+ try {
132
+ req.pathaoWebhook = this.process(req.body, req.headers);
133
+ next();
134
+ } catch (err) {
135
+ res.status(400).json({
136
+ error: err instanceof Error ? err.message : "Webhook processing failed"
137
+ });
138
+ }
139
+ };
140
+ }
141
+ /**
142
+ * Returns a generic async handler for any framework (Fastify, Hono, plain
143
+ * `http.createServer`, etc.).
144
+ *
145
+ * Never rejects — always resolves with either `{ payload, error: null }` or
146
+ * `{ payload: null, error: PathaoWebhookError }`.
147
+ *
148
+ * ```typescript
149
+ * const handle = handler.middleware();
150
+ * const { payload, error } = await handle(rawBody, request.headers);
151
+ * if (error) { reply.status(400).send({ error: error.message }); return; }
152
+ * reply.send({ received: true });
153
+ * ```
154
+ */
155
+ middleware() {
156
+ return async (rawBody, headers) => {
157
+ try {
158
+ const payload = this.process(rawBody, headers);
159
+ return { payload, error: null };
160
+ } catch (err) {
161
+ const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(
162
+ err instanceof Error ? err.message : "Unknown error"
163
+ );
164
+ return { payload: null, error: webhookErr };
165
+ }
166
+ };
167
+ }
168
+ };
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
+
178
+ exports.PATHAO_SIGNATURE_HEADER = PATHAO_SIGNATURE_HEADER;
179
+ exports.PathaoWebhookError = PathaoWebhookError;
180
+ exports.PathaoWebhookEvent = PathaoWebhookEvent;
181
+ exports.PathaoWebhookHandler = PathaoWebhookHandler;
182
+ exports.constructEvent = constructEvent;
183
+ exports.verifySignature = verifySignature;
184
+ //# sourceMappingURL=webhooks.js.map
185
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webhooks.ts"],"names":["PathaoWebhookEvent","timingSafeEqual","EventEmitter"],"mappings":";;;;;;AA6DO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EAC5C,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AAAA,EACd;AACF;AAUO,IAAK,kBAAA,qBAAAA,mBAAAA,KAAL;AACL,EAAAA,oBAAA,eAAA,CAAA,GAAkC,eAAA;AAClC,EAAAA,oBAAA,eAAA,CAAA,GAAkC,eAAA;AAClC,EAAAA,oBAAA,wBAAA,CAAA,GAAkC,wBAAA;AAClC,EAAAA,oBAAA,2BAAA,CAAA,GAAkC,2BAAA;AAClC,EAAAA,oBAAA,cAAA,CAAA,GAAkC,cAAA;AAClC,EAAAA,oBAAA,qBAAA,CAAA,GAAkC,qBAAA;AAClC,EAAAA,oBAAA,wBAAA,CAAA,GAAkC,wBAAA;AAClC,EAAAA,oBAAA,0BAAA,CAAA,GAAkC,0BAAA;AAClC,EAAAA,oBAAA,kBAAA,CAAA,GAAkC,kBAAA;AAClC,EAAAA,oBAAA,iCAAA,CAAA,GAAkC,iCAAA;AAClC,EAAAA,oBAAA,6BAAA,CAAA,GAAkC,6BAAA;AAClC,EAAAA,oBAAA,iBAAA,CAAA,GAAkC,iBAAA;AAClC,EAAAA,oBAAA,wBAAA,CAAA,GAAkC,wBAAA;AAClC,EAAAA,oBAAA,gBAAA,CAAA,GAAkC,gBAAA;AAClC,EAAAA,oBAAA,uBAAA,CAAA,GAAkC,uBAAA;AAClC,EAAAA,oBAAA,eAAA,CAAA,GAAkC,eAAA;AAClC,EAAAA,oBAAA,YAAA,CAAA,GAAkC,YAAA;AAClC,EAAAA,oBAAA,mBAAA,CAAA,GAAkC,mBAAA;AAClC,EAAAA,oBAAA,iBAAA,CAAA,GAAkC,iBAAA;AAClC,EAAAA,oBAAA,eAAA,CAAA,GAAkC,eAAA;AAClC,EAAAA,oBAAA,eAAA,CAAA,GAAkC,eAAA;AArBxB,EAAA,OAAAA,mBAAAA;AAAA,CAAA,EAAA,kBAAA,IAAA,EAAA;AAgNL,IAAM,uBAAA,GAA0B;AAUhC,SAAS,eAAA,CACd,WACA,aAAA,EACS;AACT,EAAA,IAAI,CAAC,SAAA,IAAa,CAAC,aAAA,EAAe,OAAO,KAAA;AAEzC,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAY,MAAA,CAAO,IAAA,CAAK,SAAA,EAAe,MAAM,CAAA;AACnD,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,aAAA,EAAe,MAAM,CAAA;AACnD,IAAA,IAAI,MAAA,CAAO,MAAA,KAAW,SAAA,CAAU,MAAA,EAAQ,OAAO,KAAA;AAC/C,IAAA,OAAOC,sBAAA,CAAgB,QAAQ,SAAS,CAAA;AAAA,EAC1C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAWO,SAAS,cAAA,CACd,OAAA,EACA,OAAA,EACA,aAAA,EACsB;AACtB,EAAA,MAAM,SAAA,GAAY,aAAA,CAAc,OAAA,EAAS,uBAAuB,CAAA;AAEhE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAM,IAAI,kBAAA;AAAA,MACR,WAAW,uBAAuB,CAAA,gDAAA;AAAA,KAEpC;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,eAAA,CAAgB,SAAA,EAAW,aAAa,CAAA,EAAG;AAC9C,IAAA,MAAM,IAAI,kBAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,GAAU,OAAO,QAAA,CAAS,OAAO,IAAI,OAAA,CAAQ,QAAA,CAAS,MAAM,CAAA,GAAI,OAAA;AAEtE,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,mBAAmB,oCAAoC,CAAA;AAAA,EACnE;AAEA,EAAA,IACE,CAAC,MAAA,IACD,OAAO,WAAW,QAAA,IAClB,EAAE,WAAW,MAAA,CAAA,EACb;AACA,IAAA,MAAM,IAAI,kBAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAsBO,IAAM,oBAAA,GAAN,cAAmCC,mBAAA,CAAa;AAAA,EAGrD,YAAY,aAAA,EAAuB;AACjC,IAAA,KAAA,EAAM;AACN,IAAA,IAAI,CAAC,aAAA,EAAe;AAClB,MAAA,MAAM,IAAI,kBAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AAAA,EACvB;AAAA;AAAA,EAcA,EAAA,CAAG,OAAwB,QAAA,EAA0C;AACnE,IAAA,OAAO,KAAA,CAAM,EAAA,CAAG,KAAA,EAAO,QAAQ,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAA,CAAQ,SAA0B,OAAA,EAA+C;AAC/E,IAAA,IAAI,OAAA;AAEJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,cAAA,CAAe,OAAA,EAAS,OAAA,EAAS,IAAA,CAAK,aAAa,CAAA;AAAA,IAC/D,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,UAAA,GAAa,GAAA,YAAe,kBAAA,GAC9B,GAAA,GACA,IAAI,mBAAmB,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,eAAe,CAAA;AAC/E,MAAA,IAAA,CAAK,IAAA,CAAK,SAAS,UAAU,CAAA;AAC7B,MAAA,MAAM,UAAA;AAAA,IACR;AAEA,IAAA,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,OAAO,CAAA;AAChC,IAAA,IAAA,CAAK,IAAA,CAAK,WAAW,OAAO,CAAA;AAE5B,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,iBAAA,GAAoB;AAClB,IAAA,OAAO,CACL,GAAA,EAKA,GAAA,EACA,IAAA,KACS;AACT,MAAA,IAAI;AACF,QAAA,GAAA,CAAI,gBAAgB,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAM,IAAI,OAAO,CAAA;AACtD,QAAA,IAAA,EAAK;AAAA,MACP,SAAS,GAAA,EAAK;AACZ,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK;AAAA,UACnB,KAAA,EAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,SAC7C,CAAA;AAAA,MACH;AAAA,IACF,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,UAAA,GAAa;AACX,IAAA,OAAO,OACL,SACA,OAAA,KAIG;AACH,MAAA,IAAI;AACF,QAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS,OAAO,CAAA;AAC7C,QAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAK;AAAA,MAChC,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,UAAA,GAAa,GAAA,YAAe,kBAAA,GAC9B,GAAA,GACA,IAAI,kBAAA;AAAA,UACJ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,SACvC;AACF,QAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,KAAA,EAAO,UAAA,EAAW;AAAA,MAC5C;AAAA,IACF,CAAA;AAAA,EACF;AACF;AAOA,SAAS,aAAA,CACP,SACA,IAAA,EACoB;AACpB,EAAA,MAAM,KAAA,GAAQ,KAAK,WAAA,EAAY;AAC/B,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,KAAK,CAAA,CAAA,KAAK,CAAA,CAAE,WAAA,EAAY,KAAM,KAAK,CAAA;AACpE,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,MAAM,KAAA,GAAQ,QAAQ,GAAG,CAAA;AACzB,EAAA,IAAI,MAAM,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAO,MAAM,CAAC,CAAA;AACxC,EAAA,OAAO,KAAA;AACT","file":"webhooks.js","sourcesContent":["/**\n * Pathao Webhook Support\n *\n * Handles incoming webhook events from Pathao.\n *\n * IMPORTANT: The official Pathao API documentation does not describe a webhook\n * specification. This implementation is based on observed webhook behaviour and\n * community research. Treat it as best-effort until Pathao publishes official\n * webhook docs.\n *\n * Signature mechanism: Pathao sends the raw shared secret in the\n * `X-PATHAO-Signature` header. There is no HMAC — the header value IS the\n * secret. A constant-time comparison is used to prevent timing attacks.\n *\n * @example — framework-agnostic\n * ```typescript\n * import {\n * PathaoWebhookHandler,\n * PathaoWebhookEvent,\n * } from 'pathao-merchant-sdk/webhooks';\n *\n * const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);\n *\n * handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {\n * console.log(payload.consignment_id, payload.collected_amount);\n * });\n *\n * // In your HTTP server — raw body as Buffer/string required:\n * const event = handler.process(rawBody, req.headers);\n * res.status(200).json({ received: true });\n * ```\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.raw() BEFORE the webhook middleware\n * app.post(\n * '/webhooks/pathao',\n * express.raw({ type: 'application/json' }),\n * handler.expressMiddleware(),\n * );\n * ```\n */\n\nimport { timingSafeEqual } from 'crypto';\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 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 STORE_CREATED = 'store.created',\n STORE_UPDATED = 'store.updated',\n}\n\n// ---------------------------------------------------------------------------\n// Payload types\n// ---------------------------------------------------------------------------\n\n/** Fields present on every webhook payload */\nexport interface BaseWebhookPayload {\n /** Dot-notation event type — matches a `PathaoWebhookEvent` value */\n event: string;\n /** ISO timestamp of when the state change occurred */\n updated_at: string;\n /** ISO timestamp of when this webhook was dispatched */\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\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 | 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 | StoreCreatedPayload\n | StoreUpdatedPayload;\n\n/** Maps each `PathaoWebhookEvent` to its specific payload type */\nexport interface WebhookEventPayloadMap {\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.STORE_CREATED]: StoreCreatedPayload;\n [PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;\n}\n\n// ---------------------------------------------------------------------------\n// Core functions\n// ---------------------------------------------------------------------------\n\n/** Signature header name sent by Pathao on every webhook request */\nexport const PATHAO_SIGNATURE_HEADER = 'x-pathao-signature';\n\n/**\n * Verify the `X-PATHAO-Signature` header against the configured webhook secret.\n *\n * Uses a constant-time comparison to prevent timing attacks. The header value\n * is the raw shared secret — Pathao does not hash the signature.\n *\n * @returns `true` if valid, `false` if missing or mismatched.\n */\nexport function verifySignature(\n signature: string | undefined,\n webhookSecret: string,\n): boolean {\n if (!signature || !webhookSecret) return false;\n\n try {\n const sigBuf = Buffer.from(signature, 'utf8');\n const secretBuf = Buffer.from(webhookSecret, 'utf8');\n if (sigBuf.length !== secretBuf.length) return false;\n return timingSafeEqual(sigBuf, secretBuf);\n } catch {\n return false;\n }\n}\n\n/**\n * Verify the webhook signature and parse the raw body in one step.\n *\n * Throws `PathaoWebhookError` on signature failure or malformed JSON.\n *\n * @param rawBody Raw request body — do NOT pre-parse with `JSON.parse`\n * @param headers Request headers object\n * @param webhookSecret Your Pathao webhook integration secret\n */\nexport function constructEvent(\n rawBody: Buffer | string,\n headers: Record<string, string | string[] | undefined>,\n webhookSecret: string,\n): PathaoWebhookPayload {\n const signature = extractHeader(headers, PATHAO_SIGNATURE_HEADER);\n\n if (!signature) {\n throw new PathaoWebhookError(\n `Missing ${PATHAO_SIGNATURE_HEADER} header. ` +\n 'Ensure Pathao is sending the signature.',\n );\n }\n\n if (!verifySignature(signature, webhookSecret)) {\n throw new PathaoWebhookError(\n 'Invalid webhook signature. ' +\n 'Check that your webhookSecret matches the Pathao integration secret.',\n );\n }\n\n const bodyStr = Buffer.isBuffer(rawBody) ? rawBody.toString('utf8') : rawBody;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyStr);\n } catch {\n throw new PathaoWebhookError('Webhook payload is not valid JSON.');\n }\n\n if (\n !parsed ||\n typeof parsed !== 'object' ||\n !('event' in parsed)\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\ntype GenericHeaders = Record<string, string | string[] | undefined>;\n\n/**\n * Stateful webhook handler that verifies incoming requests, parses payloads,\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 verified event regardless of type. */\n on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;\n /** Fires when verification or 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 // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Verify the signature, 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 — must NOT be pre-parsed\n * @param headers HTTP request headers\n */\n process(rawBody: Buffer | string, headers: GenericHeaders): PathaoWebhookPayload {\n let payload: PathaoWebhookPayload;\n\n try {\n payload = constructEvent(rawBody, headers, this.webhookSecret);\n } catch (err) {\n const webhookErr = err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(err instanceof Error ? err.message : 'Unknown error');\n this.emit('error', webhookErr);\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 * **Requires** `express.raw({ type: 'application/json' })` mounted on the\n * same route before this middleware so the raw body Buffer is preserved.\n *\n * On success the parsed payload is attached to `req.pathaoWebhook`.\n * On failure a `400` response is returned.\n *\n * ```typescript\n * app.post(\n * '/webhooks/pathao',\n * express.raw({ type: 'application/json' }),\n * handler.expressMiddleware(),\n * );\n * ```\n */\n expressMiddleware() {\n return (\n req: {\n body: Buffer | string;\n headers: GenericHeaders;\n pathaoWebhook?: PathaoWebhookPayload;\n },\n res: { status: (code: number) => { json: (body: unknown) => void } },\n next: (err?: unknown) => void,\n ): void => {\n try {\n req.pathaoWebhook = this.process(req.body, req.headers);\n next();\n } catch (err) {\n res.status(400).json({\n error: err instanceof Error ? err.message : 'Webhook processing failed',\n });\n }\n };\n }\n\n /**\n * Returns a generic async handler for any framework (Fastify, Hono, plain\n * `http.createServer`, etc.).\n *\n * Never rejects — always resolves with either `{ payload, error: null }` or\n * `{ payload: null, error: PathaoWebhookError }`.\n *\n * ```typescript\n * const handle = handler.middleware();\n * const { payload, error } = await handle(rawBody, request.headers);\n * if (error) { reply.status(400).send({ error: error.message }); return; }\n * reply.send({ received: true });\n * ```\n */\n middleware() {\n return async (\n rawBody: Buffer | string,\n headers: GenericHeaders,\n ): Promise<\n | { payload: PathaoWebhookPayload; error: null }\n | { payload: null; error: PathaoWebhookError }\n > => {\n try {\n const payload = this.process(rawBody, headers);\n return { payload, error: null };\n } catch (err) {\n const webhookErr = err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(\n err instanceof Error ? err.message : 'Unknown error',\n );\n return { payload: null, error: webhookErr };\n }\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Extract a single-value header, case-insensitively */\nfunction extractHeader(\n headers: GenericHeaders,\n name: string,\n): string | undefined {\n const lower = name.toLowerCase();\n const key = Object.keys(headers).find(k => k.toLowerCase() === lower);\n if (!key) return undefined;\n const value = headers[key];\n if (Array.isArray(value)) return value[0];\n return value;\n}\n"]}
@@ -0,0 +1,178 @@
1
+ import { timingSafeEqual } from 'crypto';
2
+ import { EventEmitter } from 'events';
3
+
4
+ // src/webhooks.ts
5
+ var PathaoWebhookError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "PathaoWebhookError";
9
+ }
10
+ };
11
+ var PathaoWebhookEvent = /* @__PURE__ */ ((PathaoWebhookEvent2) => {
12
+ PathaoWebhookEvent2["ORDER_CREATED"] = "order.created";
13
+ PathaoWebhookEvent2["ORDER_UPDATED"] = "order.updated";
14
+ PathaoWebhookEvent2["ORDER_PICKUP_REQUESTED"] = "order.pickup-requested";
15
+ PathaoWebhookEvent2["ORDER_ASSIGNED_FOR_PICKUP"] = "order.assigned-for-pickup";
16
+ PathaoWebhookEvent2["ORDER_PICKED"] = "order.picked";
17
+ PathaoWebhookEvent2["ORDER_PICKUP_FAILED"] = "order.pickup-failed";
18
+ PathaoWebhookEvent2["ORDER_PICKUP_CANCELLED"] = "order.pickup-cancelled";
19
+ PathaoWebhookEvent2["ORDER_AT_THE_SORTING_HUB"] = "order.at-the-sorting-hub";
20
+ PathaoWebhookEvent2["ORDER_IN_TRANSIT"] = "order.in-transit";
21
+ PathaoWebhookEvent2["ORDER_RECEIVED_AT_LAST_MILE_HUB"] = "order.received-at-last-mile-hub";
22
+ PathaoWebhookEvent2["ORDER_ASSIGNED_FOR_DELIVERY"] = "order.assigned-for-delivery";
23
+ PathaoWebhookEvent2["ORDER_DELIVERED"] = "order.delivered";
24
+ PathaoWebhookEvent2["ORDER_PARTIAL_DELIVERY"] = "order.partial-delivery";
25
+ PathaoWebhookEvent2["ORDER_RETURNED"] = "order.returned";
26
+ PathaoWebhookEvent2["ORDER_DELIVERY_FAILED"] = "order.delivery-failed";
27
+ PathaoWebhookEvent2["ORDER_ON_HOLD"] = "order.on-hold";
28
+ PathaoWebhookEvent2["ORDER_PAID"] = "order.paid";
29
+ PathaoWebhookEvent2["ORDER_PAID_RETURN"] = "order.paid-return";
30
+ PathaoWebhookEvent2["ORDER_EXCHANGED"] = "order.exchanged";
31
+ PathaoWebhookEvent2["STORE_CREATED"] = "store.created";
32
+ PathaoWebhookEvent2["STORE_UPDATED"] = "store.updated";
33
+ return PathaoWebhookEvent2;
34
+ })(PathaoWebhookEvent || {});
35
+ var PATHAO_SIGNATURE_HEADER = "x-pathao-signature";
36
+ function verifySignature(signature, webhookSecret) {
37
+ if (!signature || !webhookSecret) return false;
38
+ try {
39
+ const sigBuf = Buffer.from(signature, "utf8");
40
+ const secretBuf = Buffer.from(webhookSecret, "utf8");
41
+ if (sigBuf.length !== secretBuf.length) return false;
42
+ return timingSafeEqual(sigBuf, secretBuf);
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ function constructEvent(rawBody, headers, webhookSecret) {
48
+ const signature = extractHeader(headers, PATHAO_SIGNATURE_HEADER);
49
+ if (!signature) {
50
+ throw new PathaoWebhookError(
51
+ `Missing ${PATHAO_SIGNATURE_HEADER} header. Ensure Pathao is sending the signature.`
52
+ );
53
+ }
54
+ if (!verifySignature(signature, webhookSecret)) {
55
+ throw new PathaoWebhookError(
56
+ "Invalid webhook signature. Check that your webhookSecret matches the Pathao integration secret."
57
+ );
58
+ }
59
+ const bodyStr = Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : rawBody;
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(bodyStr);
63
+ } catch {
64
+ throw new PathaoWebhookError("Webhook payload is not valid JSON.");
65
+ }
66
+ if (!parsed || typeof parsed !== "object" || !("event" in parsed)) {
67
+ throw new PathaoWebhookError(
68
+ "Webhook payload is missing the required `event` field."
69
+ );
70
+ }
71
+ return parsed;
72
+ }
73
+ var PathaoWebhookHandler = class extends EventEmitter {
74
+ constructor(webhookSecret) {
75
+ super();
76
+ if (!webhookSecret) {
77
+ throw new PathaoWebhookError(
78
+ "webhookSecret is required to create a PathaoWebhookHandler."
79
+ );
80
+ }
81
+ this.webhookSecret = webhookSecret;
82
+ }
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ on(event, listener) {
85
+ return super.on(event, listener);
86
+ }
87
+ // ─────────────────────────────────────────────────────────────────────────
88
+ /**
89
+ * Verify the signature, parse the body, and dispatch the event.
90
+ *
91
+ * Throws `PathaoWebhookError` on failure and also emits `'error'` so
92
+ * listeners can respond without a try/catch.
93
+ *
94
+ * @param rawBody Raw request body — must NOT be pre-parsed
95
+ * @param headers HTTP request headers
96
+ */
97
+ process(rawBody, headers) {
98
+ let payload;
99
+ try {
100
+ payload = constructEvent(rawBody, headers, this.webhookSecret);
101
+ } catch (err) {
102
+ const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(err instanceof Error ? err.message : "Unknown error");
103
+ this.emit("error", webhookErr);
104
+ throw webhookErr;
105
+ }
106
+ this.emit(payload.event, payload);
107
+ this.emit("webhook", payload);
108
+ return payload;
109
+ }
110
+ /**
111
+ * Returns an Express-compatible middleware function.
112
+ *
113
+ * **Requires** `express.raw({ type: 'application/json' })` mounted on the
114
+ * same route before this middleware so the raw body Buffer is preserved.
115
+ *
116
+ * On success the parsed payload is attached to `req.pathaoWebhook`.
117
+ * On failure a `400` response is returned.
118
+ *
119
+ * ```typescript
120
+ * app.post(
121
+ * '/webhooks/pathao',
122
+ * express.raw({ type: 'application/json' }),
123
+ * handler.expressMiddleware(),
124
+ * );
125
+ * ```
126
+ */
127
+ expressMiddleware() {
128
+ return (req, res, next) => {
129
+ try {
130
+ req.pathaoWebhook = this.process(req.body, req.headers);
131
+ next();
132
+ } catch (err) {
133
+ res.status(400).json({
134
+ error: err instanceof Error ? err.message : "Webhook processing failed"
135
+ });
136
+ }
137
+ };
138
+ }
139
+ /**
140
+ * Returns a generic async handler for any framework (Fastify, Hono, plain
141
+ * `http.createServer`, etc.).
142
+ *
143
+ * Never rejects — always resolves with either `{ payload, error: null }` or
144
+ * `{ payload: null, error: PathaoWebhookError }`.
145
+ *
146
+ * ```typescript
147
+ * const handle = handler.middleware();
148
+ * const { payload, error } = await handle(rawBody, request.headers);
149
+ * if (error) { reply.status(400).send({ error: error.message }); return; }
150
+ * reply.send({ received: true });
151
+ * ```
152
+ */
153
+ middleware() {
154
+ return async (rawBody, headers) => {
155
+ try {
156
+ const payload = this.process(rawBody, headers);
157
+ return { payload, error: null };
158
+ } catch (err) {
159
+ const webhookErr = err instanceof PathaoWebhookError ? err : new PathaoWebhookError(
160
+ err instanceof Error ? err.message : "Unknown error"
161
+ );
162
+ return { payload: null, error: webhookErr };
163
+ }
164
+ };
165
+ }
166
+ };
167
+ function extractHeader(headers, name) {
168
+ const lower = name.toLowerCase();
169
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === lower);
170
+ if (!key) return void 0;
171
+ const value = headers[key];
172
+ if (Array.isArray(value)) return value[0];
173
+ return value;
174
+ }
175
+
176
+ export { PATHAO_SIGNATURE_HEADER, PathaoWebhookError, PathaoWebhookEvent, PathaoWebhookHandler, constructEvent, verifySignature };
177
+ //# sourceMappingURL=webhooks.mjs.map
178
+ //# sourceMappingURL=webhooks.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webhooks.ts"],"names":["PathaoWebhookEvent"],"mappings":"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","file":"webhooks.mjs","sourcesContent":["/**\n * Pathao Webhook Support\n *\n * Handles incoming webhook events from Pathao.\n *\n * IMPORTANT: The official Pathao API documentation does not describe a webhook\n * specification. This implementation is based on observed webhook behaviour and\n * community research. Treat it as best-effort until Pathao publishes official\n * webhook docs.\n *\n * Signature mechanism: Pathao sends the raw shared secret in the\n * `X-PATHAO-Signature` header. There is no HMAC — the header value IS the\n * secret. A constant-time comparison is used to prevent timing attacks.\n *\n * @example — framework-agnostic\n * ```typescript\n * import {\n * PathaoWebhookHandler,\n * PathaoWebhookEvent,\n * } from 'pathao-merchant-sdk/webhooks';\n *\n * const handler = new PathaoWebhookHandler(process.env.PATHAO_WEBHOOK_SECRET!);\n *\n * handler.on(PathaoWebhookEvent.ORDER_DELIVERED, (payload) => {\n * console.log(payload.consignment_id, payload.collected_amount);\n * });\n *\n * // In your HTTP server — raw body as Buffer/string required:\n * const event = handler.process(rawBody, req.headers);\n * res.status(200).json({ received: true });\n * ```\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.raw() BEFORE the webhook middleware\n * app.post(\n * '/webhooks/pathao',\n * express.raw({ type: 'application/json' }),\n * handler.expressMiddleware(),\n * );\n * ```\n */\n\nimport { timingSafeEqual } from 'crypto';\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 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 STORE_CREATED = 'store.created',\n STORE_UPDATED = 'store.updated',\n}\n\n// ---------------------------------------------------------------------------\n// Payload types\n// ---------------------------------------------------------------------------\n\n/** Fields present on every webhook payload */\nexport interface BaseWebhookPayload {\n /** Dot-notation event type — matches a `PathaoWebhookEvent` value */\n event: string;\n /** ISO timestamp of when the state change occurred */\n updated_at: string;\n /** ISO timestamp of when this webhook was dispatched */\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\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 | 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 | StoreCreatedPayload\n | StoreUpdatedPayload;\n\n/** Maps each `PathaoWebhookEvent` to its specific payload type */\nexport interface WebhookEventPayloadMap {\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.STORE_CREATED]: StoreCreatedPayload;\n [PathaoWebhookEvent.STORE_UPDATED]: StoreUpdatedPayload;\n}\n\n// ---------------------------------------------------------------------------\n// Core functions\n// ---------------------------------------------------------------------------\n\n/** Signature header name sent by Pathao on every webhook request */\nexport const PATHAO_SIGNATURE_HEADER = 'x-pathao-signature';\n\n/**\n * Verify the `X-PATHAO-Signature` header against the configured webhook secret.\n *\n * Uses a constant-time comparison to prevent timing attacks. The header value\n * is the raw shared secret — Pathao does not hash the signature.\n *\n * @returns `true` if valid, `false` if missing or mismatched.\n */\nexport function verifySignature(\n signature: string | undefined,\n webhookSecret: string,\n): boolean {\n if (!signature || !webhookSecret) return false;\n\n try {\n const sigBuf = Buffer.from(signature, 'utf8');\n const secretBuf = Buffer.from(webhookSecret, 'utf8');\n if (sigBuf.length !== secretBuf.length) return false;\n return timingSafeEqual(sigBuf, secretBuf);\n } catch {\n return false;\n }\n}\n\n/**\n * Verify the webhook signature and parse the raw body in one step.\n *\n * Throws `PathaoWebhookError` on signature failure or malformed JSON.\n *\n * @param rawBody Raw request body — do NOT pre-parse with `JSON.parse`\n * @param headers Request headers object\n * @param webhookSecret Your Pathao webhook integration secret\n */\nexport function constructEvent(\n rawBody: Buffer | string,\n headers: Record<string, string | string[] | undefined>,\n webhookSecret: string,\n): PathaoWebhookPayload {\n const signature = extractHeader(headers, PATHAO_SIGNATURE_HEADER);\n\n if (!signature) {\n throw new PathaoWebhookError(\n `Missing ${PATHAO_SIGNATURE_HEADER} header. ` +\n 'Ensure Pathao is sending the signature.',\n );\n }\n\n if (!verifySignature(signature, webhookSecret)) {\n throw new PathaoWebhookError(\n 'Invalid webhook signature. ' +\n 'Check that your webhookSecret matches the Pathao integration secret.',\n );\n }\n\n const bodyStr = Buffer.isBuffer(rawBody) ? rawBody.toString('utf8') : rawBody;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyStr);\n } catch {\n throw new PathaoWebhookError('Webhook payload is not valid JSON.');\n }\n\n if (\n !parsed ||\n typeof parsed !== 'object' ||\n !('event' in parsed)\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\ntype GenericHeaders = Record<string, string | string[] | undefined>;\n\n/**\n * Stateful webhook handler that verifies incoming requests, parses payloads,\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 verified event regardless of type. */\n on(event: 'webhook', listener: (payload: PathaoWebhookPayload) => void): this;\n /** Fires when verification or 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 // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Verify the signature, 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 — must NOT be pre-parsed\n * @param headers HTTP request headers\n */\n process(rawBody: Buffer | string, headers: GenericHeaders): PathaoWebhookPayload {\n let payload: PathaoWebhookPayload;\n\n try {\n payload = constructEvent(rawBody, headers, this.webhookSecret);\n } catch (err) {\n const webhookErr = err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(err instanceof Error ? err.message : 'Unknown error');\n this.emit('error', webhookErr);\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 * **Requires** `express.raw({ type: 'application/json' })` mounted on the\n * same route before this middleware so the raw body Buffer is preserved.\n *\n * On success the parsed payload is attached to `req.pathaoWebhook`.\n * On failure a `400` response is returned.\n *\n * ```typescript\n * app.post(\n * '/webhooks/pathao',\n * express.raw({ type: 'application/json' }),\n * handler.expressMiddleware(),\n * );\n * ```\n */\n expressMiddleware() {\n return (\n req: {\n body: Buffer | string;\n headers: GenericHeaders;\n pathaoWebhook?: PathaoWebhookPayload;\n },\n res: { status: (code: number) => { json: (body: unknown) => void } },\n next: (err?: unknown) => void,\n ): void => {\n try {\n req.pathaoWebhook = this.process(req.body, req.headers);\n next();\n } catch (err) {\n res.status(400).json({\n error: err instanceof Error ? err.message : 'Webhook processing failed',\n });\n }\n };\n }\n\n /**\n * Returns a generic async handler for any framework (Fastify, Hono, plain\n * `http.createServer`, etc.).\n *\n * Never rejects — always resolves with either `{ payload, error: null }` or\n * `{ payload: null, error: PathaoWebhookError }`.\n *\n * ```typescript\n * const handle = handler.middleware();\n * const { payload, error } = await handle(rawBody, request.headers);\n * if (error) { reply.status(400).send({ error: error.message }); return; }\n * reply.send({ received: true });\n * ```\n */\n middleware() {\n return async (\n rawBody: Buffer | string,\n headers: GenericHeaders,\n ): Promise<\n | { payload: PathaoWebhookPayload; error: null }\n | { payload: null; error: PathaoWebhookError }\n > => {\n try {\n const payload = this.process(rawBody, headers);\n return { payload, error: null };\n } catch (err) {\n const webhookErr = err instanceof PathaoWebhookError\n ? err\n : new PathaoWebhookError(\n err instanceof Error ? err.message : 'Unknown error',\n );\n return { payload: null, error: webhookErr };\n }\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Extract a single-value header, case-insensitively */\nfunction extractHeader(\n headers: GenericHeaders,\n name: string,\n): string | undefined {\n const lower = name.toLowerCase();\n const key = Object.keys(headers).find(k => k.toLowerCase() === lower);\n if (!key) return undefined;\n const value = headers[key];\n if (Array.isArray(value)) return value[0];\n return value;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pathao-merchant-sdk",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Unofficial Pathao Merchant API SDK for Node.js and TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,6 +10,11 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.mjs",
12
12
  "require": "./dist/index.js"
13
+ },
14
+ "./webhooks": {
15
+ "types": "./dist/webhooks.d.ts",
16
+ "import": "./dist/webhooks.mjs",
17
+ "require": "./dist/webhooks.js"
13
18
  }
14
19
  },
15
20
  "sideEffects": false,
@@ -46,7 +51,8 @@
46
51
  "bangladesh",
47
52
  "shipping",
48
53
  "logistics",
49
- "ecommerce"
54
+ "ecommerce",
55
+ "webhook"
50
56
  ],
51
57
  "author": "Sifat Jasim <sifatjasim@gmail.com>",
52
58
  "license": "MIT",