@scell/sdk 1.0.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/LICENSE +21 -0
- package/README.md +464 -0
- package/dist/index.d.mts +2292 -0
- package/dist/index.d.ts +2292 -0
- package/dist/index.js +1755 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1748 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +71 -0
- package/src/client.ts +297 -0
- package/src/errors.ts +256 -0
- package/src/index.ts +195 -0
- package/src/resources/api-keys.ts +141 -0
- package/src/resources/auth.ts +286 -0
- package/src/resources/balance.ts +157 -0
- package/src/resources/companies.ts +224 -0
- package/src/resources/invoices.ts +246 -0
- package/src/resources/signatures.ts +245 -0
- package/src/resources/webhooks.ts +258 -0
- package/src/types/api-keys.ts +35 -0
- package/src/types/auth.ts +58 -0
- package/src/types/balance.ts +87 -0
- package/src/types/common.ts +114 -0
- package/src/types/companies.ts +86 -0
- package/src/types/index.ts +115 -0
- package/src/types/invoices.ts +191 -0
- package/src/types/signatures.ts +153 -0
- package/src/types/webhooks.ts +146 -0
- package/src/utils/retry.ts +167 -0
- package/src/utils/webhook-verify.ts +290 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1755 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/errors.ts
|
|
20
|
+
var errors_exports = {};
|
|
21
|
+
__export(errors_exports, {
|
|
22
|
+
ScellAuthenticationError: () => exports.ScellAuthenticationError,
|
|
23
|
+
ScellAuthorizationError: () => exports.ScellAuthorizationError,
|
|
24
|
+
ScellError: () => exports.ScellError,
|
|
25
|
+
ScellInsufficientBalanceError: () => exports.ScellInsufficientBalanceError,
|
|
26
|
+
ScellNetworkError: () => exports.ScellNetworkError,
|
|
27
|
+
ScellNotFoundError: () => exports.ScellNotFoundError,
|
|
28
|
+
ScellRateLimitError: () => exports.ScellRateLimitError,
|
|
29
|
+
ScellServerError: () => exports.ScellServerError,
|
|
30
|
+
ScellTimeoutError: () => exports.ScellTimeoutError,
|
|
31
|
+
ScellValidationError: () => exports.ScellValidationError,
|
|
32
|
+
parseApiError: () => parseApiError
|
|
33
|
+
});
|
|
34
|
+
function parseApiError(status, body, headers) {
|
|
35
|
+
const errorBody = body;
|
|
36
|
+
const message = errorBody?.message ?? "Unknown error";
|
|
37
|
+
const errors = errorBody?.errors ?? {};
|
|
38
|
+
const code = errorBody?.code;
|
|
39
|
+
switch (status) {
|
|
40
|
+
case 401:
|
|
41
|
+
throw new exports.ScellAuthenticationError(message, body);
|
|
42
|
+
case 402:
|
|
43
|
+
throw new exports.ScellInsufficientBalanceError(message, body);
|
|
44
|
+
case 403:
|
|
45
|
+
throw new exports.ScellAuthorizationError(message, body);
|
|
46
|
+
case 404:
|
|
47
|
+
throw new exports.ScellNotFoundError(message, body);
|
|
48
|
+
case 422:
|
|
49
|
+
throw new exports.ScellValidationError(message, errors, body);
|
|
50
|
+
case 429: {
|
|
51
|
+
const retryAfter = headers?.get("Retry-After");
|
|
52
|
+
throw new exports.ScellRateLimitError(
|
|
53
|
+
message,
|
|
54
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0,
|
|
55
|
+
body
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
default:
|
|
59
|
+
if (status >= 500) {
|
|
60
|
+
throw new exports.ScellServerError(message, status, body);
|
|
61
|
+
}
|
|
62
|
+
throw new exports.ScellError(message, status, code, body);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ScellError = void 0; exports.ScellAuthenticationError = void 0; exports.ScellAuthorizationError = void 0; exports.ScellValidationError = void 0; exports.ScellRateLimitError = void 0; exports.ScellNotFoundError = void 0; exports.ScellServerError = void 0; exports.ScellInsufficientBalanceError = void 0; exports.ScellNetworkError = void 0; exports.ScellTimeoutError = void 0;
|
|
66
|
+
var init_errors = __esm({
|
|
67
|
+
"src/errors.ts"() {
|
|
68
|
+
exports.ScellError = class extends Error {
|
|
69
|
+
/** HTTP status code */
|
|
70
|
+
status;
|
|
71
|
+
/** Error code from API */
|
|
72
|
+
code;
|
|
73
|
+
/** Original response body */
|
|
74
|
+
body;
|
|
75
|
+
constructor(message, status, code, body) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = "ScellError";
|
|
78
|
+
this.status = status;
|
|
79
|
+
this.code = code;
|
|
80
|
+
this.body = body;
|
|
81
|
+
if (Error.captureStackTrace) {
|
|
82
|
+
Error.captureStackTrace(this, this.constructor);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
exports.ScellAuthenticationError = class extends exports.ScellError {
|
|
87
|
+
constructor(message = "Authentication failed", body) {
|
|
88
|
+
super(message, 401, "AUTHENTICATION_ERROR", body);
|
|
89
|
+
this.name = "ScellAuthenticationError";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
exports.ScellAuthorizationError = class extends exports.ScellError {
|
|
93
|
+
constructor(message = "Access denied", body) {
|
|
94
|
+
super(message, 403, "AUTHORIZATION_ERROR", body);
|
|
95
|
+
this.name = "ScellAuthorizationError";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
exports.ScellValidationError = class extends exports.ScellError {
|
|
99
|
+
/** Field-level validation errors */
|
|
100
|
+
errors;
|
|
101
|
+
constructor(message, errors = {}, body) {
|
|
102
|
+
super(message, 422, "VALIDATION_ERROR", body);
|
|
103
|
+
this.name = "ScellValidationError";
|
|
104
|
+
this.errors = errors;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get all error messages as a flat array
|
|
108
|
+
*/
|
|
109
|
+
getAllMessages() {
|
|
110
|
+
return Object.values(this.errors).flat();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get error messages for a specific field
|
|
114
|
+
*/
|
|
115
|
+
getFieldErrors(field) {
|
|
116
|
+
return this.errors[field] ?? [];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if a specific field has errors
|
|
120
|
+
*/
|
|
121
|
+
hasFieldError(field) {
|
|
122
|
+
const fieldErrors = this.errors[field];
|
|
123
|
+
return fieldErrors !== void 0 && fieldErrors.length > 0;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
exports.ScellRateLimitError = class extends exports.ScellError {
|
|
127
|
+
/** Seconds to wait before retrying */
|
|
128
|
+
retryAfter;
|
|
129
|
+
constructor(message = "Rate limit exceeded", retryAfter, body) {
|
|
130
|
+
super(message, 429, "RATE_LIMIT_EXCEEDED", body);
|
|
131
|
+
this.name = "ScellRateLimitError";
|
|
132
|
+
this.retryAfter = retryAfter;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
exports.ScellNotFoundError = class extends exports.ScellError {
|
|
136
|
+
constructor(message = "Resource not found", body) {
|
|
137
|
+
super(message, 404, "NOT_FOUND", body);
|
|
138
|
+
this.name = "ScellNotFoundError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
exports.ScellServerError = class extends exports.ScellError {
|
|
142
|
+
constructor(message = "Internal server error", status = 500, body) {
|
|
143
|
+
super(message, status, "SERVER_ERROR", body);
|
|
144
|
+
this.name = "ScellServerError";
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
exports.ScellInsufficientBalanceError = class extends exports.ScellError {
|
|
148
|
+
constructor(message = "Insufficient balance", body) {
|
|
149
|
+
super(message, 402, "INSUFFICIENT_BALANCE", body);
|
|
150
|
+
this.name = "ScellInsufficientBalanceError";
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
exports.ScellNetworkError = class extends exports.ScellError {
|
|
154
|
+
originalError;
|
|
155
|
+
constructor(message, originalError) {
|
|
156
|
+
super(message, 0, "NETWORK_ERROR");
|
|
157
|
+
this.name = "ScellNetworkError";
|
|
158
|
+
this.originalError = originalError;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
exports.ScellTimeoutError = class extends exports.ScellError {
|
|
162
|
+
constructor(message = "Request timed out") {
|
|
163
|
+
super(message, 0, "TIMEOUT");
|
|
164
|
+
this.name = "ScellTimeoutError";
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// src/client.ts
|
|
171
|
+
init_errors();
|
|
172
|
+
|
|
173
|
+
// src/utils/retry.ts
|
|
174
|
+
init_errors();
|
|
175
|
+
var DEFAULT_RETRY_OPTIONS = {
|
|
176
|
+
maxRetries: 3,
|
|
177
|
+
baseDelay: 1e3,
|
|
178
|
+
maxDelay: 3e4,
|
|
179
|
+
jitterFactor: 0.1
|
|
180
|
+
};
|
|
181
|
+
function isRetryableError(error) {
|
|
182
|
+
if (error instanceof exports.ScellRateLimitError) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (error instanceof exports.ScellServerError) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
if (error instanceof Error && error.name === "ScellNetworkError") {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
function calculateDelay(attempt, baseDelay, maxDelay, jitterFactor, retryAfter) {
|
|
194
|
+
if (retryAfter !== void 0 && retryAfter > 0) {
|
|
195
|
+
return retryAfter * 1e3;
|
|
196
|
+
}
|
|
197
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
198
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
199
|
+
const jitter = cappedDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
200
|
+
return Math.floor(cappedDelay + jitter);
|
|
201
|
+
}
|
|
202
|
+
function sleep(ms) {
|
|
203
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
204
|
+
}
|
|
205
|
+
async function withRetry(fn, options = {}) {
|
|
206
|
+
const maxRetries = options.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;
|
|
207
|
+
const baseDelay = options.baseDelay ?? DEFAULT_RETRY_OPTIONS.baseDelay;
|
|
208
|
+
const maxDelay = options.maxDelay ?? DEFAULT_RETRY_OPTIONS.maxDelay;
|
|
209
|
+
const jitterFactor = options.jitterFactor ?? DEFAULT_RETRY_OPTIONS.jitterFactor;
|
|
210
|
+
const isRetryable = options.isRetryable ?? isRetryableError;
|
|
211
|
+
let lastError;
|
|
212
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
213
|
+
try {
|
|
214
|
+
return await fn();
|
|
215
|
+
} catch (error) {
|
|
216
|
+
lastError = error;
|
|
217
|
+
if (attempt >= maxRetries || !isRetryable(error)) {
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
const retryAfter = error instanceof exports.ScellRateLimitError ? error.retryAfter : void 0;
|
|
221
|
+
const delay = calculateDelay(
|
|
222
|
+
attempt,
|
|
223
|
+
baseDelay,
|
|
224
|
+
maxDelay,
|
|
225
|
+
jitterFactor,
|
|
226
|
+
retryAfter
|
|
227
|
+
);
|
|
228
|
+
await sleep(delay);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
throw lastError;
|
|
232
|
+
}
|
|
233
|
+
function createRetryWrapper(defaultOptions = {}) {
|
|
234
|
+
return (fn, options = {}) => withRetry(fn, { ...defaultOptions, ...options });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/client.ts
|
|
238
|
+
var HttpClient = class {
|
|
239
|
+
baseUrl;
|
|
240
|
+
timeout;
|
|
241
|
+
retryOptions;
|
|
242
|
+
enableRetry;
|
|
243
|
+
fetchFn;
|
|
244
|
+
authMode;
|
|
245
|
+
authToken;
|
|
246
|
+
constructor(authMode, authToken, config = {}) {
|
|
247
|
+
this.authMode = authMode;
|
|
248
|
+
this.authToken = authToken;
|
|
249
|
+
this.baseUrl = config.baseUrl ?? "https://api.scell.io/api/v1";
|
|
250
|
+
this.timeout = config.timeout ?? 3e4;
|
|
251
|
+
this.retryOptions = config.retry ?? {};
|
|
252
|
+
this.enableRetry = config.enableRetry ?? true;
|
|
253
|
+
this.fetchFn = config.fetch ?? fetch;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Build URL with query parameters
|
|
257
|
+
*/
|
|
258
|
+
buildUrl(path, query) {
|
|
259
|
+
const url = new URL(path, this.baseUrl);
|
|
260
|
+
if (query) {
|
|
261
|
+
for (const [key, value] of Object.entries(query)) {
|
|
262
|
+
if (value !== void 0) {
|
|
263
|
+
url.searchParams.set(key, String(value));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return url.toString();
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Build headers for request
|
|
271
|
+
*/
|
|
272
|
+
buildHeaders(additionalHeaders) {
|
|
273
|
+
const headers = {
|
|
274
|
+
"Content-Type": "application/json",
|
|
275
|
+
Accept: "application/json",
|
|
276
|
+
...additionalHeaders
|
|
277
|
+
};
|
|
278
|
+
if (this.authMode === "bearer") {
|
|
279
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
280
|
+
} else {
|
|
281
|
+
headers["X-API-Key"] = this.authToken;
|
|
282
|
+
}
|
|
283
|
+
return headers;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Execute HTTP request
|
|
287
|
+
*/
|
|
288
|
+
async executeRequest(config) {
|
|
289
|
+
const { method, path, body, query, headers, timeout, signal } = config;
|
|
290
|
+
const url = this.buildUrl(path, query);
|
|
291
|
+
const requestHeaders = this.buildHeaders(headers);
|
|
292
|
+
const requestTimeout = timeout ?? this.timeout;
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
295
|
+
const combinedSignal = signal ? new AbortController().signal : controller.signal;
|
|
296
|
+
if (signal) {
|
|
297
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const fetchOptions = {
|
|
301
|
+
method,
|
|
302
|
+
headers: requestHeaders,
|
|
303
|
+
signal: combinedSignal
|
|
304
|
+
};
|
|
305
|
+
if (body !== void 0) {
|
|
306
|
+
fetchOptions.body = JSON.stringify(body);
|
|
307
|
+
}
|
|
308
|
+
const response = await this.fetchFn(url, fetchOptions);
|
|
309
|
+
clearTimeout(timeoutId);
|
|
310
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
311
|
+
let responseBody;
|
|
312
|
+
if (contentType.includes("application/json")) {
|
|
313
|
+
responseBody = await response.json();
|
|
314
|
+
} else {
|
|
315
|
+
responseBody = await response.text();
|
|
316
|
+
}
|
|
317
|
+
if (!response.ok) {
|
|
318
|
+
parseApiError(response.status, responseBody, response.headers);
|
|
319
|
+
}
|
|
320
|
+
return responseBody;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
clearTimeout(timeoutId);
|
|
323
|
+
if (error instanceof Error) {
|
|
324
|
+
if (error.name === "AbortError") {
|
|
325
|
+
throw new exports.ScellTimeoutError(
|
|
326
|
+
`Request timed out after ${requestTimeout}ms`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
330
|
+
throw new exports.ScellNetworkError("Network request failed", error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Execute request with optional retry
|
|
338
|
+
*/
|
|
339
|
+
async request(config) {
|
|
340
|
+
const shouldRetry = this.enableRetry && !config.skipRetry;
|
|
341
|
+
if (shouldRetry) {
|
|
342
|
+
return withRetry(
|
|
343
|
+
() => this.executeRequest(config),
|
|
344
|
+
this.retryOptions
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
return this.executeRequest(config);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* GET request
|
|
351
|
+
*/
|
|
352
|
+
async get(path, query, options) {
|
|
353
|
+
const config = {
|
|
354
|
+
method: "GET",
|
|
355
|
+
path,
|
|
356
|
+
...options
|
|
357
|
+
};
|
|
358
|
+
if (query !== void 0) {
|
|
359
|
+
config.query = query;
|
|
360
|
+
}
|
|
361
|
+
return this.request(config);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* POST request
|
|
365
|
+
*/
|
|
366
|
+
async post(path, body, options) {
|
|
367
|
+
return this.request({
|
|
368
|
+
method: "POST",
|
|
369
|
+
path,
|
|
370
|
+
body,
|
|
371
|
+
...options
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* PUT request
|
|
376
|
+
*/
|
|
377
|
+
async put(path, body, options) {
|
|
378
|
+
return this.request({
|
|
379
|
+
method: "PUT",
|
|
380
|
+
path,
|
|
381
|
+
body,
|
|
382
|
+
...options
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* PATCH request
|
|
387
|
+
*/
|
|
388
|
+
async patch(path, body, options) {
|
|
389
|
+
return this.request({
|
|
390
|
+
method: "PATCH",
|
|
391
|
+
path,
|
|
392
|
+
body,
|
|
393
|
+
...options
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* DELETE request
|
|
398
|
+
*/
|
|
399
|
+
async delete(path, options) {
|
|
400
|
+
return this.request({
|
|
401
|
+
method: "DELETE",
|
|
402
|
+
path,
|
|
403
|
+
...options
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/resources/api-keys.ts
|
|
409
|
+
var ApiKeysResource = class {
|
|
410
|
+
constructor(http) {
|
|
411
|
+
this.http = http;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* List all API keys
|
|
415
|
+
*
|
|
416
|
+
* @param requestOptions - Request options
|
|
417
|
+
* @returns List of API keys (without secrets)
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* const { data: keys } = await client.apiKeys.list();
|
|
422
|
+
* keys.forEach(key => {
|
|
423
|
+
* console.log(`${key.name}: ${key.key_prefix}...`);
|
|
424
|
+
* });
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
async list(requestOptions) {
|
|
428
|
+
return this.http.get(
|
|
429
|
+
"/api-keys",
|
|
430
|
+
void 0,
|
|
431
|
+
requestOptions
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Get a specific API key
|
|
436
|
+
*
|
|
437
|
+
* @param id - API Key UUID
|
|
438
|
+
* @param requestOptions - Request options
|
|
439
|
+
* @returns API key details (without secret)
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```typescript
|
|
443
|
+
* const { data: key } = await client.apiKeys.get('key-uuid');
|
|
444
|
+
* console.log(`Last used: ${key.last_used_at}`);
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
447
|
+
async get(id, requestOptions) {
|
|
448
|
+
return this.http.get(
|
|
449
|
+
`/api-keys/${id}`,
|
|
450
|
+
void 0,
|
|
451
|
+
requestOptions
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Create a new API key
|
|
456
|
+
*
|
|
457
|
+
* Important: The full key is only returned once during creation.
|
|
458
|
+
* Store it securely - you won't be able to retrieve it again.
|
|
459
|
+
*
|
|
460
|
+
* @param input - API key configuration
|
|
461
|
+
* @param requestOptions - Request options
|
|
462
|
+
* @returns Created API key with full key value
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```typescript
|
|
466
|
+
* const { data: apiKey } = await client.apiKeys.create({
|
|
467
|
+
* name: 'Production Integration',
|
|
468
|
+
* company_id: 'company-uuid',
|
|
469
|
+
* environment: 'production',
|
|
470
|
+
* permissions: ['invoices:write', 'signatures:write']
|
|
471
|
+
* });
|
|
472
|
+
*
|
|
473
|
+
* // IMPORTANT: Store this key securely!
|
|
474
|
+
* // You won't be able to see it again.
|
|
475
|
+
* console.log('Save this key:', apiKey.key);
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
async create(input, requestOptions) {
|
|
479
|
+
return this.http.post(
|
|
480
|
+
"/api-keys",
|
|
481
|
+
input,
|
|
482
|
+
requestOptions
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Delete an API key
|
|
487
|
+
*
|
|
488
|
+
* Warning: This will immediately revoke the key and all
|
|
489
|
+
* requests using it will fail.
|
|
490
|
+
*
|
|
491
|
+
* @param id - API Key UUID
|
|
492
|
+
* @param requestOptions - Request options
|
|
493
|
+
* @returns Deletion confirmation
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* await client.apiKeys.delete('key-uuid');
|
|
498
|
+
* console.log('API key revoked');
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
async delete(id, requestOptions) {
|
|
502
|
+
return this.http.delete(`/api-keys/${id}`, requestOptions);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// src/resources/auth.ts
|
|
507
|
+
var AuthResource = class {
|
|
508
|
+
constructor(http) {
|
|
509
|
+
this.http = http;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Get current authenticated user
|
|
513
|
+
*
|
|
514
|
+
* @param requestOptions - Request options
|
|
515
|
+
* @returns Current user details
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```typescript
|
|
519
|
+
* const { data: user } = await client.auth.me();
|
|
520
|
+
* console.log(`Logged in as: ${user.name} (${user.email})`);
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
async me(requestOptions) {
|
|
524
|
+
return this.http.get(
|
|
525
|
+
"/auth/me",
|
|
526
|
+
void 0,
|
|
527
|
+
requestOptions
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Logout (revoke current token)
|
|
532
|
+
*
|
|
533
|
+
* @param requestOptions - Request options
|
|
534
|
+
* @returns Logout confirmation
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```typescript
|
|
538
|
+
* await client.auth.logout();
|
|
539
|
+
* // Token is now invalid
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
async logout(requestOptions) {
|
|
543
|
+
return this.http.post(
|
|
544
|
+
"/auth/logout",
|
|
545
|
+
void 0,
|
|
546
|
+
requestOptions
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
var ScellAuth = {
|
|
551
|
+
/**
|
|
552
|
+
* Default base URL for auth requests
|
|
553
|
+
*/
|
|
554
|
+
baseUrl: "https://api.scell.io/api/v1",
|
|
555
|
+
/**
|
|
556
|
+
* Register a new user
|
|
557
|
+
*
|
|
558
|
+
* @param input - Registration data
|
|
559
|
+
* @param baseUrl - Optional base URL override
|
|
560
|
+
* @returns Auth response with token
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* const auth = await ScellAuth.register({
|
|
565
|
+
* name: 'Jane Doe',
|
|
566
|
+
* email: 'jane@example.com',
|
|
567
|
+
* password: 'MySecurePassword123!',
|
|
568
|
+
* password_confirmation: 'MySecurePassword123!'
|
|
569
|
+
* });
|
|
570
|
+
*
|
|
571
|
+
* console.log('Welcome,', auth.user.name);
|
|
572
|
+
* const client = new ScellClient(auth.token);
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
async register(input, baseUrl) {
|
|
576
|
+
const url = `${baseUrl ?? this.baseUrl}/auth/register`;
|
|
577
|
+
const response = await fetch(url, {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: {
|
|
580
|
+
"Content-Type": "application/json",
|
|
581
|
+
Accept: "application/json"
|
|
582
|
+
},
|
|
583
|
+
body: JSON.stringify(input)
|
|
584
|
+
});
|
|
585
|
+
const body = await response.json();
|
|
586
|
+
if (!response.ok) {
|
|
587
|
+
const { parseApiError: parseApiError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
588
|
+
parseApiError2(response.status, body, response.headers);
|
|
589
|
+
}
|
|
590
|
+
return body;
|
|
591
|
+
},
|
|
592
|
+
/**
|
|
593
|
+
* Login an existing user
|
|
594
|
+
*
|
|
595
|
+
* @param credentials - Login credentials
|
|
596
|
+
* @param baseUrl - Optional base URL override
|
|
597
|
+
* @returns Auth response with token
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* const auth = await ScellAuth.login({
|
|
602
|
+
* email: 'user@example.com',
|
|
603
|
+
* password: 'password'
|
|
604
|
+
* });
|
|
605
|
+
*
|
|
606
|
+
* // Store the token securely
|
|
607
|
+
* localStorage.setItem('scell_token', auth.token);
|
|
608
|
+
*
|
|
609
|
+
* // Create client
|
|
610
|
+
* const client = new ScellClient(auth.token);
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
613
|
+
async login(credentials, baseUrl) {
|
|
614
|
+
const url = `${baseUrl ?? this.baseUrl}/auth/login`;
|
|
615
|
+
const response = await fetch(url, {
|
|
616
|
+
method: "POST",
|
|
617
|
+
headers: {
|
|
618
|
+
"Content-Type": "application/json",
|
|
619
|
+
Accept: "application/json"
|
|
620
|
+
},
|
|
621
|
+
body: JSON.stringify(credentials)
|
|
622
|
+
});
|
|
623
|
+
const body = await response.json();
|
|
624
|
+
if (!response.ok) {
|
|
625
|
+
const { parseApiError: parseApiError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
626
|
+
parseApiError2(response.status, body, response.headers);
|
|
627
|
+
}
|
|
628
|
+
return body;
|
|
629
|
+
},
|
|
630
|
+
/**
|
|
631
|
+
* Request password reset email
|
|
632
|
+
*
|
|
633
|
+
* @param input - Email address
|
|
634
|
+
* @param baseUrl - Optional base URL override
|
|
635
|
+
* @returns Confirmation message
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```typescript
|
|
639
|
+
* await ScellAuth.forgotPassword({
|
|
640
|
+
* email: 'user@example.com'
|
|
641
|
+
* });
|
|
642
|
+
* console.log('Check your email for reset link');
|
|
643
|
+
* ```
|
|
644
|
+
*/
|
|
645
|
+
async forgotPassword(input, baseUrl) {
|
|
646
|
+
const url = `${baseUrl ?? this.baseUrl}/auth/forgot-password`;
|
|
647
|
+
const response = await fetch(url, {
|
|
648
|
+
method: "POST",
|
|
649
|
+
headers: {
|
|
650
|
+
"Content-Type": "application/json",
|
|
651
|
+
Accept: "application/json"
|
|
652
|
+
},
|
|
653
|
+
body: JSON.stringify(input)
|
|
654
|
+
});
|
|
655
|
+
const body = await response.json();
|
|
656
|
+
if (!response.ok) {
|
|
657
|
+
const { parseApiError: parseApiError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
658
|
+
parseApiError2(response.status, body, response.headers);
|
|
659
|
+
}
|
|
660
|
+
return body;
|
|
661
|
+
},
|
|
662
|
+
/**
|
|
663
|
+
* Reset password with token from email
|
|
664
|
+
*
|
|
665
|
+
* @param input - Reset password data
|
|
666
|
+
* @param baseUrl - Optional base URL override
|
|
667
|
+
* @returns Confirmation message
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* ```typescript
|
|
671
|
+
* await ScellAuth.resetPassword({
|
|
672
|
+
* email: 'user@example.com',
|
|
673
|
+
* token: 'reset-token-from-email',
|
|
674
|
+
* password: 'NewSecurePassword123!',
|
|
675
|
+
* password_confirmation: 'NewSecurePassword123!'
|
|
676
|
+
* });
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
async resetPassword(input, baseUrl) {
|
|
680
|
+
const url = `${baseUrl ?? this.baseUrl}/auth/reset-password`;
|
|
681
|
+
const response = await fetch(url, {
|
|
682
|
+
method: "POST",
|
|
683
|
+
headers: {
|
|
684
|
+
"Content-Type": "application/json",
|
|
685
|
+
Accept: "application/json"
|
|
686
|
+
},
|
|
687
|
+
body: JSON.stringify(input)
|
|
688
|
+
});
|
|
689
|
+
const body = await response.json();
|
|
690
|
+
if (!response.ok) {
|
|
691
|
+
const { parseApiError: parseApiError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
692
|
+
parseApiError2(response.status, body, response.headers);
|
|
693
|
+
}
|
|
694
|
+
return body;
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// src/resources/balance.ts
|
|
699
|
+
var BalanceResource = class {
|
|
700
|
+
constructor(http) {
|
|
701
|
+
this.http = http;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get current balance and settings
|
|
705
|
+
*
|
|
706
|
+
* @param requestOptions - Request options
|
|
707
|
+
* @returns Current balance details
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* ```typescript
|
|
711
|
+
* const { data: balance } = await client.balance.get();
|
|
712
|
+
* console.log(`Balance: ${balance.amount} ${balance.currency}`);
|
|
713
|
+
*
|
|
714
|
+
* if (balance.amount < balance.low_balance_alert_threshold) {
|
|
715
|
+
* console.log('Warning: Low balance!');
|
|
716
|
+
* }
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
async get(requestOptions) {
|
|
720
|
+
return this.http.get(
|
|
721
|
+
"/balance",
|
|
722
|
+
void 0,
|
|
723
|
+
requestOptions
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Reload balance
|
|
728
|
+
*
|
|
729
|
+
* Note: This is a simulation endpoint. In production, use Stripe integration.
|
|
730
|
+
*
|
|
731
|
+
* @param input - Reload amount (10-10000 EUR)
|
|
732
|
+
* @param requestOptions - Request options
|
|
733
|
+
* @returns Reload transaction details
|
|
734
|
+
*
|
|
735
|
+
* @example
|
|
736
|
+
* ```typescript
|
|
737
|
+
* const { transaction } = await client.balance.reload({ amount: 100 });
|
|
738
|
+
* console.log(`New balance: ${transaction.balance_after}`);
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
async reload(input, requestOptions) {
|
|
742
|
+
return this.http.post(
|
|
743
|
+
"/balance/reload",
|
|
744
|
+
input,
|
|
745
|
+
requestOptions
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Update balance settings
|
|
750
|
+
*
|
|
751
|
+
* Configure auto-reload and alert thresholds.
|
|
752
|
+
*
|
|
753
|
+
* @param input - Settings to update
|
|
754
|
+
* @param requestOptions - Request options
|
|
755
|
+
* @returns Updated settings
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* ```typescript
|
|
759
|
+
* await client.balance.updateSettings({
|
|
760
|
+
* auto_reload_enabled: true,
|
|
761
|
+
* auto_reload_threshold: 50,
|
|
762
|
+
* auto_reload_amount: 200,
|
|
763
|
+
* low_balance_alert_threshold: 100,
|
|
764
|
+
* critical_balance_alert_threshold: 25
|
|
765
|
+
* });
|
|
766
|
+
* ```
|
|
767
|
+
*/
|
|
768
|
+
async updateSettings(input, requestOptions) {
|
|
769
|
+
return this.http.put(
|
|
770
|
+
"/balance/settings",
|
|
771
|
+
input,
|
|
772
|
+
requestOptions
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* List balance transactions
|
|
777
|
+
*
|
|
778
|
+
* @param options - Filter and pagination options
|
|
779
|
+
* @param requestOptions - Request options
|
|
780
|
+
* @returns Paginated list of transactions
|
|
781
|
+
*
|
|
782
|
+
* @example
|
|
783
|
+
* ```typescript
|
|
784
|
+
* // List all invoice debits
|
|
785
|
+
* const { data, meta } = await client.balance.transactions({
|
|
786
|
+
* type: 'debit',
|
|
787
|
+
* service: 'invoice',
|
|
788
|
+
* from: '2024-01-01',
|
|
789
|
+
* to: '2024-01-31'
|
|
790
|
+
* });
|
|
791
|
+
*
|
|
792
|
+
* data.forEach(tx => {
|
|
793
|
+
* console.log(`${tx.description}: -${tx.amount} EUR`);
|
|
794
|
+
* });
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
async transactions(options = {}, requestOptions) {
|
|
798
|
+
return this.http.get(
|
|
799
|
+
"/balance/transactions",
|
|
800
|
+
options,
|
|
801
|
+
requestOptions
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// src/resources/companies.ts
|
|
807
|
+
var CompaniesResource = class {
|
|
808
|
+
constructor(http) {
|
|
809
|
+
this.http = http;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* List all companies for the authenticated user
|
|
813
|
+
*
|
|
814
|
+
* @param requestOptions - Request options
|
|
815
|
+
* @returns List of companies
|
|
816
|
+
*
|
|
817
|
+
* @example
|
|
818
|
+
* ```typescript
|
|
819
|
+
* const { data: companies } = await client.companies.list();
|
|
820
|
+
* companies.forEach(c => console.log(c.name, c.status));
|
|
821
|
+
* ```
|
|
822
|
+
*/
|
|
823
|
+
async list(requestOptions) {
|
|
824
|
+
return this.http.get(
|
|
825
|
+
"/companies",
|
|
826
|
+
void 0,
|
|
827
|
+
requestOptions
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Get a specific company by ID
|
|
832
|
+
*
|
|
833
|
+
* @param id - Company UUID
|
|
834
|
+
* @param requestOptions - Request options
|
|
835
|
+
* @returns Company details
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```typescript
|
|
839
|
+
* const { data: company } = await client.companies.get('uuid');
|
|
840
|
+
* console.log(company.name, company.siret);
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
async get(id, requestOptions) {
|
|
844
|
+
return this.http.get(
|
|
845
|
+
`/companies/${id}`,
|
|
846
|
+
void 0,
|
|
847
|
+
requestOptions
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Create a new company
|
|
852
|
+
*
|
|
853
|
+
* @param input - Company creation data
|
|
854
|
+
* @param requestOptions - Request options
|
|
855
|
+
* @returns Created company
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```typescript
|
|
859
|
+
* const { data: company } = await client.companies.create({
|
|
860
|
+
* name: 'Acme Corp',
|
|
861
|
+
* siret: '12345678901234',
|
|
862
|
+
* vat_number: 'FR12345678901',
|
|
863
|
+
* legal_form: 'SAS',
|
|
864
|
+
* address_line1: '123 Business Street',
|
|
865
|
+
* postal_code: '75001',
|
|
866
|
+
* city: 'Paris',
|
|
867
|
+
* country: 'FR',
|
|
868
|
+
* email: 'contact@acme.com',
|
|
869
|
+
* phone: '+33 1 23 45 67 89'
|
|
870
|
+
* });
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
async create(input, requestOptions) {
|
|
874
|
+
return this.http.post(
|
|
875
|
+
"/companies",
|
|
876
|
+
input,
|
|
877
|
+
requestOptions
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Update a company
|
|
882
|
+
*
|
|
883
|
+
* @param id - Company UUID
|
|
884
|
+
* @param input - Fields to update
|
|
885
|
+
* @param requestOptions - Request options
|
|
886
|
+
* @returns Updated company
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```typescript
|
|
890
|
+
* const { data: company } = await client.companies.update('uuid', {
|
|
891
|
+
* email: 'new-email@acme.com',
|
|
892
|
+
* phone: '+33 1 98 76 54 32'
|
|
893
|
+
* });
|
|
894
|
+
* ```
|
|
895
|
+
*/
|
|
896
|
+
async update(id, input, requestOptions) {
|
|
897
|
+
return this.http.put(
|
|
898
|
+
`/companies/${id}`,
|
|
899
|
+
input,
|
|
900
|
+
requestOptions
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Delete a company
|
|
905
|
+
*
|
|
906
|
+
* @param id - Company UUID
|
|
907
|
+
* @param requestOptions - Request options
|
|
908
|
+
* @returns Deletion confirmation
|
|
909
|
+
*
|
|
910
|
+
* @example
|
|
911
|
+
* ```typescript
|
|
912
|
+
* await client.companies.delete('company-uuid');
|
|
913
|
+
* ```
|
|
914
|
+
*/
|
|
915
|
+
async delete(id, requestOptions) {
|
|
916
|
+
return this.http.delete(
|
|
917
|
+
`/companies/${id}`,
|
|
918
|
+
requestOptions
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Initiate KYC verification for a company
|
|
923
|
+
*
|
|
924
|
+
* @param id - Company UUID
|
|
925
|
+
* @param requestOptions - Request options
|
|
926
|
+
* @returns KYC reference and redirect URL
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* ```typescript
|
|
930
|
+
* const { kyc_reference, redirect_url } = await client.companies.initiateKyc(
|
|
931
|
+
* 'company-uuid'
|
|
932
|
+
* );
|
|
933
|
+
* // Redirect user to redirect_url for KYC verification
|
|
934
|
+
* ```
|
|
935
|
+
*/
|
|
936
|
+
async initiateKyc(id, requestOptions) {
|
|
937
|
+
return this.http.post(
|
|
938
|
+
`/companies/${id}/kyc`,
|
|
939
|
+
void 0,
|
|
940
|
+
requestOptions
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Get KYC verification status
|
|
945
|
+
*
|
|
946
|
+
* @param id - Company UUID
|
|
947
|
+
* @param requestOptions - Request options
|
|
948
|
+
* @returns Current KYC status
|
|
949
|
+
*
|
|
950
|
+
* @example
|
|
951
|
+
* ```typescript
|
|
952
|
+
* const status = await client.companies.kycStatus('company-uuid');
|
|
953
|
+
* if (status.status === 'active') {
|
|
954
|
+
* console.log('KYC completed at:', status.kyc_completed_at);
|
|
955
|
+
* }
|
|
956
|
+
* ```
|
|
957
|
+
*/
|
|
958
|
+
async kycStatus(id, requestOptions) {
|
|
959
|
+
return this.http.get(
|
|
960
|
+
`/companies/${id}/kyc/status`,
|
|
961
|
+
void 0,
|
|
962
|
+
requestOptions
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
// src/resources/invoices.ts
|
|
968
|
+
var InvoicesResource = class {
|
|
969
|
+
constructor(http) {
|
|
970
|
+
this.http = http;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* List invoices with optional filtering
|
|
974
|
+
*
|
|
975
|
+
* @param options - Filter and pagination options
|
|
976
|
+
* @param requestOptions - Request options
|
|
977
|
+
* @returns Paginated list of invoices
|
|
978
|
+
*
|
|
979
|
+
* @example
|
|
980
|
+
* ```typescript
|
|
981
|
+
* // List all outgoing invoices
|
|
982
|
+
* const { data, meta } = await client.invoices.list({
|
|
983
|
+
* direction: 'outgoing',
|
|
984
|
+
* per_page: 50
|
|
985
|
+
* });
|
|
986
|
+
* console.log(`Found ${meta.total} invoices`);
|
|
987
|
+
* ```
|
|
988
|
+
*/
|
|
989
|
+
async list(options = {}, requestOptions) {
|
|
990
|
+
return this.http.get(
|
|
991
|
+
"/invoices",
|
|
992
|
+
options,
|
|
993
|
+
requestOptions
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Get a specific invoice by ID
|
|
998
|
+
*
|
|
999
|
+
* @param id - Invoice UUID
|
|
1000
|
+
* @param requestOptions - Request options
|
|
1001
|
+
* @returns Invoice details
|
|
1002
|
+
*
|
|
1003
|
+
* @example
|
|
1004
|
+
* ```typescript
|
|
1005
|
+
* const { data: invoice } = await client.invoices.get('uuid-here');
|
|
1006
|
+
* console.log('Invoice number:', invoice.invoice_number);
|
|
1007
|
+
* ```
|
|
1008
|
+
*/
|
|
1009
|
+
async get(id, requestOptions) {
|
|
1010
|
+
return this.http.get(
|
|
1011
|
+
`/invoices/${id}`,
|
|
1012
|
+
void 0,
|
|
1013
|
+
requestOptions
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Create a new invoice
|
|
1018
|
+
*
|
|
1019
|
+
* Note: This endpoint requires API key authentication.
|
|
1020
|
+
* Creating an invoice in production mode will debit your balance.
|
|
1021
|
+
*
|
|
1022
|
+
* @param input - Invoice creation data
|
|
1023
|
+
* @param requestOptions - Request options
|
|
1024
|
+
* @returns Created invoice
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```typescript
|
|
1028
|
+
* const { data: invoice } = await client.invoices.create({
|
|
1029
|
+
* invoice_number: 'FACT-2024-001',
|
|
1030
|
+
* direction: 'outgoing',
|
|
1031
|
+
* output_format: 'facturx',
|
|
1032
|
+
* issue_date: '2024-01-15',
|
|
1033
|
+
* total_ht: 100.00,
|
|
1034
|
+
* total_tax: 20.00,
|
|
1035
|
+
* total_ttc: 120.00,
|
|
1036
|
+
* seller_siret: '12345678901234',
|
|
1037
|
+
* seller_name: 'My Company',
|
|
1038
|
+
* seller_address: {
|
|
1039
|
+
* line1: '1 Rue Example',
|
|
1040
|
+
* postal_code: '75001',
|
|
1041
|
+
* city: 'Paris',
|
|
1042
|
+
* country: 'FR'
|
|
1043
|
+
* },
|
|
1044
|
+
* buyer_siret: '98765432109876',
|
|
1045
|
+
* buyer_name: 'Client Company',
|
|
1046
|
+
* buyer_address: {
|
|
1047
|
+
* line1: '2 Avenue Test',
|
|
1048
|
+
* postal_code: '75002',
|
|
1049
|
+
* city: 'Paris',
|
|
1050
|
+
* country: 'FR'
|
|
1051
|
+
* },
|
|
1052
|
+
* lines: [{
|
|
1053
|
+
* description: 'Service prestation',
|
|
1054
|
+
* quantity: 1,
|
|
1055
|
+
* unit_price: 100.00,
|
|
1056
|
+
* tax_rate: 20.00,
|
|
1057
|
+
* total_ht: 100.00,
|
|
1058
|
+
* total_tax: 20.00,
|
|
1059
|
+
* total_ttc: 120.00
|
|
1060
|
+
* }]
|
|
1061
|
+
* });
|
|
1062
|
+
* ```
|
|
1063
|
+
*/
|
|
1064
|
+
async create(input, requestOptions) {
|
|
1065
|
+
return this.http.post(
|
|
1066
|
+
"/invoices",
|
|
1067
|
+
input,
|
|
1068
|
+
requestOptions
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Download invoice file
|
|
1073
|
+
*
|
|
1074
|
+
* @param id - Invoice UUID
|
|
1075
|
+
* @param type - File type to download
|
|
1076
|
+
* @param requestOptions - Request options
|
|
1077
|
+
* @returns Temporary download URL
|
|
1078
|
+
*
|
|
1079
|
+
* @example
|
|
1080
|
+
* ```typescript
|
|
1081
|
+
* // Download PDF version
|
|
1082
|
+
* const { url, expires_at } = await client.invoices.download(
|
|
1083
|
+
* 'invoice-uuid',
|
|
1084
|
+
* 'pdf'
|
|
1085
|
+
* );
|
|
1086
|
+
* console.log('Download before:', expires_at);
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
async download(id, type, requestOptions) {
|
|
1090
|
+
return this.http.get(
|
|
1091
|
+
`/invoices/${id}/download/${type}`,
|
|
1092
|
+
void 0,
|
|
1093
|
+
requestOptions
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Get invoice audit trail (Piste d'Audit Fiable)
|
|
1098
|
+
*
|
|
1099
|
+
* @param id - Invoice UUID
|
|
1100
|
+
* @param requestOptions - Request options
|
|
1101
|
+
* @returns Audit trail entries with integrity validation
|
|
1102
|
+
*
|
|
1103
|
+
* @example
|
|
1104
|
+
* ```typescript
|
|
1105
|
+
* const { data: entries, integrity_valid } = await client.invoices.auditTrail(
|
|
1106
|
+
* 'invoice-uuid'
|
|
1107
|
+
* );
|
|
1108
|
+
*
|
|
1109
|
+
* if (integrity_valid) {
|
|
1110
|
+
* console.log('Audit trail is valid');
|
|
1111
|
+
* entries.forEach(e => console.log(e.action, e.created_at));
|
|
1112
|
+
* }
|
|
1113
|
+
* ```
|
|
1114
|
+
*/
|
|
1115
|
+
async auditTrail(id, requestOptions) {
|
|
1116
|
+
return this.http.get(
|
|
1117
|
+
`/invoices/${id}/audit-trail`,
|
|
1118
|
+
void 0,
|
|
1119
|
+
requestOptions
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Convert invoice to another format
|
|
1124
|
+
*
|
|
1125
|
+
* @param input - Conversion parameters
|
|
1126
|
+
* @param requestOptions - Request options
|
|
1127
|
+
* @returns Conversion status
|
|
1128
|
+
*
|
|
1129
|
+
* @example
|
|
1130
|
+
* ```typescript
|
|
1131
|
+
* await client.invoices.convert({
|
|
1132
|
+
* invoice_id: 'invoice-uuid',
|
|
1133
|
+
* target_format: 'ubl'
|
|
1134
|
+
* });
|
|
1135
|
+
* ```
|
|
1136
|
+
*/
|
|
1137
|
+
async convert(input, requestOptions) {
|
|
1138
|
+
return this.http.post("/invoices/convert", input, requestOptions);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// src/resources/signatures.ts
|
|
1143
|
+
var SignaturesResource = class {
|
|
1144
|
+
constructor(http) {
|
|
1145
|
+
this.http = http;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* List signature requests with optional filtering
|
|
1149
|
+
*
|
|
1150
|
+
* @param options - Filter and pagination options
|
|
1151
|
+
* @param requestOptions - Request options
|
|
1152
|
+
* @returns Paginated list of signatures
|
|
1153
|
+
*
|
|
1154
|
+
* @example
|
|
1155
|
+
* ```typescript
|
|
1156
|
+
* const { data, meta } = await client.signatures.list({
|
|
1157
|
+
* status: 'pending',
|
|
1158
|
+
* per_page: 25
|
|
1159
|
+
* });
|
|
1160
|
+
* console.log(`${meta.total} pending signatures`);
|
|
1161
|
+
* ```
|
|
1162
|
+
*/
|
|
1163
|
+
async list(options = {}, requestOptions) {
|
|
1164
|
+
return this.http.get(
|
|
1165
|
+
"/signatures",
|
|
1166
|
+
options,
|
|
1167
|
+
requestOptions
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Get a specific signature by ID
|
|
1172
|
+
*
|
|
1173
|
+
* @param id - Signature UUID
|
|
1174
|
+
* @param requestOptions - Request options
|
|
1175
|
+
* @returns Signature details with signers
|
|
1176
|
+
*
|
|
1177
|
+
* @example
|
|
1178
|
+
* ```typescript
|
|
1179
|
+
* const { data: signature } = await client.signatures.get('uuid-here');
|
|
1180
|
+
* signature.signers?.forEach(signer => {
|
|
1181
|
+
* console.log(`${signer.full_name}: ${signer.status}`);
|
|
1182
|
+
* });
|
|
1183
|
+
* ```
|
|
1184
|
+
*/
|
|
1185
|
+
async get(id, requestOptions) {
|
|
1186
|
+
return this.http.get(
|
|
1187
|
+
`/signatures/${id}`,
|
|
1188
|
+
void 0,
|
|
1189
|
+
requestOptions
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Create a new signature request
|
|
1194
|
+
*
|
|
1195
|
+
* Note: This endpoint requires API key authentication.
|
|
1196
|
+
* Creating a signature in production mode will debit your balance.
|
|
1197
|
+
*
|
|
1198
|
+
* @param input - Signature creation data
|
|
1199
|
+
* @param requestOptions - Request options
|
|
1200
|
+
* @returns Created signature
|
|
1201
|
+
*
|
|
1202
|
+
* @example
|
|
1203
|
+
* ```typescript
|
|
1204
|
+
* import { readFileSync } from 'fs';
|
|
1205
|
+
*
|
|
1206
|
+
* const pdfContent = readFileSync('contract.pdf');
|
|
1207
|
+
* const { data: signature } = await client.signatures.create({
|
|
1208
|
+
* title: 'Service Agreement',
|
|
1209
|
+
* description: 'Annual service contract',
|
|
1210
|
+
* document: pdfContent.toString('base64'),
|
|
1211
|
+
* document_name: 'contract.pdf',
|
|
1212
|
+
* signers: [
|
|
1213
|
+
* {
|
|
1214
|
+
* first_name: 'Alice',
|
|
1215
|
+
* last_name: 'Smith',
|
|
1216
|
+
* email: 'alice@example.com',
|
|
1217
|
+
* auth_method: 'email'
|
|
1218
|
+
* },
|
|
1219
|
+
* {
|
|
1220
|
+
* first_name: 'Bob',
|
|
1221
|
+
* last_name: 'Jones',
|
|
1222
|
+
* phone: '+33612345678',
|
|
1223
|
+
* auth_method: 'sms'
|
|
1224
|
+
* }
|
|
1225
|
+
* ],
|
|
1226
|
+
* ui_config: {
|
|
1227
|
+
* logo_url: 'https://mycompany.com/logo.png',
|
|
1228
|
+
* primary_color: '#3b82f6',
|
|
1229
|
+
* company_name: 'My Company'
|
|
1230
|
+
* },
|
|
1231
|
+
* redirect_complete_url: 'https://myapp.com/signed',
|
|
1232
|
+
* redirect_cancel_url: 'https://myapp.com/cancelled'
|
|
1233
|
+
* });
|
|
1234
|
+
*
|
|
1235
|
+
* // Send signing URLs to signers
|
|
1236
|
+
* signature.signers?.forEach(signer => {
|
|
1237
|
+
* console.log(`${signer.email}: ${signer.signing_url}`);
|
|
1238
|
+
* });
|
|
1239
|
+
* ```
|
|
1240
|
+
*/
|
|
1241
|
+
async create(input, requestOptions) {
|
|
1242
|
+
return this.http.post(
|
|
1243
|
+
"/signatures",
|
|
1244
|
+
input,
|
|
1245
|
+
requestOptions
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Download signature files
|
|
1250
|
+
*
|
|
1251
|
+
* @param id - Signature UUID
|
|
1252
|
+
* @param type - File type to download
|
|
1253
|
+
* @param requestOptions - Request options
|
|
1254
|
+
* @returns Temporary download URL
|
|
1255
|
+
*
|
|
1256
|
+
* @example
|
|
1257
|
+
* ```typescript
|
|
1258
|
+
* // Download signed document
|
|
1259
|
+
* const { url } = await client.signatures.download(
|
|
1260
|
+
* 'signature-uuid',
|
|
1261
|
+
* 'signed'
|
|
1262
|
+
* );
|
|
1263
|
+
*
|
|
1264
|
+
* // Download audit trail (proof file)
|
|
1265
|
+
* const { url: auditUrl } = await client.signatures.download(
|
|
1266
|
+
* 'signature-uuid',
|
|
1267
|
+
* 'audit_trail'
|
|
1268
|
+
* );
|
|
1269
|
+
* ```
|
|
1270
|
+
*/
|
|
1271
|
+
async download(id, type, requestOptions) {
|
|
1272
|
+
return this.http.get(
|
|
1273
|
+
`/signatures/${id}/download/${type}`,
|
|
1274
|
+
void 0,
|
|
1275
|
+
requestOptions
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Send reminder to pending signers
|
|
1280
|
+
*
|
|
1281
|
+
* @param id - Signature UUID
|
|
1282
|
+
* @param requestOptions - Request options
|
|
1283
|
+
* @returns Number of signers reminded
|
|
1284
|
+
*
|
|
1285
|
+
* @example
|
|
1286
|
+
* ```typescript
|
|
1287
|
+
* const { signers_reminded } = await client.signatures.remind('uuid');
|
|
1288
|
+
* console.log(`Reminded ${signers_reminded} signers`);
|
|
1289
|
+
* ```
|
|
1290
|
+
*/
|
|
1291
|
+
async remind(id, requestOptions) {
|
|
1292
|
+
return this.http.post(
|
|
1293
|
+
`/signatures/${id}/remind`,
|
|
1294
|
+
void 0,
|
|
1295
|
+
requestOptions
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Cancel a signature request
|
|
1300
|
+
*
|
|
1301
|
+
* Note: Cannot cancel completed signatures.
|
|
1302
|
+
*
|
|
1303
|
+
* @param id - Signature UUID
|
|
1304
|
+
* @param requestOptions - Request options
|
|
1305
|
+
* @returns Cancellation confirmation
|
|
1306
|
+
*
|
|
1307
|
+
* @example
|
|
1308
|
+
* ```typescript
|
|
1309
|
+
* await client.signatures.cancel('signature-uuid');
|
|
1310
|
+
* console.log('Signature cancelled');
|
|
1311
|
+
* ```
|
|
1312
|
+
*/
|
|
1313
|
+
async cancel(id, requestOptions) {
|
|
1314
|
+
return this.http.post(
|
|
1315
|
+
`/signatures/${id}/cancel`,
|
|
1316
|
+
void 0,
|
|
1317
|
+
requestOptions
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
// src/resources/webhooks.ts
|
|
1323
|
+
var WebhooksResource = class {
|
|
1324
|
+
constructor(http) {
|
|
1325
|
+
this.http = http;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* List all webhooks
|
|
1329
|
+
*
|
|
1330
|
+
* @param options - Filter options
|
|
1331
|
+
* @param requestOptions - Request options
|
|
1332
|
+
* @returns List of webhooks
|
|
1333
|
+
*
|
|
1334
|
+
* @example
|
|
1335
|
+
* ```typescript
|
|
1336
|
+
* const { data: webhooks } = await client.webhooks.list();
|
|
1337
|
+
* webhooks.forEach(wh => {
|
|
1338
|
+
* console.log(`${wh.url}: ${wh.is_active ? 'active' : 'inactive'}`);
|
|
1339
|
+
* });
|
|
1340
|
+
* ```
|
|
1341
|
+
*/
|
|
1342
|
+
async list(options = {}, requestOptions) {
|
|
1343
|
+
return this.http.get(
|
|
1344
|
+
"/webhooks",
|
|
1345
|
+
options,
|
|
1346
|
+
requestOptions
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Create a new webhook
|
|
1351
|
+
*
|
|
1352
|
+
* Important: The secret is only returned once during creation.
|
|
1353
|
+
* Store it securely - you'll need it to verify webhook signatures.
|
|
1354
|
+
*
|
|
1355
|
+
* @param input - Webhook configuration
|
|
1356
|
+
* @param requestOptions - Request options
|
|
1357
|
+
* @returns Created webhook with secret
|
|
1358
|
+
*
|
|
1359
|
+
* @example
|
|
1360
|
+
* ```typescript
|
|
1361
|
+
* const { data: webhook } = await client.webhooks.create({
|
|
1362
|
+
* url: 'https://myapp.com/webhooks/scell',
|
|
1363
|
+
* events: [
|
|
1364
|
+
* 'invoice.created',
|
|
1365
|
+
* 'invoice.validated',
|
|
1366
|
+
* 'signature.completed',
|
|
1367
|
+
* 'balance.low'
|
|
1368
|
+
* ],
|
|
1369
|
+
* environment: 'production',
|
|
1370
|
+
* headers: {
|
|
1371
|
+
* 'X-Custom-Auth': 'my-secret-token'
|
|
1372
|
+
* },
|
|
1373
|
+
* retry_count: 5,
|
|
1374
|
+
* timeout_seconds: 30
|
|
1375
|
+
* });
|
|
1376
|
+
*
|
|
1377
|
+
* // IMPORTANT: Store this secret securely!
|
|
1378
|
+
* await saveWebhookSecret(webhook.id, webhook.secret);
|
|
1379
|
+
* ```
|
|
1380
|
+
*/
|
|
1381
|
+
async create(input, requestOptions) {
|
|
1382
|
+
return this.http.post(
|
|
1383
|
+
"/webhooks",
|
|
1384
|
+
input,
|
|
1385
|
+
requestOptions
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Update a webhook
|
|
1390
|
+
*
|
|
1391
|
+
* @param id - Webhook UUID
|
|
1392
|
+
* @param input - Fields to update
|
|
1393
|
+
* @param requestOptions - Request options
|
|
1394
|
+
* @returns Updated webhook
|
|
1395
|
+
*
|
|
1396
|
+
* @example
|
|
1397
|
+
* ```typescript
|
|
1398
|
+
* // Disable a webhook temporarily
|
|
1399
|
+
* await client.webhooks.update('webhook-uuid', {
|
|
1400
|
+
* is_active: false
|
|
1401
|
+
* });
|
|
1402
|
+
*
|
|
1403
|
+
* // Update events
|
|
1404
|
+
* await client.webhooks.update('webhook-uuid', {
|
|
1405
|
+
* events: ['invoice.validated', 'signature.completed']
|
|
1406
|
+
* });
|
|
1407
|
+
* ```
|
|
1408
|
+
*/
|
|
1409
|
+
async update(id, input, requestOptions) {
|
|
1410
|
+
return this.http.put(
|
|
1411
|
+
`/webhooks/${id}`,
|
|
1412
|
+
input,
|
|
1413
|
+
requestOptions
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Delete a webhook
|
|
1418
|
+
*
|
|
1419
|
+
* @param id - Webhook UUID
|
|
1420
|
+
* @param requestOptions - Request options
|
|
1421
|
+
* @returns Deletion confirmation
|
|
1422
|
+
*
|
|
1423
|
+
* @example
|
|
1424
|
+
* ```typescript
|
|
1425
|
+
* await client.webhooks.delete('webhook-uuid');
|
|
1426
|
+
* ```
|
|
1427
|
+
*/
|
|
1428
|
+
async delete(id, requestOptions) {
|
|
1429
|
+
return this.http.delete(`/webhooks/${id}`, requestOptions);
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Regenerate webhook secret
|
|
1433
|
+
*
|
|
1434
|
+
* Use this if your secret has been compromised.
|
|
1435
|
+
* The old secret will immediately stop working.
|
|
1436
|
+
*
|
|
1437
|
+
* @param id - Webhook UUID
|
|
1438
|
+
* @param requestOptions - Request options
|
|
1439
|
+
* @returns Webhook with new secret
|
|
1440
|
+
*
|
|
1441
|
+
* @example
|
|
1442
|
+
* ```typescript
|
|
1443
|
+
* const { data: webhook } = await client.webhooks.regenerateSecret(
|
|
1444
|
+
* 'webhook-uuid'
|
|
1445
|
+
* );
|
|
1446
|
+
*
|
|
1447
|
+
* // Update your stored secret
|
|
1448
|
+
* await updateWebhookSecret(webhook.id, webhook.secret);
|
|
1449
|
+
* ```
|
|
1450
|
+
*/
|
|
1451
|
+
async regenerateSecret(id, requestOptions) {
|
|
1452
|
+
return this.http.post(
|
|
1453
|
+
`/webhooks/${id}/regenerate-secret`,
|
|
1454
|
+
void 0,
|
|
1455
|
+
requestOptions
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Test webhook by sending a test event
|
|
1460
|
+
*
|
|
1461
|
+
* @param id - Webhook UUID
|
|
1462
|
+
* @param requestOptions - Request options
|
|
1463
|
+
* @returns Test result
|
|
1464
|
+
*
|
|
1465
|
+
* @example
|
|
1466
|
+
* ```typescript
|
|
1467
|
+
* const result = await client.webhooks.test('webhook-uuid');
|
|
1468
|
+
*
|
|
1469
|
+
* if (result.success) {
|
|
1470
|
+
* console.log(`Success! Response time: ${result.response_time_ms}ms`);
|
|
1471
|
+
* } else {
|
|
1472
|
+
* console.log(`Failed: ${result.error}`);
|
|
1473
|
+
* }
|
|
1474
|
+
* ```
|
|
1475
|
+
*/
|
|
1476
|
+
async test(id, requestOptions) {
|
|
1477
|
+
return this.http.post(
|
|
1478
|
+
`/webhooks/${id}/test`,
|
|
1479
|
+
void 0,
|
|
1480
|
+
requestOptions
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Get webhook delivery logs
|
|
1485
|
+
*
|
|
1486
|
+
* @param id - Webhook UUID
|
|
1487
|
+
* @param options - Pagination options
|
|
1488
|
+
* @param requestOptions - Request options
|
|
1489
|
+
* @returns Paginated list of logs
|
|
1490
|
+
*
|
|
1491
|
+
* @example
|
|
1492
|
+
* ```typescript
|
|
1493
|
+
* const { data: logs } = await client.webhooks.logs('webhook-uuid', {
|
|
1494
|
+
* per_page: 50
|
|
1495
|
+
* });
|
|
1496
|
+
*
|
|
1497
|
+
* logs.forEach(log => {
|
|
1498
|
+
* const status = log.success ? 'OK' : 'FAILED';
|
|
1499
|
+
* console.log(`${log.event} - ${status} (${log.response_time_ms}ms)`);
|
|
1500
|
+
* });
|
|
1501
|
+
* ```
|
|
1502
|
+
*/
|
|
1503
|
+
async logs(id, options = {}, requestOptions) {
|
|
1504
|
+
return this.http.get(
|
|
1505
|
+
`/webhooks/${id}/logs`,
|
|
1506
|
+
options,
|
|
1507
|
+
requestOptions
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
// src/utils/webhook-verify.ts
|
|
1513
|
+
function parseSignatureHeader(header) {
|
|
1514
|
+
const parts = header.split(",");
|
|
1515
|
+
let timestamp;
|
|
1516
|
+
let signature;
|
|
1517
|
+
for (const part of parts) {
|
|
1518
|
+
const splitIndex = part.indexOf("=");
|
|
1519
|
+
if (splitIndex === -1) continue;
|
|
1520
|
+
const key = part.substring(0, splitIndex);
|
|
1521
|
+
const value = part.substring(splitIndex + 1);
|
|
1522
|
+
if (key === "t") {
|
|
1523
|
+
timestamp = parseInt(value, 10);
|
|
1524
|
+
} else if (key === "v1") {
|
|
1525
|
+
signature = value;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (timestamp === void 0 || signature === void 0) {
|
|
1529
|
+
return null;
|
|
1530
|
+
}
|
|
1531
|
+
return { timestamp, signature };
|
|
1532
|
+
}
|
|
1533
|
+
async function computeSignature(payload, timestamp, secret) {
|
|
1534
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
1535
|
+
const encoder = new TextEncoder();
|
|
1536
|
+
const keyData = encoder.encode(secret);
|
|
1537
|
+
const messageData = encoder.encode(signedPayload);
|
|
1538
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
1539
|
+
"raw",
|
|
1540
|
+
keyData,
|
|
1541
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1542
|
+
false,
|
|
1543
|
+
["sign"]
|
|
1544
|
+
);
|
|
1545
|
+
const signatureBuffer = await crypto.subtle.sign(
|
|
1546
|
+
"HMAC",
|
|
1547
|
+
cryptoKey,
|
|
1548
|
+
messageData
|
|
1549
|
+
);
|
|
1550
|
+
return Array.from(new Uint8Array(signatureBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1551
|
+
}
|
|
1552
|
+
function secureCompare(a, b) {
|
|
1553
|
+
if (a.length !== b.length) {
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
let result = 0;
|
|
1557
|
+
for (let i = 0; i < a.length; i++) {
|
|
1558
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1559
|
+
}
|
|
1560
|
+
return result === 0;
|
|
1561
|
+
}
|
|
1562
|
+
var ScellWebhooks = {
|
|
1563
|
+
/**
|
|
1564
|
+
* Verify webhook signature
|
|
1565
|
+
*
|
|
1566
|
+
* @param payload - Raw request body as string
|
|
1567
|
+
* @param signature - Value of X-Scell-Signature header
|
|
1568
|
+
* @param secret - Your webhook secret (whsec_...)
|
|
1569
|
+
* @param options - Verification options
|
|
1570
|
+
* @returns True if signature is valid
|
|
1571
|
+
*
|
|
1572
|
+
* @example
|
|
1573
|
+
* ```typescript
|
|
1574
|
+
* const isValid = await ScellWebhooks.verifySignature(
|
|
1575
|
+
* rawBody,
|
|
1576
|
+
* req.headers['x-scell-signature'],
|
|
1577
|
+
* 'whsec_abc123...'
|
|
1578
|
+
* );
|
|
1579
|
+
* ```
|
|
1580
|
+
*/
|
|
1581
|
+
async verifySignature(payload, signature, secret, options = {}) {
|
|
1582
|
+
const { tolerance = 300 } = options;
|
|
1583
|
+
const parsed = parseSignatureHeader(signature);
|
|
1584
|
+
if (!parsed) {
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
const { timestamp, signature: providedSignature } = parsed;
|
|
1588
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1589
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
1590
|
+
return false;
|
|
1591
|
+
}
|
|
1592
|
+
const expectedSignature = await computeSignature(payload, timestamp, secret);
|
|
1593
|
+
return secureCompare(expectedSignature, providedSignature);
|
|
1594
|
+
},
|
|
1595
|
+
/**
|
|
1596
|
+
* Verify webhook signature synchronously using Node.js crypto
|
|
1597
|
+
* (Only works in Node.js environment)
|
|
1598
|
+
*
|
|
1599
|
+
* @param payload - Raw request body as string
|
|
1600
|
+
* @param signature - Value of X-Scell-Signature header
|
|
1601
|
+
* @param secret - Your webhook secret (whsec_...)
|
|
1602
|
+
* @param options - Verification options
|
|
1603
|
+
* @returns True if signature is valid
|
|
1604
|
+
*/
|
|
1605
|
+
verifySignatureSync(payload, signature, secret, options = {}) {
|
|
1606
|
+
const { tolerance = 300 } = options;
|
|
1607
|
+
const parsed = parseSignatureHeader(signature);
|
|
1608
|
+
if (!parsed) {
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
const { timestamp, signature: providedSignature } = parsed;
|
|
1612
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1613
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
const crypto2 = __require("crypto");
|
|
1617
|
+
const signedPayload = `${timestamp}.${payload}`;
|
|
1618
|
+
const expectedSignature = crypto2.createHmac("sha256", secret).update(signedPayload).digest("hex");
|
|
1619
|
+
try {
|
|
1620
|
+
return crypto2.timingSafeEqual(
|
|
1621
|
+
Buffer.from(expectedSignature),
|
|
1622
|
+
Buffer.from(providedSignature)
|
|
1623
|
+
);
|
|
1624
|
+
} catch {
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1627
|
+
},
|
|
1628
|
+
/**
|
|
1629
|
+
* Parse webhook payload
|
|
1630
|
+
*
|
|
1631
|
+
* @param payload - Raw request body as string
|
|
1632
|
+
* @returns Parsed webhook payload
|
|
1633
|
+
*
|
|
1634
|
+
* @example
|
|
1635
|
+
* ```typescript
|
|
1636
|
+
* const event = ScellWebhooks.parsePayload<InvoiceWebhookData>(rawBody);
|
|
1637
|
+
* if (event.event === 'invoice.validated') {
|
|
1638
|
+
* console.log('Invoice validated:', event.data.invoice_number);
|
|
1639
|
+
* }
|
|
1640
|
+
* ```
|
|
1641
|
+
*/
|
|
1642
|
+
parsePayload(payload) {
|
|
1643
|
+
return JSON.parse(payload);
|
|
1644
|
+
},
|
|
1645
|
+
/**
|
|
1646
|
+
* Construct a test webhook event for local testing
|
|
1647
|
+
*
|
|
1648
|
+
* @param event - Event type
|
|
1649
|
+
* @param data - Event data
|
|
1650
|
+
* @param secret - Webhook secret for signing
|
|
1651
|
+
* @returns Object with payload and headers for testing
|
|
1652
|
+
*/
|
|
1653
|
+
async constructTestEvent(event, data, secret) {
|
|
1654
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1655
|
+
const webhookPayload = {
|
|
1656
|
+
event,
|
|
1657
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1658
|
+
data
|
|
1659
|
+
};
|
|
1660
|
+
const payload = JSON.stringify(webhookPayload);
|
|
1661
|
+
const signature = await computeSignature(payload, timestamp, secret);
|
|
1662
|
+
return {
|
|
1663
|
+
payload,
|
|
1664
|
+
headers: {
|
|
1665
|
+
"X-Scell-Signature": `t=${timestamp},v1=${signature}`,
|
|
1666
|
+
"X-Scell-Event": event,
|
|
1667
|
+
"X-Scell-Delivery": crypto.randomUUID(),
|
|
1668
|
+
"Content-Type": "application/json"
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
// src/index.ts
|
|
1675
|
+
init_errors();
|
|
1676
|
+
var ScellClient = class {
|
|
1677
|
+
http;
|
|
1678
|
+
/** Authentication operations */
|
|
1679
|
+
auth;
|
|
1680
|
+
/** Company management */
|
|
1681
|
+
companies;
|
|
1682
|
+
/** API key management */
|
|
1683
|
+
apiKeys;
|
|
1684
|
+
/** Balance and transactions */
|
|
1685
|
+
balance;
|
|
1686
|
+
/** Webhook management */
|
|
1687
|
+
webhooks;
|
|
1688
|
+
/** Invoice listing (read-only via dashboard) */
|
|
1689
|
+
invoices;
|
|
1690
|
+
/** Signature listing (read-only via dashboard) */
|
|
1691
|
+
signatures;
|
|
1692
|
+
/**
|
|
1693
|
+
* Create a new Scell Dashboard Client
|
|
1694
|
+
*
|
|
1695
|
+
* @param token - Bearer token from login
|
|
1696
|
+
* @param config - Client configuration
|
|
1697
|
+
*
|
|
1698
|
+
* @example
|
|
1699
|
+
* ```typescript
|
|
1700
|
+
* const client = new ScellClient('your-bearer-token', {
|
|
1701
|
+
* baseUrl: 'https://api.scell.io/api/v1',
|
|
1702
|
+
* timeout: 30000,
|
|
1703
|
+
* retry: { maxRetries: 3 }
|
|
1704
|
+
* });
|
|
1705
|
+
* ```
|
|
1706
|
+
*/
|
|
1707
|
+
constructor(token, config = {}) {
|
|
1708
|
+
this.http = new HttpClient("bearer", token, config);
|
|
1709
|
+
this.auth = new AuthResource(this.http);
|
|
1710
|
+
this.companies = new CompaniesResource(this.http);
|
|
1711
|
+
this.apiKeys = new ApiKeysResource(this.http);
|
|
1712
|
+
this.balance = new BalanceResource(this.http);
|
|
1713
|
+
this.webhooks = new WebhooksResource(this.http);
|
|
1714
|
+
this.invoices = new InvoicesResource(this.http);
|
|
1715
|
+
this.signatures = new SignaturesResource(this.http);
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
var ScellApiClient = class {
|
|
1719
|
+
http;
|
|
1720
|
+
/** Invoice operations (create, download, convert) */
|
|
1721
|
+
invoices;
|
|
1722
|
+
/** Signature operations (create, download, remind, cancel) */
|
|
1723
|
+
signatures;
|
|
1724
|
+
/**
|
|
1725
|
+
* Create a new Scell API Client
|
|
1726
|
+
*
|
|
1727
|
+
* @param apiKey - Your API key (from dashboard)
|
|
1728
|
+
* @param config - Client configuration
|
|
1729
|
+
*
|
|
1730
|
+
* @example
|
|
1731
|
+
* ```typescript
|
|
1732
|
+
* // Production client
|
|
1733
|
+
* const client = new ScellApiClient('sk_live_xxx');
|
|
1734
|
+
*
|
|
1735
|
+
* // Sandbox client
|
|
1736
|
+
* const sandboxClient = new ScellApiClient('sk_test_xxx', {
|
|
1737
|
+
* baseUrl: 'https://api.scell.io/api/v1/sandbox'
|
|
1738
|
+
* });
|
|
1739
|
+
* ```
|
|
1740
|
+
*/
|
|
1741
|
+
constructor(apiKey, config = {}) {
|
|
1742
|
+
this.http = new HttpClient("api-key", apiKey, config);
|
|
1743
|
+
this.invoices = new InvoicesResource(this.http);
|
|
1744
|
+
this.signatures = new SignaturesResource(this.http);
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
|
|
1748
|
+
exports.ScellApiClient = ScellApiClient;
|
|
1749
|
+
exports.ScellAuth = ScellAuth;
|
|
1750
|
+
exports.ScellClient = ScellClient;
|
|
1751
|
+
exports.ScellWebhooks = ScellWebhooks;
|
|
1752
|
+
exports.createRetryWrapper = createRetryWrapper;
|
|
1753
|
+
exports.withRetry = withRetry;
|
|
1754
|
+
//# sourceMappingURL=index.js.map
|
|
1755
|
+
//# sourceMappingURL=index.js.map
|