@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/dist/index.cjs CHANGED
@@ -383,14 +383,20 @@ var Connectors = class {
383
383
  async verify(params) {
384
384
  return this.request("POST", "/account/connectors/verify", { body: params });
385
385
  }
386
- /** Register a webhook for a connector. `POST /account/{merchantId}/connectors/webhooks/{connectorId}` */
387
- async registerWebhook(merchantId, connectorId) {
388
- return this.request(
389
- "POST",
390
- `/account/${encodeURIComponent(merchantId)}/connectors/webhooks/${encodeURIComponent(connectorId)}`
391
- );
386
+ /**
387
+ * Register a webhook for a connector.
388
+ * `POST /account/{merchantId}/connectors/webhooks/{connectorId}`
389
+ *
390
+ * @param params - Optional event scope. Defaults to `{ event_type: 'all_events' }`
391
+ * on the backend when omitted; pass `{ event_type: { specific_event: '…' } }`
392
+ * to scope to a single event.
393
+ */
394
+ async registerWebhook(merchantId, connectorId, params) {
395
+ const path = `/account/${encodeURIComponent(merchantId)}/connectors/webhooks/${encodeURIComponent(connectorId)}`;
396
+ if (params === void 0) return this.request("POST", path);
397
+ return this.request("POST", path, { body: params });
392
398
  }
393
- /** Get a webhook for a connector. `GET /account/{merchantId}/connectors/webhooks/{connectorId}` */
399
+ /** Get registered webhooks for a connector. `GET /account/{merchantId}/connectors/webhooks/{connectorId}` */
394
400
  async getWebhook(merchantId, connectorId) {
395
401
  return this.request(
396
402
  "GET",
@@ -979,15 +985,34 @@ var Payments = class {
979
985
  * Retrieve a payment by its ID.
980
986
  *
981
987
  * @param paymentId - The unique payment intent ID.
988
+ * @param options - Optional query flags. `force_sync` asks the backend to
989
+ * reconcile state with the connector before returning (used to recover a
990
+ * stuck intent when a webhook was lost). `all_keys_required` lifts the
991
+ * backend's `should_call_connector` gate so a `requires_payment_method`
992
+ * intent can still trigger a sync — without it the backend short-circuits
993
+ * and returns the local snapshot. Both flags are JWT-authenticated dashboard
994
+ * helpers; API-key callers can use them too where the backend allows.
982
995
  * @returns The payment intent.
983
996
  *
984
997
  * @example
985
998
  * ```typescript
986
999
  * const payment = await delopay.payments.retrieve('pay_abc123');
1000
+ * const synced = await delopay.payments.retrieve('pay_abc123', {
1001
+ * force_sync: true,
1002
+ * all_keys_required: true,
1003
+ * });
987
1004
  * ```
988
1005
  */
989
- async retrieve(paymentId) {
990
- return this.request("GET", `/payments/${encodeURIComponent(paymentId)}`);
1006
+ async retrieve(paymentId, options) {
1007
+ const path = `/payments/${encodeURIComponent(paymentId)}`;
1008
+ if (options === void 0) return this.request("GET", path);
1009
+ const query = {};
1010
+ if (options.force_sync !== void 0) query["force_sync"] = options.force_sync;
1011
+ if (options.all_keys_required !== void 0) {
1012
+ query["all_keys_required"] = options.all_keys_required;
1013
+ }
1014
+ if (Object.keys(query).length === 0) return this.request("GET", path);
1015
+ return this.request("GET", path, { query });
991
1016
  }
992
1017
  /**
993
1018
  * Update an existing payment intent before it is confirmed.
@@ -1388,29 +1413,41 @@ var Projects = class {
1388
1413
  * Retrieve a project by its ID.
1389
1414
  *
1390
1415
  * @param projectId - The unique project ID.
1416
+ * @param merchantId - Optional merchant scope. When provided, sent as
1417
+ * `?merchant_id=…` — required by dashboards that authenticate with a JWT
1418
+ * spanning multiple merchants and need to disambiguate which one this
1419
+ * call applies to. API-key callers can omit it.
1391
1420
  * @returns The project.
1392
1421
  */
1393
- async retrieve(projectId) {
1394
- return this.request("GET", `/projects/${encodeURIComponent(projectId)}`);
1422
+ async retrieve(projectId, merchantId) {
1423
+ const path = `/projects/${encodeURIComponent(projectId)}`;
1424
+ if (merchantId === void 0) return this.request("GET", path);
1425
+ return this.request("GET", path, { query: { merchant_id: merchantId } });
1395
1426
  }
1396
1427
  /**
1397
1428
  * Update a project's details.
1398
1429
  *
1399
1430
  * @param projectId - The project ID to update.
1400
1431
  * @param params - Fields to update.
1432
+ * @param merchantId - Optional merchant scope. See {@link Projects.retrieve}.
1401
1433
  * @returns The updated project.
1402
1434
  */
1403
- async update(projectId, params) {
1404
- return this.request("PUT", `/projects/${encodeURIComponent(projectId)}`, { body: params });
1435
+ async update(projectId, params, merchantId) {
1436
+ const path = `/projects/${encodeURIComponent(projectId)}`;
1437
+ if (merchantId === void 0) return this.request("PUT", path, { body: params });
1438
+ return this.request("PUT", path, { body: params, query: { merchant_id: merchantId } });
1405
1439
  }
1406
1440
  /**
1407
1441
  * Delete a project.
1408
1442
  *
1409
1443
  * @param projectId - The project ID to delete.
1444
+ * @param merchantId - Optional merchant scope. See {@link Projects.retrieve}.
1410
1445
  * @returns The deleted project object.
1411
1446
  */
1412
- async delete(projectId) {
1413
- return this.request("DELETE", `/projects/${encodeURIComponent(projectId)}`);
1447
+ async delete(projectId, merchantId) {
1448
+ const path = `/projects/${encodeURIComponent(projectId)}`;
1449
+ if (merchantId === void 0) return this.request("DELETE", path);
1450
+ return this.request("DELETE", path, { query: { merchant_id: merchantId } });
1414
1451
  }
1415
1452
  /**
1416
1453
  * List all projects for a merchant.
@@ -2089,9 +2126,20 @@ var Users = class {
2089
2126
  async listRolesV2() {
2090
2127
  return this.request("GET", "/user/role/v2/list");
2091
2128
  }
2092
- /** List invitable roles. `GET /user/role/list/invite` */
2093
- async listInvitableRoles() {
2094
- return this.request("GET", "/user/role/list/invite");
2129
+ /**
2130
+ * List invitable roles. `GET /user/role/list/invite`
2131
+ *
2132
+ * @param params - Optional query. `entity_type` scopes the role list to a
2133
+ * particular entity (e.g. `'merchant'` to list only merchant-scoped roles
2134
+ * when inviting employees from the merchant dashboard).
2135
+ */
2136
+ async listInvitableRoles(params) {
2137
+ if (params === void 0 || params.entity_type === void 0) {
2138
+ return this.request("GET", "/user/role/list/invite");
2139
+ }
2140
+ return this.request("GET", "/user/role/list/invite", {
2141
+ query: { entity_type: params.entity_type }
2142
+ });
2095
2143
  }
2096
2144
  /** List updatable roles. `GET /user/role/list/update` */
2097
2145
  async listUpdatableRoles() {
@@ -2155,72 +2203,75 @@ var Webhooks = {
2155
2203
  /**
2156
2204
  * Verify the signature of an incoming Delopay webhook and return the parsed event.
2157
2205
  *
2206
+ * Delopay signs each outgoing webhook with HMAC-SHA512 over the raw request body,
2207
+ * using your shop's webhook secret (the *payment response hash key* configured on
2208
+ * the shop). The hex-encoded digest is delivered in the `X-Webhook-Signature-512`
2209
+ * HTTP header.
2210
+ *
2158
2211
  * Uses the Web Crypto API (`globalThis.crypto.subtle`), so it runs unchanged in
2159
2212
  * Node 18+, modern browsers, Deno, Bun, and edge runtimes (Cloudflare Workers, Vercel Edge).
2160
2213
  *
2161
- * This method is available as a static property on the `Delopay` class
2214
+ * Available as a static property on the `Delopay` class
2162
2215
  * (`Delopay.webhooks.verify`) and does not require a client instance.
2163
2216
  *
2164
- * @param rawBody - The raw request body string (do **not** parse it before passing).
2165
- * @param signatureHeader - The value of the `delopay-signature` HTTP header.
2166
- * @param secret - Your webhook signing secret from the Delopay dashboard.
2167
- * @param options - Optional verification settings (replay tolerance).
2217
+ * @param rawBody - The raw request body. Pass the original bytes (`Uint8Array` /
2218
+ * `Buffer`) when possible; if you pass a string, it must be the unmodified UTF-8
2219
+ * text of the request body. Do **not** parse it before passing.
2220
+ * @param signatureHeader - The value of the `X-Webhook-Signature-512` HTTP header.
2221
+ * @param secret - Your shop's webhook signing secret.
2168
2222
  * @returns Promise that resolves to the parsed webhook event.
2169
- * @throws {Error} When the signature is invalid, the timestamp is missing, or the event is too old.
2223
+ * @throws {Error} When the signature header is malformed or does not match the body.
2170
2224
  *
2171
2225
  * @example
2172
2226
  * ```typescript
2173
2227
  * // Express example
2174
2228
  * app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
2175
- * const event = await Delopay.webhooks.verify(
2176
- * req.body.toString(),
2177
- * req.headers['delopay-signature'] as string,
2178
- * process.env.DELOPAY_WEBHOOK_SECRET!,
2179
- * );
2180
- * console.log(event.type, event.data);
2181
- * res.sendStatus(200);
2229
+ * try {
2230
+ * const event = await Delopay.webhooks.verify(
2231
+ * req.body, // Buffer from express.raw()
2232
+ * req.header('x-webhook-signature-512') ?? '',
2233
+ * process.env.DELOPAY_WEBHOOK_SECRET!,
2234
+ * );
2235
+ * console.log(event.type, event.data);
2236
+ * res.sendStatus(200);
2237
+ * } catch {
2238
+ * res.status(400).send('Invalid signature');
2239
+ * }
2182
2240
  * });
2183
2241
  * ```
2184
2242
  */
2185
- async verify(rawBody, signatureHeader, secret, options) {
2186
- const tolerance = options?.tolerance ?? 300;
2187
- const parts = signatureHeader.split(",");
2188
- const timestampPart = parts.find((p) => p.startsWith("t="));
2189
- const signaturePart = parts.find((p) => p.startsWith("v1="));
2190
- if (!timestampPart || !signaturePart) {
2191
- throw new Error("Invalid webhook signature format");
2192
- }
2193
- const timestamp = Number.parseInt(timestampPart.slice(2), 10);
2194
- if (!Number.isFinite(timestamp)) {
2195
- throw new Error("Invalid webhook timestamp");
2196
- }
2197
- const signatureHex = signaturePart.slice(3);
2198
- const signatureBytes = hexToBytes(signatureHex);
2243
+ async verify(rawBody, signatureHeader, secret) {
2199
2244
  const subtle = globalThis.crypto?.subtle;
2200
2245
  if (!subtle) {
2201
2246
  throw new Error(
2202
2247
  "Web Crypto unavailable: Delopay.webhooks.verify requires globalThis.crypto.subtle (Node 18+, modern browsers, Workers, Deno)"
2203
2248
  );
2204
2249
  }
2250
+ const signatureBytes = hexToBytes(signatureHeader.trim());
2251
+ if (!signatureBytes) {
2252
+ throw new Error("Invalid webhook signature format");
2253
+ }
2205
2254
  const encoder = new TextEncoder();
2255
+ const bodyBytes = typeof rawBody === "string" ? encoder.encode(rawBody) : rawBody;
2206
2256
  const asBufferSource = (bytes) => bytes;
2207
2257
  const key = await subtle.importKey(
2208
2258
  "raw",
2209
2259
  asBufferSource(encoder.encode(secret)),
2210
- { name: "HMAC", hash: "SHA-256" },
2260
+ { name: "HMAC", hash: "SHA-512" },
2211
2261
  false,
2212
2262
  ["verify"]
2213
2263
  );
2214
- const signedPayload = asBufferSource(encoder.encode(`${timestamp}.${rawBody}`));
2215
- const signaturesMatch = signatureBytes !== null && await subtle.verify("HMAC", key, asBufferSource(signatureBytes), signedPayload);
2216
- if (!signaturesMatch) {
2264
+ const valid = await subtle.verify(
2265
+ "HMAC",
2266
+ key,
2267
+ asBufferSource(signatureBytes),
2268
+ asBufferSource(bodyBytes)
2269
+ );
2270
+ if (!valid) {
2217
2271
  throw new Error("Invalid webhook signature");
2218
2272
  }
2219
- const age = Math.floor(Date.now() / 1e3) - timestamp;
2220
- if (Math.abs(age) > tolerance) {
2221
- throw new Error("Webhook timestamp too old");
2222
- }
2223
- return JSON.parse(rawBody);
2273
+ const bodyText = typeof rawBody === "string" ? rawBody : new TextDecoder("utf-8").decode(rawBody);
2274
+ return JSON.parse(bodyText);
2224
2275
  }
2225
2276
  };
2226
2277