@delopay/sdk 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -167,24 +167,26 @@ const gateways = await delopay.shops.gateways.list(merchantId, shop.shop_id);
167
167
 
168
168
  ### Webhook verification
169
169
 
170
+ Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body and delivers the hex-encoded digest in the `X-Webhook-Signature-512` header. Use `express.raw()` (not `express.json()`) so the bytes reach the verifier unchanged.
171
+
170
172
  ```typescript
173
+ import express from 'express';
171
174
  import { Delopay } from '@delopay/sdk';
172
175
 
173
- // In your Express handler:
174
- app.post('/webhooks/delopay', express.raw({ type: 'application/json' }), (req, res) => {
175
- const signature = req.headers['delopay-signature'] as string;
176
+ app.post('/webhooks/delopay', express.raw({ type: 'application/json' }), async (req, res) => {
177
+ const signature = req.header('x-webhook-signature-512') ?? '';
176
178
  const secret = process.env.DELOPAY_WEBHOOK_SECRET!;
177
179
 
178
180
  let event;
179
181
  try {
180
- event = Delopay.webhooks.verify(req.body.toString(), signature, secret);
181
- } catch (err) {
182
+ event = await Delopay.webhooks.verify(req.body, signature, secret);
183
+ } catch {
182
184
  return res.status(400).send('Invalid signature');
183
185
  }
184
186
 
185
187
  switch (event.type) {
186
188
  case 'payment_succeeded':
187
- // fulfill order
189
+ // fulfil order
188
190
  break;
189
191
  case 'payment_failed':
190
192
  // notify customer
@@ -345,14 +345,20 @@ var Connectors = class {
345
345
  async verify(params) {
346
346
  return this.request("POST", "/account/connectors/verify", { body: params });
347
347
  }
348
- /** Register a webhook for a connector. `POST /account/{merchantId}/connectors/webhooks/{connectorId}` */
349
- async registerWebhook(merchantId, connectorId) {
350
- return this.request(
351
- "POST",
352
- `/account/${encodeURIComponent(merchantId)}/connectors/webhooks/${encodeURIComponent(connectorId)}`
353
- );
348
+ /**
349
+ * Register a webhook for a connector.
350
+ * `POST /account/{merchantId}/connectors/webhooks/{connectorId}`
351
+ *
352
+ * @param params - Optional event scope. Defaults to `{ event_type: 'all_events' }`
353
+ * on the backend when omitted; pass `{ event_type: { specific_event: '…' } }`
354
+ * to scope to a single event.
355
+ */
356
+ async registerWebhook(merchantId, connectorId, params) {
357
+ const path = `/account/${encodeURIComponent(merchantId)}/connectors/webhooks/${encodeURIComponent(connectorId)}`;
358
+ if (params === void 0) return this.request("POST", path);
359
+ return this.request("POST", path, { body: params });
354
360
  }
355
- /** Get a webhook for a connector. `GET /account/{merchantId}/connectors/webhooks/{connectorId}` */
361
+ /** Get registered webhooks for a connector. `GET /account/{merchantId}/connectors/webhooks/{connectorId}` */
356
362
  async getWebhook(merchantId, connectorId) {
357
363
  return this.request(
358
364
  "GET",
@@ -941,15 +947,34 @@ var Payments = class {
941
947
  * Retrieve a payment by its ID.
942
948
  *
943
949
  * @param paymentId - The unique payment intent ID.
950
+ * @param options - Optional query flags. `force_sync` asks the backend to
951
+ * reconcile state with the connector before returning (used to recover a
952
+ * stuck intent when a webhook was lost). `all_keys_required` lifts the
953
+ * backend's `should_call_connector` gate so a `requires_payment_method`
954
+ * intent can still trigger a sync — without it the backend short-circuits
955
+ * and returns the local snapshot. Both flags are JWT-authenticated dashboard
956
+ * helpers; API-key callers can use them too where the backend allows.
944
957
  * @returns The payment intent.
945
958
  *
946
959
  * @example
947
960
  * ```typescript
948
961
  * const payment = await delopay.payments.retrieve('pay_abc123');
