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