@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 +8 -6
- package/dist/{chunk-WTVISXEY.js → chunk-VQPHGGNQ.js} +106 -55
- package/dist/chunk-VQPHGGNQ.js.map +1 -0
- package/dist/index.cjs +105 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -38
- package/dist/index.d.ts +113 -38
- package/dist/index.js +1 -1
- package/dist/internal.cjs +105 -54
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/package.json +16 -16
- package/dist/chunk-WTVISXEY.js.map +0 -1
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
|
-
|
|
174
|
-
|
|
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
|
|
181
|
-
} catch
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
2055
|
-
|
|
2056
|
-
|
|
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
|
-
*
|
|
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
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
* @param
|
|
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
|
|
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
|
-
*
|
|
2138
|
-
*
|
|
2139
|
-
*
|
|
2140
|
-
*
|
|
2141
|
-
*
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
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
|
|
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-
|
|
2222
|
+
{ name: "HMAC", hash: "SHA-512" },
|
|
2173
2223
|
false,
|
|
2174
2224
|
["verify"]
|
|
2175
2225
|
);
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
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
|
|
2182
|
-
|
|
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-
|
|
2880
|
+
//# sourceMappingURL=chunk-VQPHGGNQ.js.map
|