962
+ * const synced = await delopay.payments.retrieve('pay_abc123', {
963
+ * force_sync: true,
964
+ * all_keys_required: true,
965
+ * });
949
966
  * ```
950
967
  */
951
- async retrieve(paymentId) {
952
- return this.request("GET", `/payments/${encodeURIComponent(paymentId)}`);
968
+ async retrieve(paymentId, options) {
969
+ const path = `/payments/${encodeURIComponent(paymentId)}`;
970
+ if (options === void 0) return this.request("GET", path);
971
+ const query = {};
972
+ if (options.force_sync !== void 0) query["force_sync"] = options.force_sync;
973
+ if (options.all_keys_required !== void 0) {
974
+ query["all_keys_required"] = options.all_keys_required;
975
+ }
976
+ if (Object.keys(query).length === 0) return this.request("GET", path);
977
+ return this.request("GET", path, { query });
953
978
  }
954
979
  /**
955
980
  * Update an existing payment intent before it is confirmed.
@@ -1350,29 +1375,41 @@ var Projects = class {
1350
1375
  * Retrieve a project by its ID.
1351
1376
  *
1352
1377
  * @param projectId - The unique project ID.
1378
+ * @param merchantId - Optional merchant scope. When provided, sent as
1379
+ * `?merchant_id=…` — required by dashboards that authenticate with a JWT
1380
+ * spanning multiple merchants and need to disambiguate which one this
1381
+ * call applies to. API-key callers can omit it.
1353
1382
  * @returns The project.
1354
1383
  */
1355
- async retrieve(projectId) {
1356
- return this.request("GET", `/projects/${encodeURIComponent(projectId)}`);
1384
+ async retrieve(projectId, merchantId) {
1385
+ const path = `/projects/${encodeURIComponent(projectId)}`;
1386
+ if (merchantId === void 0) return this.request("GET", path);
1387
+ return this.request("GET", path, { query: { merchant_id: merchantId } });
1357
1388
  }
1358
1389
  /**
1359
1390
  * Update a project's details.
1360
1391
  *
1361
1392
  * @param projectId - The project ID to update.
1362
1393
  * @param params - Fields to update.
1394
+ * @param merchantId - Optional merchant scope. See {@link Projects.retrieve}.
1363
1395
  * @returns The updated project.
1364
1396
  */
1365
- async update(projectId, params) {
1366
- return this.request("PUT", `/projects/${encodeURIComponent(projectId)}`, { body: params });
1397
+ async update(projectId, params, merchantId) {
1398
+ const path = `/projects/${encodeURIComponent(projectId)}`;
1399
+ if (merchantId === void 0) return this.request("PUT", path, { body: params });
1400
+ return this.request("PUT", path, { body: params, query: { merchant_id: merchantId } });
1367
1401
  }
1368
1402
  /**
1369
1403
  * Delete a project.
1370
1404
  *
1371
1405
  * @param projectId - The project ID to delete.
1406
+ * @param merchantId - Optional merchant scope. See {@link Projects.retrieve}.
1372
1407
  * @returns The deleted project object.
1373
1408
  */
1374
- async delete(projectId) {
1375
- return this.request("DELETE", `/projects/${encodeURIComponent(projectId)}`);
1409
+ async delete(projectId, merchantId) {
1410
+ const path = `/projects/${encodeURIComponent(projectId)}`;
1411
+ if (merchantId === void 0) return this.request("DELETE", path);
1412
+ return this.request("DELETE", path, { query: { merchant_id: merchantId } });
1376
1413
  }
1377
1414
  /**
1378
1415
  * List all projects for a merchant.
@@ -2051,9 +2088,20 @@ var Users = class {
2051
2088
  async listRolesV2() {
2052
2089
  return this.request("GET", "/user/role/v2/list");
2053
2090
  }
2054
- /** List invitable roles. `GET /user/role/list/invite` */
2055
- async listInvitableRoles() {
2056
- return this.request("GET", "/user/role/list/invite");
2091
+ /**
2092
+ * List invitable roles. `GET /user/role/list/invite`
2093
+ *
2094
+ * @param params - Optional query. `entity_type` scopes the role list to a
2095
+ * particular entity (e.g. `'merchant'` to list only merchant-scoped roles
2096
+ * when inviting employees from the merchant dashboard).
2097
+ */
2098
+ async listInvitableRoles(params) {
2099
+ if (params === void 0 || params.entity_type === void 0) {
2100
+ return this.request("GET", "/user/role/list/invite");
2101
+ }
2102
+ return this.request("GET", "/user/role/list/invite", {
2103
+ query: { entity_type: params.entity_type }
2104
+ });
2057
2105
  }
2058
2106
  /** List updatable roles. `GET /user/role/list/update` */
2059
2107
  async listUpdatableRoles() {
@@ -2117,72 +2165,75 @@ var Webhooks = {
2117
2165
  /**
2118
2166
  * Verify the signature of an incoming Delopay webhook and return the parsed event.
2119
2167
  *
2168
+ * Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body,
2169
+ * using your shop's webhook secret (the *payment response hash key* configured on
2170
+ * the shop). The hex-encoded digest is delivered in the `X-Webhook-Signature-512`
2171
+ * HTTP header.
2172
+ *
2120
2173
  * Uses the Web Crypto API (`globalThis.crypto.subtle`), so it runs unchanged in
2121
2174
  * Node 18+, modern browsers, Deno, Bun, and edge runtimes (Cloudflare Workers, Vercel Edge).
2122
2175
  *
2123
- * This method is available as a static property on the `Delopay` class
2176
+ * Available as a static property on the `Delopay` class
2124
2177
  * (`Delopay.webhooks.verify`) and does not require a client instance.
2125
2178
  *
2126
- * @param rawBody - The raw request body string (do **not** parse it before passing).
2127
- * @param signatureHeader - The value of the `delopay-signature` HTTP header.
2128
- * @param secret - Your webhook signing secret from the Delopay dashboard.
2129
- * @param options - Optional verification settings (replay tolerance).
2179
+ * @param rawBody - The raw request body. Pass the original bytes (`Uint8Array` /
2180
+ * `Buffer`) when possible; if you pass a string, it must be the unmodified UTF-8
2181
+ * text of the request body. Do **not** parse it before passing.
2182
+ * @param signatureHeader - The value of the `X-Webhook-Signature-512` HTTP header.
2183
+ * @param secret - Your shop's webhook signing secret.
2130
2184
  * @returns Promise that resolves to the parsed webhook event.
2131
- * @throws {Error} When the signature is invalid, the timestamp is missing, or the event is too old.
2185
+ * @throws {Error} When the signature header is malformed or does not match the body.
2132
2186
  *
2133
2187
  * @example
2134
2188
  * ```typescript
2135
2189
  * // Express example
2136
2190
  * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
2137
- * const event = await Delopay.webhooks.verify(
2138
- * req.body.toString(),
2139
- * req.headers['delopay-signature'] as string,
2140
- * process.env.DELOPAY_WEBHOOK_SECRET!,
2141
- * );
2142
- * console.log(event.type, event.data);
2143
- * res.sendStatus(200);
2191
+ * try {
2192
+ * const event = await Delopay.webhooks.verify(
2193
+ * req.body, // Buffer from express.raw()
2194
+ * req.header('x-webhook-signature-512') ?? '',
2195
+ * process.env.DELOPAY_WEBHOOK_SECRET!,
2196
+ * );
2197
+ * console.log(event.type, event.data);
2198
+ * res.sendStatus(200);
2199
+ * } catch {
2200
+ * res.status(400).send('Invalid signature');
2201
+ * }
2144
2202
  * });
2145
2203
  * ```
2146
2204
  */
2147
- async verify(rawBody, signatureHeader, secret, options) {
2148
- const tolerance = options?.tolerance ?? 300;
2149
- const parts = signatureHeader.split(",");
2150
- const timestampPart = parts.find((p) => p.startsWith("t="));
2151
- const signaturePart = parts.find((p) => p.startsWith("v1="));
2152
- if (!timestampPart || !signaturePart) {
2153
- throw new Error("Invalid webhook signature format");
2154
- }
2155
- const timestamp = Number.parseInt(timestampPart.slice(2), 10);
2156
- if (!Number.isFinite(timestamp)) {
2157
- throw new Error("Invalid webhook timestamp");
2158
- }
2159
- const signatureHex = signaturePart.slice(3);
2160
- const signatureBytes = hexToBytes(signatureHex);
2205
+ async verify(rawBody, signatureHeader, secret) {
2161
2206
  const subtle = globalThis.crypto?.subtle;
2162
2207
  if (!subtle) {
2163
2208
  throw new Error(
2164
2209
  "Web Crypto unavailable: Delopay.webhooks.verify requires globalThis.crypto.subtle (Node 18+, modern browsers, Workers, Deno)"
2165
2210
  );
2166
2211
  }
2212
+ const signatureBytes = hexToBytes(signatureHeader.trim());
2213
+ if (!signatureBytes) {
2214
+ throw new Error("Invalid webhook signature format");
2215
+ }
2167
2216
  const encoder = new TextEncoder();
2217
+ const bodyBytes = typeof rawBody === "string" ? encoder.encode(rawBody) : rawBody;
2168
2218
  const asBufferSource = (bytes) => bytes;
2169
2219
  const key = await subtle.importKey(
2170
2220
  "raw",
2171
2221
  asBufferSource(encoder.encode(secret)),
2172
- { name: "HMAC", hash: "SHA-256" },
2222
+ { name: "HMAC", hash: "SHA-512" },
2173
2223
  false,
2174
2224
  ["verify"]
2175
2225
  );
2176
- const signedPayload = asBufferSource(encoder.encode(`${timestamp}.${rawBody}`));
2177
- const signaturesMatch = signatureBytes !== null && await subtle.verify("HMAC", key, asBufferSource(signatureBytes), signedPayload);
2178
- if (!signaturesMatch) {
2226
+ const valid = await subtle.verify(
2227
+ "HMAC",
2228
+ key,
2229
+ asBufferSource(signatureBytes),
2230
+ asBufferSource(bodyBytes)
2231
+ );
2232
+ if (!valid) {
2179
2233
  throw new Error("Invalid webhook signature");
2180
2234
  }
2181
- const age = Math.floor(Date.now() / 1e3) - timestamp;
2182
- if (Math.abs(age) > tolerance) {
2183
- throw new Error("Webhook timestamp too old");
2184
- }
2185
- return JSON.parse(rawBody);
2235
+ const bodyText = typeof rawBody === "string" ? rawBody : new TextDecoder("utf-8").decode(rawBody);
2236
+ return JSON.parse(bodyText);
2186
2237
  }
2187
2238
  };
2188
2239
 
@@ -2826,4 +2877,4 @@ export {
2826
2877
  Subscriptions,
2827
2878
  Delopay
2828
2879
  };
2829
- //# sourceMappingURL=chunk-WTVISXEY.js.map
2880
+ //# sourceMappingURL=chunk-VQPHGGNQ.js.map