@taxora/sdk 1.1.0 → 1.3.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/CHANGELOG.md +22 -0
- package/README.md +282 -1
- package/dist/index.cjs +1243 -190
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +684 -29
- package/dist/index.d.ts +684 -29
- package/dist/index.js +1216 -189
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -60,37 +60,293 @@ var AuthenticationException = class extends TaxoraException {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
// src/exceptions/apiErrorMessage.ts
|
|
64
|
+
var MAX_LENGTH = 500;
|
|
65
|
+
var MAX_PLAIN_LENGTH = 200;
|
|
66
|
+
var REASON_PHRASES = {
|
|
67
|
+
400: "Bad Request",
|
|
68
|
+
401: "Unauthorized",
|
|
69
|
+
403: "Forbidden",
|
|
70
|
+
404: "Not Found",
|
|
71
|
+
405: "Method Not Allowed",
|
|
72
|
+
408: "Request Timeout",
|
|
73
|
+
409: "Conflict",
|
|
74
|
+
413: "Payload Too Large",
|
|
75
|
+
422: "Unprocessable Entity",
|
|
76
|
+
429: "Too Many Requests",
|
|
77
|
+
500: "Internal Server Error",
|
|
78
|
+
502: "Bad Gateway",
|
|
79
|
+
503: "Service Unavailable",
|
|
80
|
+
504: "Gateway Timeout"
|
|
81
|
+
};
|
|
82
|
+
function describeApiError(body, statusCode) {
|
|
83
|
+
return jsonErrorMessage(body) ?? plainErrorMessage(body) ?? statusErrorMessage(statusCode);
|
|
84
|
+
}
|
|
85
|
+
function detailApiError(body, statusCode) {
|
|
86
|
+
return jsonErrorMessage(body) ?? plainErrorMessage(body) ?? statusPhrase(statusCode);
|
|
87
|
+
}
|
|
88
|
+
function jsonErrorMessage(body) {
|
|
89
|
+
if (body === null || body === void 0 || body.trim() === "") return null;
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(body);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
97
|
+
const payload = parsed;
|
|
98
|
+
for (const key of ["message", "error"]) {
|
|
99
|
+
const value = payload[key];
|
|
100
|
+
if (typeof value === "string") {
|
|
101
|
+
const message = sanitize(value);
|
|
102
|
+
if (message !== null) return message;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const errors = payload["errors"];
|
|
106
|
+
if (typeof errors === "object" && errors !== null) {
|
|
107
|
+
return fromErrorBag(errors);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function statusErrorMessage(statusCode) {
|
|
112
|
+
return `Taxora API request failed (${statusPhrase(statusCode)}).`;
|
|
113
|
+
}
|
|
114
|
+
function statusPhrase(statusCode) {
|
|
115
|
+
const phrase = REASON_PHRASES[statusCode];
|
|
116
|
+
return `HTTP ${statusCode}${phrase ? ` ${phrase}` : ""}`;
|
|
117
|
+
}
|
|
118
|
+
function plainErrorMessage(body) {
|
|
119
|
+
if (body === null || body === void 0) return null;
|
|
120
|
+
const value = body.replace(/\s+/g, " ").trim();
|
|
121
|
+
if (value === "" || value.length > MAX_PLAIN_LENGTH) return null;
|
|
122
|
+
if (value.includes("<") || value.includes("{")) return null;
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
function fromErrorBag(errors) {
|
|
126
|
+
for (const [field, messages] of Object.entries(errors)) {
|
|
127
|
+
for (const message of Array.isArray(messages) ? messages : [messages]) {
|
|
128
|
+
if (typeof message !== "string") continue;
|
|
129
|
+
const sanitized = sanitize(message);
|
|
130
|
+
if (sanitized === null) continue;
|
|
131
|
+
return field !== "" ? `${field}: ${sanitized}` : sanitized;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function sanitize(value) {
|
|
137
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
138
|
+
if (collapsed === "") return null;
|
|
139
|
+
if (/<!doctype\s+html|<html[\s>]/i.test(collapsed)) return null;
|
|
140
|
+
return collapsed.length > MAX_LENGTH ? `${collapsed.slice(0, MAX_LENGTH - 1)}\u2026` : collapsed;
|
|
141
|
+
}
|
|
142
|
+
|
|
63
143
|
// src/exceptions/HttpException.ts
|
|
64
|
-
var HttpException = class extends TaxoraException {
|
|
144
|
+
var HttpException = class _HttpException extends TaxoraException {
|
|
65
145
|
statusCode;
|
|
66
146
|
responseBody;
|
|
67
|
-
|
|
147
|
+
/** Raw `Retry-After` header of the failed response, when it carried one. */
|
|
148
|
+
retryAfter;
|
|
149
|
+
constructor(message, statusCode, responseBody, context = {}, retryAfter = null) {
|
|
68
150
|
super(message, context);
|
|
69
151
|
this.name = "HttpException";
|
|
70
152
|
this.statusCode = statusCode;
|
|
71
153
|
this.responseBody = responseBody;
|
|
154
|
+
this.retryAfter = retryAfter;
|
|
72
155
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
73
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The final failure after the SDK retried a transient gateway error, e.g.
|
|
159
|
+
* "Taxora VAT validation failed after 3 attempts (HTTP 504 Gateway Timeout)."
|
|
160
|
+
*/
|
|
161
|
+
static afterAttempts(operation, attempts, last) {
|
|
162
|
+
const detail = detailApiError(last.getResponseBody(), last.getStatusCode());
|
|
163
|
+
return new _HttpException(
|
|
164
|
+
`Taxora ${operation} failed after ${attempts} attempts (${detail}).`,
|
|
165
|
+
last.getStatusCode(),
|
|
166
|
+
last.getResponseBody(),
|
|
167
|
+
{ ...last.context, attempts, cause: last },
|
|
168
|
+
last.getRetryAfter()
|
|
169
|
+
);
|
|
170
|
+
}
|
|
74
171
|
getStatusCode() {
|
|
75
172
|
return this.statusCode;
|
|
76
173
|
}
|
|
77
174
|
getResponseBody() {
|
|
78
175
|
return this.responseBody;
|
|
79
176
|
}
|
|
177
|
+
getRetryAfter() {
|
|
178
|
+
return this.retryAfter;
|
|
179
|
+
}
|
|
80
180
|
};
|
|
81
181
|
|
|
182
|
+
// src/http/RetryPolicy.ts
|
|
183
|
+
var DEFAULT_RETRYABLE_STATUS_CODES = [502, 503, 504];
|
|
184
|
+
var RETRY_AFTER_ONLY_STATUS_CODES = [429];
|
|
185
|
+
var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
186
|
+
"ECONNRESET",
|
|
187
|
+
"ECONNREFUSED",
|
|
188
|
+
"ECONNABORTED",
|
|
189
|
+
"EPIPE",
|
|
190
|
+
"ETIMEDOUT",
|
|
191
|
+
"EAI_AGAIN",
|
|
192
|
+
"EHOSTUNREACH",
|
|
193
|
+
"ENETUNREACH",
|
|
194
|
+
"ENETDOWN",
|
|
195
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
196
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
197
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
198
|
+
"UND_ERR_SOCKET"
|
|
199
|
+
]);
|
|
200
|
+
var RetryPolicy = class _RetryPolicy {
|
|
201
|
+
maxAttempts;
|
|
202
|
+
initialDelayMs;
|
|
203
|
+
backoffMultiplier;
|
|
204
|
+
maxDelayMs;
|
|
205
|
+
retryableStatusCodes;
|
|
206
|
+
respectRetryAfter;
|
|
207
|
+
maxRetryAfterMs;
|
|
208
|
+
retryOnNetworkErrors;
|
|
209
|
+
sleeper;
|
|
210
|
+
constructor(options = {}) {
|
|
211
|
+
this.maxAttempts = options.maxAttempts ?? 3;
|
|
212
|
+
this.initialDelayMs = options.initialDelayMs ?? 500;
|
|
213
|
+
this.backoffMultiplier = options.backoffMultiplier ?? 2;
|
|
214
|
+
this.maxDelayMs = options.maxDelayMs ?? 5e3;
|
|
215
|
+
this.retryableStatusCodes = options.retryableStatusCodes ?? DEFAULT_RETRYABLE_STATUS_CODES;
|
|
216
|
+
this.respectRetryAfter = options.respectRetryAfter ?? true;
|
|
217
|
+
this.maxRetryAfterMs = options.maxRetryAfterMs ?? 1e4;
|
|
218
|
+
this.retryOnNetworkErrors = options.retryOnNetworkErrors ?? true;
|
|
219
|
+
this.sleeper = options.sleeper;
|
|
220
|
+
if (this.maxAttempts < 1) throw new Error("maxAttempts must be at least 1.");
|
|
221
|
+
if (this.initialDelayMs < 0 || this.maxDelayMs < 0) throw new Error("Delays must not be negative.");
|
|
222
|
+
if (this.backoffMultiplier < 1) throw new Error("backoffMultiplier must be at least 1.");
|
|
223
|
+
}
|
|
224
|
+
/** No retrying at all — every failure is surfaced on the first attempt. */
|
|
225
|
+
static disabled() {
|
|
226
|
+
return new _RetryPolicy({ maxAttempts: 1 });
|
|
227
|
+
}
|
|
228
|
+
/** Retry immediately, without waiting (mainly useful in tests). */
|
|
229
|
+
static withoutDelay(maxAttempts = 3) {
|
|
230
|
+
return new _RetryPolicy({ maxAttempts, initialDelayMs: 0, maxDelayMs: 0 });
|
|
231
|
+
}
|
|
232
|
+
isRetryable(statusCode, retryAfterHeader) {
|
|
233
|
+
const retryAfterMs = this.retryAfterMs(retryAfterHeader);
|
|
234
|
+
if (retryAfterMs !== null && retryAfterMs > this.maxRetryAfterMs) return false;
|
|
235
|
+
if (this.retryableStatusCodes.includes(statusCode)) return true;
|
|
236
|
+
return retryAfterMs !== null && RETRY_AFTER_ONLY_STATUS_CODES.includes(statusCode);
|
|
237
|
+
}
|
|
238
|
+
/** @param attempt Number of attempts made so far (1 = the first one just failed). */
|
|
239
|
+
shouldRetry(statusCode, attempt, retryAfterHeader) {
|
|
240
|
+
return attempt < this.maxAttempts && this.isRetryable(statusCode, retryAfterHeader);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A connection-level failure: the request never got an answer, so repeating it
|
|
244
|
+
* is as safe as repeating a gateway timeout.
|
|
245
|
+
*/
|
|
246
|
+
isRetryableError(error) {
|
|
247
|
+
if (!this.retryOnNetworkErrors || !(error instanceof Error)) return false;
|
|
248
|
+
if (error.name === "AbortError" || error.name === "TimeoutError") return true;
|
|
249
|
+
const code = error.code ?? error.cause?.code;
|
|
250
|
+
if (typeof code === "string" && NETWORK_ERROR_CODES.has(code)) return true;
|
|
251
|
+
return error instanceof TypeError && /fetch failed|network|failed to fetch/i.test(error.message);
|
|
252
|
+
}
|
|
253
|
+
shouldRetryError(error, attempt) {
|
|
254
|
+
return attempt < this.maxAttempts && this.isRetryableError(error);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* The wait the API asked for, in milliseconds — seconds or HTTP-date form — or
|
|
258
|
+
* null when there is none, it cannot be parsed, or the header is ignored.
|
|
259
|
+
*/
|
|
260
|
+
retryAfterMs(header, now = /* @__PURE__ */ new Date()) {
|
|
261
|
+
if (!this.respectRetryAfter || header === null || header === void 0) return null;
|
|
262
|
+
const value = header.trim();
|
|
263
|
+
if (value === "") return null;
|
|
264
|
+
if (/^\d+$/.test(value)) return Number(value) * 1e3;
|
|
265
|
+
const date = Date.parse(value);
|
|
266
|
+
if (Number.isNaN(date)) return null;
|
|
267
|
+
return Math.max(0, date - now.getTime());
|
|
268
|
+
}
|
|
269
|
+
/** The wait before the next attempt: what the API asked for, else exponential backoff. */
|
|
270
|
+
delayMsForAttempt(attempt, retryAfterHeader) {
|
|
271
|
+
const retryAfterMs = this.retryAfterMs(retryAfterHeader);
|
|
272
|
+
if (retryAfterMs !== null) return Math.min(retryAfterMs, this.maxRetryAfterMs);
|
|
273
|
+
if (this.initialDelayMs === 0) return 0;
|
|
274
|
+
const delay = Math.round(this.initialDelayMs * this.backoffMultiplier ** Math.max(0, attempt - 1));
|
|
275
|
+
return Math.max(0, Math.min(delay, this.maxDelayMs));
|
|
276
|
+
}
|
|
277
|
+
async sleepBeforeRetry(attempt, retryAfterHeader) {
|
|
278
|
+
const delayMs = this.delayMsForAttempt(attempt, retryAfterHeader);
|
|
279
|
+
if (delayMs <= 0) return;
|
|
280
|
+
if (this.sleeper) {
|
|
281
|
+
await this.sleeper(delayMs);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/http/withRetries.ts
|
|
289
|
+
async function withRetries(policy, operation, request) {
|
|
290
|
+
let attempt = 1;
|
|
291
|
+
for (; ; ) {
|
|
292
|
+
try {
|
|
293
|
+
return await request();
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (!(error instanceof HttpException)) {
|
|
296
|
+
if (!policy.shouldRetryError(error, attempt)) throw error;
|
|
297
|
+
await policy.sleepBeforeRetry(attempt);
|
|
298
|
+
attempt++;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const retryAfter = error.getRetryAfter();
|
|
302
|
+
if (!policy.shouldRetry(error.getStatusCode(), attempt, retryAfter)) {
|
|
303
|
+
throw attempt > 1 && policy.isRetryable(error.getStatusCode(), retryAfter) ? HttpException.afterAttempts(operation, attempt, error) : error;
|
|
304
|
+
}
|
|
305
|
+
await policy.sleepBeforeRetry(attempt, retryAfter);
|
|
306
|
+
attempt++;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function withResponseRetries(policy, operation, send) {
|
|
311
|
+
let attempt = 1;
|
|
312
|
+
for (; ; ) {
|
|
313
|
+
let response;
|
|
314
|
+
try {
|
|
315
|
+
response = await send();
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (!policy.shouldRetryError(error, attempt)) throw error;
|
|
318
|
+
await policy.sleepBeforeRetry(attempt);
|
|
319
|
+
attempt++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const retryAfter = response.headers.get("retry-after");
|
|
323
|
+
if (!policy.shouldRetry(response.status, attempt, retryAfter)) {
|
|
324
|
+
if (attempt > 1 && policy.isRetryable(response.status, retryAfter)) {
|
|
325
|
+
const body = await response.text();
|
|
326
|
+
const last = new HttpException(describeApiError(body, response.status), response.status, body, {}, retryAfter);
|
|
327
|
+
throw HttpException.afterAttempts(operation, attempt, last);
|
|
328
|
+
}
|
|
329
|
+
return response;
|
|
330
|
+
}
|
|
331
|
+
await policy.sleepBeforeRetry(attempt, retryAfter);
|
|
332
|
+
attempt++;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
82
336
|
// src/endpoints/AuthEndpoint.ts
|
|
83
337
|
var AuthEndpoint = class {
|
|
84
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
338
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
85
339
|
this.baseUrl = baseUrl;
|
|
86
340
|
this.apiKey = apiKey;
|
|
87
341
|
this.tokenStorage = tokenStorage;
|
|
88
342
|
this.httpClient = httpClient;
|
|
343
|
+
this.retryPolicy = retryPolicy;
|
|
89
344
|
}
|
|
90
345
|
baseUrl;
|
|
91
346
|
apiKey;
|
|
92
347
|
tokenStorage;
|
|
93
348
|
httpClient;
|
|
349
|
+
retryPolicy;
|
|
94
350
|
async login(identifier, password, device, loginIdentifier = LoginIdentifier.EMAIL) {
|
|
95
351
|
const deviceName = device ?? hostname();
|
|
96
352
|
const body = {
|
|
@@ -103,24 +359,32 @@ var AuthEndpoint = class {
|
|
|
103
359
|
} else {
|
|
104
360
|
body["email"] = identifier;
|
|
105
361
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const responseText = await response.text();
|
|
114
|
-
if (response.status === 401) {
|
|
115
|
-
throw new AuthenticationException("Authentication failed: invalid credentials", {
|
|
116
|
-
status: 401,
|
|
117
|
-
body: responseText
|
|
362
|
+
const data = await withRetries(this.retryPolicy, "login", async () => {
|
|
363
|
+
const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/login`, {
|
|
364
|
+
headers: {
|
|
365
|
+
"Content-Type": "application/json",
|
|
366
|
+
"x-api-key": this.apiKey
|
|
367
|
+
},
|
|
368
|
+
body: JSON.stringify(body)
|
|
118
369
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
370
|
+
const responseText = await response.text();
|
|
371
|
+
if (response.status === 401) {
|
|
372
|
+
throw new AuthenticationException("Authentication failed: invalid credentials", {
|
|
373
|
+
status: 401,
|
|
374
|
+
body: responseText
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
throw new HttpException(
|
|
379
|
+
describeApiError(responseText, response.status),
|
|
380
|
+
response.status,
|
|
381
|
+
responseText,
|
|
382
|
+
{},
|
|
383
|
+
response.headers.get("retry-after")
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return this.parseResponse(responseText, response.status);
|
|
387
|
+
});
|
|
124
388
|
const token = Token.fromArray(data);
|
|
125
389
|
this.tokenStorage.set(token);
|
|
126
390
|
return token;
|
|
@@ -137,28 +401,36 @@ var AuthEndpoint = class {
|
|
|
137
401
|
if (storedToken) {
|
|
138
402
|
headers["Authorization"] = `Bearer ${storedToken.accessToken}`;
|
|
139
403
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
404
|
+
const data = await withRetries(this.retryPolicy, "token refresh", async () => {
|
|
405
|
+
const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/refresh`, { headers });
|
|
406
|
+
const responseText = await response.text();
|
|
407
|
+
if (response.status === 401) {
|
|
408
|
+
throw new AuthenticationException("Token refresh failed: unauthorized", { status: 401, body: responseText });
|
|
409
|
+
}
|
|
410
|
+
if (!response.ok) {
|
|
411
|
+
throw new HttpException(
|
|
412
|
+
describeApiError(responseText, response.status),
|
|
413
|
+
response.status,
|
|
414
|
+
responseText,
|
|
415
|
+
{},
|
|
416
|
+
response.headers.get("retry-after")
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return this.parseResponse(responseText, response.status);
|
|
420
|
+
});
|
|
149
421
|
const token = Token.fromArray(data);
|
|
150
422
|
this.tokenStorage.set(token);
|
|
151
423
|
return token;
|
|
152
424
|
}
|
|
153
|
-
parseResponse(responseText) {
|
|
425
|
+
parseResponse(responseText, statusCode) {
|
|
154
426
|
let parsed;
|
|
155
427
|
try {
|
|
156
428
|
parsed = JSON.parse(responseText);
|
|
157
429
|
} catch {
|
|
158
|
-
throw new HttpException("Invalid JSON response",
|
|
430
|
+
throw new HttpException("Invalid JSON response", statusCode, responseText);
|
|
159
431
|
}
|
|
160
432
|
if (typeof parsed !== "object" || parsed === null) {
|
|
161
|
-
throw new HttpException("Unexpected response format",
|
|
433
|
+
throw new HttpException("Unexpected response format", statusCode, responseText);
|
|
162
434
|
}
|
|
163
435
|
const obj = parsed;
|
|
164
436
|
if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
|
|
@@ -182,16 +454,18 @@ function normalizeCompanyResource(data) {
|
|
|
182
454
|
|
|
183
455
|
// src/endpoints/CompanyEndpoint.ts
|
|
184
456
|
var CompanyEndpoint = class {
|
|
185
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
457
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
186
458
|
this.baseUrl = baseUrl;
|
|
187
459
|
this.apiKey = apiKey;
|
|
188
460
|
this.tokenStorage = tokenStorage;
|
|
189
461
|
this.httpClient = httpClient;
|
|
462
|
+
this.retryPolicy = retryPolicy;
|
|
190
463
|
}
|
|
191
464
|
baseUrl;
|
|
192
465
|
apiKey;
|
|
193
466
|
tokenStorage;
|
|
194
467
|
httpClient;
|
|
468
|
+
retryPolicy;
|
|
195
469
|
async get() {
|
|
196
470
|
const headers = {
|
|
197
471
|
"Content-Type": "application/json",
|
|
@@ -201,25 +475,33 @@ var CompanyEndpoint = class {
|
|
|
201
475
|
if (token) {
|
|
202
476
|
headers["Authorization"] = `Bearer ${token.accessToken}`;
|
|
203
477
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
478
|
+
return withRetries(this.retryPolicy, "company request", async () => {
|
|
479
|
+
const response = await this.httpClient.request("GET", `${this.baseUrl}/company`, { headers });
|
|
480
|
+
const responseText = await response.text();
|
|
481
|
+
if (!response.ok) {
|
|
482
|
+
throw new HttpException(
|
|
483
|
+
describeApiError(responseText, response.status),
|
|
484
|
+
response.status,
|
|
485
|
+
responseText,
|
|
486
|
+
{},
|
|
487
|
+
response.headers.get("retry-after")
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
let parsed;
|
|
491
|
+
try {
|
|
492
|
+
parsed = JSON.parse(responseText);
|
|
493
|
+
} catch {
|
|
494
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
495
|
+
}
|
|
496
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
497
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
498
|
+
}
|
|
499
|
+
const obj = parsed;
|
|
500
|
+
if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
|
|
501
|
+
return normalizeCompanyResource(obj["data"]);
|
|
502
|
+
}
|
|
503
|
+
return normalizeCompanyResource(obj);
|
|
504
|
+
});
|
|
223
505
|
}
|
|
224
506
|
};
|
|
225
507
|
|
|
@@ -258,6 +540,31 @@ function describeComplianceEnrollmentStatus(status) {
|
|
|
258
540
|
return DESCRIPTIONS[status];
|
|
259
541
|
}
|
|
260
542
|
|
|
543
|
+
// src/enums/ComplianceService.ts
|
|
544
|
+
var ComplianceService = {
|
|
545
|
+
E_REPORTING: "e_reporting",
|
|
546
|
+
E_INVOICING: "e_invoicing",
|
|
547
|
+
UNKNOWN: "unknown"
|
|
548
|
+
};
|
|
549
|
+
var VALUES2 = Object.values(ComplianceService);
|
|
550
|
+
function toComplianceService(value) {
|
|
551
|
+
if (typeof value === "string") {
|
|
552
|
+
const normalized = value.trim().toLowerCase();
|
|
553
|
+
if (VALUES2.includes(normalized)) {
|
|
554
|
+
return normalized;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return ComplianceService.UNKNOWN;
|
|
558
|
+
}
|
|
559
|
+
var DESCRIPTIONS2 = {
|
|
560
|
+
[ComplianceService.E_REPORTING]: "E-reporting of transaction data to the tax authority (France: DGFiP Flux 10).",
|
|
561
|
+
[ComplianceService.E_INVOICING]: "Structured e-invoices delivered to the buyer (Norway: EHF / Peppol BIS 3.0).",
|
|
562
|
+
[ComplianceService.UNKNOWN]: "Unknown service."
|
|
563
|
+
};
|
|
564
|
+
function describeComplianceService(service) {
|
|
565
|
+
return DESCRIPTIONS2[service];
|
|
566
|
+
}
|
|
567
|
+
|
|
261
568
|
// src/dto/ComplianceEnrollment.ts
|
|
262
569
|
function asString(value, fallback = "") {
|
|
263
570
|
return typeof value === "string" ? value : fallback;
|
|
@@ -269,7 +576,7 @@ function asNumber(value) {
|
|
|
269
576
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
270
577
|
}
|
|
271
578
|
var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
272
|
-
constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt) {
|
|
579
|
+
constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt, service = ComplianceService.UNKNOWN) {
|
|
273
580
|
this.id = id;
|
|
274
581
|
this.companyId = companyId;
|
|
275
582
|
this.country = country;
|
|
@@ -288,6 +595,7 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
288
595
|
this.autoSend = autoSend;
|
|
289
596
|
this.createdAt = createdAt;
|
|
290
597
|
this.updatedAt = updatedAt;
|
|
598
|
+
this.service = service;
|
|
291
599
|
}
|
|
292
600
|
id;
|
|
293
601
|
companyId;
|
|
@@ -307,6 +615,7 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
307
615
|
autoSend;
|
|
308
616
|
createdAt;
|
|
309
617
|
updatedAt;
|
|
618
|
+
service;
|
|
310
619
|
static fromArray(data) {
|
|
311
620
|
const regimeConfig = typeof data["regime_config"] === "object" && data["regime_config"] !== null && !Array.isArray(data["regime_config"]) ? data["regime_config"] : null;
|
|
312
621
|
return new _ComplianceEnrollment(
|
|
@@ -327,7 +636,8 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
327
636
|
asNullableString(data["notification_email"]),
|
|
328
637
|
data["auto_send"] === true,
|
|
329
638
|
asNullableString(data["created_at"]),
|
|
330
|
-
asNullableString(data["updated_at"])
|
|
639
|
+
asNullableString(data["updated_at"]),
|
|
640
|
+
toComplianceService(data["service"])
|
|
331
641
|
);
|
|
332
642
|
}
|
|
333
643
|
};
|
|
@@ -377,17 +687,17 @@ var ComplianceTaxReportState = {
|
|
|
377
687
|
ERROR: "error",
|
|
378
688
|
UNKNOWN: "unknown"
|
|
379
689
|
};
|
|
380
|
-
var
|
|
690
|
+
var VALUES3 = Object.values(ComplianceTaxReportState);
|
|
381
691
|
function toComplianceTaxReportState(value) {
|
|
382
692
|
if (typeof value === "string") {
|
|
383
693
|
const normalized = value.trim().toLowerCase();
|
|
384
|
-
if (
|
|
694
|
+
if (VALUES3.includes(normalized)) {
|
|
385
695
|
return normalized;
|
|
386
696
|
}
|
|
387
697
|
}
|
|
388
698
|
return ComplianceTaxReportState.UNKNOWN;
|
|
389
699
|
}
|
|
390
|
-
var
|
|
700
|
+
var DESCRIPTIONS3 = {
|
|
391
701
|
[ComplianceTaxReportState.NEW]: "Created at provider, waiting for the daily ledger batch.",
|
|
392
702
|
[ComplianceTaxReportState.SENT]: "Ledger transmitted to PPF.",
|
|
393
703
|
[ComplianceTaxReportState.ACKNOWLEDGED]: "PPF acknowledged receipt of the ledger.",
|
|
@@ -397,7 +707,7 @@ var DESCRIPTIONS2 = {
|
|
|
397
707
|
[ComplianceTaxReportState.UNKNOWN]: "Unknown state."
|
|
398
708
|
};
|
|
399
709
|
function describeComplianceTaxReportState(state) {
|
|
400
|
-
return
|
|
710
|
+
return DESCRIPTIONS3[state];
|
|
401
711
|
}
|
|
402
712
|
|
|
403
713
|
// src/dto/ComplianceTaxReport.ts
|
|
@@ -519,6 +829,31 @@ var ComplianceTaxReportPage = class _ComplianceTaxReportPage {
|
|
|
519
829
|
}
|
|
520
830
|
};
|
|
521
831
|
|
|
832
|
+
// src/enums/ComplianceDocumentType.ts
|
|
833
|
+
var ComplianceDocumentType = {
|
|
834
|
+
INVOICE: "invoice",
|
|
835
|
+
CREDIT_NOTE: "credit_note",
|
|
836
|
+
UNKNOWN: "unknown"
|
|
837
|
+
};
|
|
838
|
+
var VALUES4 = Object.values(ComplianceDocumentType);
|
|
839
|
+
function toComplianceDocumentType(value) {
|
|
840
|
+
if (typeof value === "string") {
|
|
841
|
+
const normalized = value.trim().toLowerCase();
|
|
842
|
+
if (VALUES4.includes(normalized)) {
|
|
843
|
+
return normalized;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return ComplianceDocumentType.UNKNOWN;
|
|
847
|
+
}
|
|
848
|
+
var DESCRIPTIONS4 = {
|
|
849
|
+
[ComplianceDocumentType.INVOICE]: "Commercial invoice.",
|
|
850
|
+
[ComplianceDocumentType.CREDIT_NOTE]: "Credit note correcting a previously issued invoice.",
|
|
851
|
+
[ComplianceDocumentType.UNKNOWN]: "Unknown document type."
|
|
852
|
+
};
|
|
853
|
+
function describeComplianceDocumentType(type) {
|
|
854
|
+
return DESCRIPTIONS4[type];
|
|
855
|
+
}
|
|
856
|
+
|
|
522
857
|
// src/enums/ComplianceTransactionState.ts
|
|
523
858
|
var ComplianceTransactionState = {
|
|
524
859
|
PENDING: "pending",
|
|
@@ -527,17 +862,17 @@ var ComplianceTransactionState = {
|
|
|
527
862
|
ERROR: "error",
|
|
528
863
|
UNKNOWN: "unknown"
|
|
529
864
|
};
|
|
530
|
-
var
|
|
865
|
+
var VALUES5 = Object.values(ComplianceTransactionState);
|
|
531
866
|
function toComplianceTransactionState(value) {
|
|
532
867
|
if (typeof value === "string") {
|
|
533
868
|
const normalized = value.trim().toLowerCase();
|
|
534
|
-
if (
|
|
869
|
+
if (VALUES5.includes(normalized)) {
|
|
535
870
|
return normalized;
|
|
536
871
|
}
|
|
537
872
|
}
|
|
538
873
|
return ComplianceTransactionState.UNKNOWN;
|
|
539
874
|
}
|
|
540
|
-
var
|
|
875
|
+
var DESCRIPTIONS5 = {
|
|
541
876
|
[ComplianceTransactionState.PENDING]: "Recorded locally, not yet submitted to the provider.",
|
|
542
877
|
[ComplianceTransactionState.SENDING]: "Submission to the provider in progress.",
|
|
543
878
|
[ComplianceTransactionState.SUBMITTED]: "Successfully submitted to the provider.",
|
|
@@ -545,7 +880,7 @@ var DESCRIPTIONS3 = {
|
|
|
545
880
|
[ComplianceTransactionState.UNKNOWN]: "Unknown state."
|
|
546
881
|
};
|
|
547
882
|
function describeComplianceTransactionState(state) {
|
|
548
|
-
return
|
|
883
|
+
return DESCRIPTIONS5[state];
|
|
549
884
|
}
|
|
550
885
|
|
|
551
886
|
// src/enums/ComplianceTransactionType.ts
|
|
@@ -557,17 +892,17 @@ var ComplianceTransactionType = {
|
|
|
557
892
|
CROSSBORDER_INBOUND: "crossborder_inbound",
|
|
558
893
|
UNKNOWN: "unknown"
|
|
559
894
|
};
|
|
560
|
-
var
|
|
895
|
+
var VALUES6 = Object.values(ComplianceTransactionType);
|
|
561
896
|
function toComplianceTransactionType(value) {
|
|
562
897
|
if (typeof value === "string") {
|
|
563
898
|
const normalized = value.trim().toLowerCase();
|
|
564
|
-
if (
|
|
899
|
+
if (VALUES6.includes(normalized)) {
|
|
565
900
|
return normalized;
|
|
566
901
|
}
|
|
567
902
|
}
|
|
568
903
|
return ComplianceTransactionType.UNKNOWN;
|
|
569
904
|
}
|
|
570
|
-
var
|
|
905
|
+
var DESCRIPTIONS6 = {
|
|
571
906
|
[ComplianceTransactionType.B2C_OUTBOUND]: "Sale to a private person in the merchant country (DGFiP Flux 10.3).",
|
|
572
907
|
[ComplianceTransactionType.B2B_DOMESTIC_OUTBOUND]: "Sale to a domestic business (DGFiP Flux 1, Phase 2).",
|
|
573
908
|
[ComplianceTransactionType.B2B_DOMESTIC_INBOUND]: "Purchase from a domestic business (DGFiP Flux 1, Phase 2).",
|
|
@@ -576,7 +911,7 @@ var DESCRIPTIONS4 = {
|
|
|
576
911
|
[ComplianceTransactionType.UNKNOWN]: "Unknown transaction type."
|
|
577
912
|
};
|
|
578
913
|
function describeComplianceTransactionType(type) {
|
|
579
|
-
return
|
|
914
|
+
return DESCRIPTIONS6[type];
|
|
580
915
|
}
|
|
581
916
|
|
|
582
917
|
// src/dto/ComplianceTransaction.ts
|
|
@@ -594,8 +929,34 @@ function asMoney2(value) {
|
|
|
594
929
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
595
930
|
return "0.00";
|
|
596
931
|
}
|
|
932
|
+
function asNullableNumber2(value) {
|
|
933
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
934
|
+
if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) return Number(value);
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
function asRecord(value) {
|
|
938
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
939
|
+
}
|
|
940
|
+
function asRecordList(value) {
|
|
941
|
+
return Array.isArray(value) ? value.filter((row) => asRecord(row) !== null) : [];
|
|
942
|
+
}
|
|
943
|
+
function toInvoiceLines(providerPayload) {
|
|
944
|
+
const invoice = asRecord(asRecord(providerPayload)?.["invoice"]);
|
|
945
|
+
return asRecordList(invoice?.["invoice_lines_attributes"]).map((line) => ({
|
|
946
|
+
description: asString3(line["description"]),
|
|
947
|
+
quantity: asNullableNumber2(line["quantity"]),
|
|
948
|
+
price: asMoney2(line["price"]),
|
|
949
|
+
unit: asNullableNumber2(line["unit"]),
|
|
950
|
+
taxes: asRecordList(line["taxes_attributes"]).map((tax) => ({
|
|
951
|
+
name: asString3(tax["name"]),
|
|
952
|
+
percent: asNullableNumber2(tax["percent"]),
|
|
953
|
+
category: asNullableString3(tax["category"]),
|
|
954
|
+
comment: asNullableString3(tax["comment"])
|
|
955
|
+
}))
|
|
956
|
+
}));
|
|
957
|
+
}
|
|
597
958
|
var ComplianceTransaction = class _ComplianceTransaction {
|
|
598
|
-
constructor(id, companyId, complianceEnrollmentId, country, regime, transactionType, transactionTypeLabel, state, stateLabel, invoiceNumber, invoiceDate, dueDate, currency, subtotal, taxAmount, total, counterpartyName, counterpartyCountry, counterpartyVatNumber, isPaid, paidAt, providerInvoiceId, submissionError, reportedAt, taxReport, createdAt, updatedAt) {
|
|
959
|
+
constructor(id, companyId, complianceEnrollmentId, country, regime, transactionType, transactionTypeLabel, state, stateLabel, invoiceNumber, invoiceDate, dueDate, currency, subtotal, taxAmount, total, counterpartyName, counterpartyCountry, counterpartyVatNumber, isPaid, paidAt, providerInvoiceId, submissionError, reportedAt, taxReport, createdAt, updatedAt, counterpartyRegisterId = null, counterpartyAddress = null, counterpartyCity = null, counterpartyPostalcode = null, counterpartyEmail = null, buyerReference = null, documentType = ComplianceDocumentType.INVOICE, amendedNumber = null, amendedDate = null, providerState = null, invoiceLines = []) {
|
|
599
960
|
this.id = id;
|
|
600
961
|
this.companyId = companyId;
|
|
601
962
|
this.complianceEnrollmentId = complianceEnrollmentId;
|
|
@@ -623,6 +984,17 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
623
984
|
this.taxReport = taxReport;
|
|
624
985
|
this.createdAt = createdAt;
|
|
625
986
|
this.updatedAt = updatedAt;
|
|
987
|
+
this.counterpartyRegisterId = counterpartyRegisterId;
|
|
988
|
+
this.counterpartyAddress = counterpartyAddress;
|
|
989
|
+
this.counterpartyCity = counterpartyCity;
|
|
990
|
+
this.counterpartyPostalcode = counterpartyPostalcode;
|
|
991
|
+
this.counterpartyEmail = counterpartyEmail;
|
|
992
|
+
this.buyerReference = buyerReference;
|
|
993
|
+
this.documentType = documentType;
|
|
994
|
+
this.amendedNumber = amendedNumber;
|
|
995
|
+
this.amendedDate = amendedDate;
|
|
996
|
+
this.providerState = providerState;
|
|
997
|
+
this.invoiceLines = invoiceLines;
|
|
626
998
|
}
|
|
627
999
|
id;
|
|
628
1000
|
companyId;
|
|
@@ -651,6 +1023,20 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
651
1023
|
taxReport;
|
|
652
1024
|
createdAt;
|
|
653
1025
|
updatedAt;
|
|
1026
|
+
counterpartyRegisterId;
|
|
1027
|
+
counterpartyAddress;
|
|
1028
|
+
counterpartyCity;
|
|
1029
|
+
counterpartyPostalcode;
|
|
1030
|
+
counterpartyEmail;
|
|
1031
|
+
buyerReference;
|
|
1032
|
+
documentType;
|
|
1033
|
+
amendedNumber;
|
|
1034
|
+
amendedDate;
|
|
1035
|
+
providerState;
|
|
1036
|
+
invoiceLines;
|
|
1037
|
+
get isCreditNote() {
|
|
1038
|
+
return this.documentType === ComplianceDocumentType.CREDIT_NOTE;
|
|
1039
|
+
}
|
|
654
1040
|
static fromArray(data) {
|
|
655
1041
|
const taxReport = typeof data["tax_report"] === "object" && data["tax_report"] !== null && !Array.isArray(data["tax_report"]) ? ComplianceTaxReport.fromArray(data["tax_report"]) : void 0;
|
|
656
1042
|
return new _ComplianceTransaction(
|
|
@@ -680,7 +1066,18 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
680
1066
|
asNullableString3(data["reported_at"]),
|
|
681
1067
|
taxReport,
|
|
682
1068
|
asNullableString3(data["created_at"]),
|
|
683
|
-
asNullableString3(data["updated_at"])
|
|
1069
|
+
asNullableString3(data["updated_at"]),
|
|
1070
|
+
asNullableString3(data["counterparty_register_id"]),
|
|
1071
|
+
asNullableString3(data["counterparty_address"]),
|
|
1072
|
+
asNullableString3(data["counterparty_city"]),
|
|
1073
|
+
asNullableString3(data["counterparty_postalcode"]),
|
|
1074
|
+
asNullableString3(data["counterparty_email"]),
|
|
1075
|
+
asNullableString3(data["buyer_reference"]),
|
|
1076
|
+
data["document_type"] === void 0 || data["document_type"] === null ? ComplianceDocumentType.INVOICE : toComplianceDocumentType(data["document_type"]),
|
|
1077
|
+
asNullableString3(data["amended_number"]),
|
|
1078
|
+
asNullableString3(data["amended_date"]),
|
|
1079
|
+
asNullableString3(data["provider_state"]),
|
|
1080
|
+
toInvoiceLines(data["provider_payload"])
|
|
684
1081
|
);
|
|
685
1082
|
}
|
|
686
1083
|
};
|
|
@@ -739,10 +1136,134 @@ var ImportResult = class _ImportResult {
|
|
|
739
1136
|
}
|
|
740
1137
|
};
|
|
741
1138
|
|
|
742
|
-
// src/
|
|
1139
|
+
// src/enums/PeppolLookupStatus.ts
|
|
1140
|
+
var PeppolLookupStatus = {
|
|
1141
|
+
REACHABLE: "reachable",
|
|
1142
|
+
NOT_REACHABLE: "not_reachable",
|
|
1143
|
+
PENDING: "pending",
|
|
1144
|
+
UNKNOWN: "unknown"
|
|
1145
|
+
};
|
|
1146
|
+
var VALUES7 = Object.values(PeppolLookupStatus);
|
|
1147
|
+
function toPeppolLookupStatus(value) {
|
|
1148
|
+
if (typeof value === "string") {
|
|
1149
|
+
const normalized = value.trim().toLowerCase();
|
|
1150
|
+
if (VALUES7.includes(normalized)) {
|
|
1151
|
+
return normalized;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return PeppolLookupStatus.UNKNOWN;
|
|
1155
|
+
}
|
|
1156
|
+
var DESCRIPTIONS7 = {
|
|
1157
|
+
[PeppolLookupStatus.REACHABLE]: "The participant is registered on Peppol and can receive e-invoices.",
|
|
1158
|
+
[PeppolLookupStatus.NOT_REACHABLE]: "The participant is not registered on Peppol.",
|
|
1159
|
+
[PeppolLookupStatus.PENDING]: "The directory lookup is still resolving \u2014 ask again in a few seconds.",
|
|
1160
|
+
[PeppolLookupStatus.UNKNOWN]: "Unknown lookup status."
|
|
1161
|
+
};
|
|
1162
|
+
function describePeppolLookupStatus(status) {
|
|
1163
|
+
return DESCRIPTIONS7[status];
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/dto/PeppolLookupResult.ts
|
|
743
1167
|
function asString4(value, fallback = "") {
|
|
744
1168
|
return typeof value === "string" ? value : fallback;
|
|
745
1169
|
}
|
|
1170
|
+
function asNullableString4(value) {
|
|
1171
|
+
return typeof value === "string" ? value : null;
|
|
1172
|
+
}
|
|
1173
|
+
var PeppolLookupResult = class _PeppolLookupResult {
|
|
1174
|
+
constructor(status, reachable, country, scheme, id, documentTypes, transportTypeCode, checkedAt) {
|
|
1175
|
+
this.status = status;
|
|
1176
|
+
this.reachable = reachable;
|
|
1177
|
+
this.country = country;
|
|
1178
|
+
this.scheme = scheme;
|
|
1179
|
+
this.id = id;
|
|
1180
|
+
this.documentTypes = documentTypes;
|
|
1181
|
+
this.transportTypeCode = transportTypeCode;
|
|
1182
|
+
this.checkedAt = checkedAt;
|
|
1183
|
+
}
|
|
1184
|
+
status;
|
|
1185
|
+
reachable;
|
|
1186
|
+
country;
|
|
1187
|
+
scheme;
|
|
1188
|
+
id;
|
|
1189
|
+
documentTypes;
|
|
1190
|
+
transportTypeCode;
|
|
1191
|
+
checkedAt;
|
|
1192
|
+
/** True while the directory is still resolving — ask again in a few seconds. */
|
|
1193
|
+
get isPending() {
|
|
1194
|
+
return this.status === PeppolLookupStatus.PENDING;
|
|
1195
|
+
}
|
|
1196
|
+
static fromArray(data) {
|
|
1197
|
+
const documentTypes = Array.isArray(data["document_types"]) ? data["document_types"].filter((t) => typeof t === "string") : [];
|
|
1198
|
+
return new _PeppolLookupResult(
|
|
1199
|
+
toPeppolLookupStatus(data["status"]),
|
|
1200
|
+
typeof data["reachable"] === "boolean" ? data["reachable"] : null,
|
|
1201
|
+
asString4(data["country"]),
|
|
1202
|
+
asString4(data["scheme"]),
|
|
1203
|
+
asString4(data["id"]),
|
|
1204
|
+
documentTypes,
|
|
1205
|
+
asNullableString4(data["transport_type_code"]),
|
|
1206
|
+
asNullableString4(data["checked_at"])
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/dto/RegistryCompany.ts
|
|
1212
|
+
function asString5(value, fallback = "") {
|
|
1213
|
+
return typeof value === "string" ? value : fallback;
|
|
1214
|
+
}
|
|
1215
|
+
function asNullableString5(value) {
|
|
1216
|
+
return typeof value === "string" ? value : null;
|
|
1217
|
+
}
|
|
1218
|
+
var RegistryCompany = class _RegistryCompany {
|
|
1219
|
+
constructor(orgNumber, companyName, organisationForm, vatRegistered, vatNumber, enterpriseRegister, bankrupt, underLiquidation, address, postalcode, city, country) {
|
|
1220
|
+
this.orgNumber = orgNumber;
|
|
1221
|
+
this.companyName = companyName;
|
|
1222
|
+
this.organisationForm = organisationForm;
|
|
1223
|
+
this.vatRegistered = vatRegistered;
|
|
1224
|
+
this.vatNumber = vatNumber;
|
|
1225
|
+
this.enterpriseRegister = enterpriseRegister;
|
|
1226
|
+
this.bankrupt = bankrupt;
|
|
1227
|
+
this.underLiquidation = underLiquidation;
|
|
1228
|
+
this.address = address;
|
|
1229
|
+
this.postalcode = postalcode;
|
|
1230
|
+
this.city = city;
|
|
1231
|
+
this.country = country;
|
|
1232
|
+
}
|
|
1233
|
+
orgNumber;
|
|
1234
|
+
companyName;
|
|
1235
|
+
organisationForm;
|
|
1236
|
+
vatRegistered;
|
|
1237
|
+
vatNumber;
|
|
1238
|
+
enterpriseRegister;
|
|
1239
|
+
bankrupt;
|
|
1240
|
+
underLiquidation;
|
|
1241
|
+
address;
|
|
1242
|
+
postalcode;
|
|
1243
|
+
city;
|
|
1244
|
+
country;
|
|
1245
|
+
static fromArray(data) {
|
|
1246
|
+
return new _RegistryCompany(
|
|
1247
|
+
asString5(data["org_number"]),
|
|
1248
|
+
asString5(data["company_name"]),
|
|
1249
|
+
asNullableString5(data["organisation_form"]),
|
|
1250
|
+
data["vat_registered"] === true,
|
|
1251
|
+
asNullableString5(data["vat_number"]),
|
|
1252
|
+
data["enterprise_register"] === true,
|
|
1253
|
+
data["bankrupt"] === true,
|
|
1254
|
+
data["under_liquidation"] === true,
|
|
1255
|
+
asNullableString5(data["address"]),
|
|
1256
|
+
asNullableString5(data["postalcode"]),
|
|
1257
|
+
asNullableString5(data["city"]),
|
|
1258
|
+
asString5(data["country"])
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
// src/dto/RevenueStatistics.ts
|
|
1264
|
+
function asString6(value, fallback = "") {
|
|
1265
|
+
return typeof value === "string" ? value : fallback;
|
|
1266
|
+
}
|
|
746
1267
|
function asNumber4(value) {
|
|
747
1268
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
748
1269
|
}
|
|
@@ -792,35 +1313,35 @@ var RevenueStatistics = class _RevenueStatistics {
|
|
|
792
1313
|
typeof data["totals"] === "object" && data["totals"] !== null ? data["totals"] : {}
|
|
793
1314
|
);
|
|
794
1315
|
const timeSeries = asRecords(data["time_series"]).map((row) => ({
|
|
795
|
-
bucket:
|
|
1316
|
+
bucket: asString6(row["bucket"]),
|
|
796
1317
|
...toTotals(row)
|
|
797
1318
|
}));
|
|
798
1319
|
const byType = asRecords(data["by_type"]).map((row) => ({
|
|
799
|
-
type:
|
|
800
|
-
typeLabel:
|
|
1320
|
+
type: asString6(row["type"]),
|
|
1321
|
+
typeLabel: asString6(row["type_label"]),
|
|
801
1322
|
count: asNumber4(row["count"]),
|
|
802
1323
|
total: asMoney3(row["total"])
|
|
803
1324
|
}));
|
|
804
1325
|
const byCountry = asRecords(data["by_country"]).map((row) => ({
|
|
805
|
-
country:
|
|
1326
|
+
country: asString6(row["country"], "unknown"),
|
|
806
1327
|
count: asNumber4(row["count"]),
|
|
807
1328
|
total: asMoney3(row["total"])
|
|
808
1329
|
}));
|
|
809
1330
|
const byState = asRecords(data["by_state"]).map((row) => ({
|
|
810
|
-
state:
|
|
811
|
-
stateLabel:
|
|
1331
|
+
state: asString6(row["state"]),
|
|
1332
|
+
stateLabel: asString6(row["state_label"]),
|
|
812
1333
|
count: asNumber4(row["count"]),
|
|
813
1334
|
total: asMoney3(row["total"])
|
|
814
1335
|
}));
|
|
815
1336
|
const byCurrency = asRecords(data["by_currency"]).map((row) => ({
|
|
816
|
-
currency:
|
|
1337
|
+
currency: asString6(row["currency"]),
|
|
817
1338
|
...toTotals(row)
|
|
818
1339
|
}));
|
|
819
1340
|
return new _RevenueStatistics(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1341
|
+
asString6(range["date_from"]) || void 0,
|
|
1342
|
+
asString6(range["date_to"]) || void 0,
|
|
1343
|
+
asString6(range["interval"]) || void 0,
|
|
1344
|
+
asString6(data["primary_currency"], "EUR"),
|
|
824
1345
|
data["is_multi_currency"] === true,
|
|
825
1346
|
totals,
|
|
826
1347
|
timeSeries,
|
|
@@ -833,10 +1354,10 @@ var RevenueStatistics = class _RevenueStatistics {
|
|
|
833
1354
|
};
|
|
834
1355
|
|
|
835
1356
|
// src/dto/SireneLookupResult.ts
|
|
836
|
-
function
|
|
1357
|
+
function asString7(value, fallback = "") {
|
|
837
1358
|
return typeof value === "string" ? value : fallback;
|
|
838
1359
|
}
|
|
839
|
-
function
|
|
1360
|
+
function asNullableString6(value) {
|
|
840
1361
|
return typeof value === "string" ? value : null;
|
|
841
1362
|
}
|
|
842
1363
|
var SireneLookupResult = class _SireneLookupResult {
|
|
@@ -862,21 +1383,21 @@ var SireneLookupResult = class _SireneLookupResult {
|
|
|
862
1383
|
postalcode;
|
|
863
1384
|
static fromArray(data) {
|
|
864
1385
|
return new _SireneLookupResult(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1386
|
+
asString7(data["company_name"]),
|
|
1387
|
+
asString7(data["siren"]),
|
|
1388
|
+
asNullableString6(data["siret"]),
|
|
1389
|
+
asNullableString6(data["naf_code"]),
|
|
1390
|
+
asNullableString6(data["naf_full"]),
|
|
1391
|
+
asNullableString6(data["enterprise_size"]),
|
|
1392
|
+
asNullableString6(data["address"]),
|
|
1393
|
+
asNullableString6(data["city"]),
|
|
1394
|
+
asNullableString6(data["postalcode"])
|
|
874
1395
|
);
|
|
875
1396
|
}
|
|
876
1397
|
};
|
|
877
1398
|
|
|
878
1399
|
// src/dto/VatRates.ts
|
|
879
|
-
function
|
|
1400
|
+
function asString8(value, fallback = "") {
|
|
880
1401
|
return typeof value === "string" ? value : fallback;
|
|
881
1402
|
}
|
|
882
1403
|
function asNumber5(value) {
|
|
@@ -894,12 +1415,12 @@ var VatRates = class _VatRates {
|
|
|
894
1415
|
static fromArray(data) {
|
|
895
1416
|
const rates = (Array.isArray(data["rates"]) ? data["rates"] : []).filter((row) => typeof row === "object" && row !== null).map((row) => ({
|
|
896
1417
|
percent: asNumber5(row["percent"]),
|
|
897
|
-
category:
|
|
898
|
-
labelKey:
|
|
899
|
-
taxName:
|
|
1418
|
+
category: asString8(row["category"]),
|
|
1419
|
+
labelKey: asString8(row["label_key"]),
|
|
1420
|
+
taxName: asString8(row["tax_name"]),
|
|
900
1421
|
requiresVatex: row["requires_vatex"] === true
|
|
901
1422
|
}));
|
|
902
|
-
return new _VatRates(
|
|
1423
|
+
return new _VatRates(asString8(data["country"]), asString8(data["currency"], "EUR"), rates);
|
|
903
1424
|
}
|
|
904
1425
|
};
|
|
905
1426
|
|
|
@@ -918,10 +1439,48 @@ var ValidationException = class extends TaxoraException {
|
|
|
918
1439
|
}
|
|
919
1440
|
};
|
|
920
1441
|
|
|
1442
|
+
// src/support/NorwegianOrgNumber.ts
|
|
1443
|
+
var WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2];
|
|
1444
|
+
var NorwegianOrgNumber = {
|
|
1445
|
+
/** Peppol identifier scheme for Norwegian organisation numbers. */
|
|
1446
|
+
PEPPOL_SCHEME: "0192",
|
|
1447
|
+
/**
|
|
1448
|
+
* Strips whitespace, dots, dashes, a "0192:" Peppol scheme prefix, the "NO"
|
|
1449
|
+
* prefix and the "MVA" suffix: "NO 923 609 016 MVA", "923609016MVA" and
|
|
1450
|
+
* "923 609 016" all become "923609016". Does not validate.
|
|
1451
|
+
*/
|
|
1452
|
+
normalize(value) {
|
|
1453
|
+
let v = (value ?? "").trim().toUpperCase().replace(/[\s.-]/g, "");
|
|
1454
|
+
if (v.startsWith("0192:")) v = v.slice(5);
|
|
1455
|
+
if (v.startsWith("NO")) v = v.slice(2);
|
|
1456
|
+
if (v.endsWith("MVA")) v = v.slice(0, -3);
|
|
1457
|
+
return v;
|
|
1458
|
+
},
|
|
1459
|
+
/** True when the (normalized) value is 9 digits with a valid mod-11 check digit. */
|
|
1460
|
+
isValid(value) {
|
|
1461
|
+
const digits = NorwegianOrgNumber.normalize(value);
|
|
1462
|
+
if (!/^\d{9}$/.test(digits)) return false;
|
|
1463
|
+
let sum = 0;
|
|
1464
|
+
for (let i = 0; i < WEIGHTS.length; i++) {
|
|
1465
|
+
sum += Number(digits[i]) * WEIGHTS[i];
|
|
1466
|
+
}
|
|
1467
|
+
let check = 11 - sum % 11;
|
|
1468
|
+
if (check === 11) check = 0;
|
|
1469
|
+
return check !== 10 && check === Number(digits[8]);
|
|
1470
|
+
},
|
|
1471
|
+
/** "NO923609016MVA" — the Norwegian VAT number of an MVA-registered entity. Does not validate. */
|
|
1472
|
+
toVatNumber(value) {
|
|
1473
|
+
return `NO${NorwegianOrgNumber.normalize(value)}MVA`;
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
|
|
921
1477
|
// src/endpoints/EReportingEndpoint.ts
|
|
922
1478
|
var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
923
1479
|
var NAF_CODE_REGEX = /^\d{2}$/;
|
|
1480
|
+
var COUNTRY_REGEX = /^[A-Za-z]{2}$/;
|
|
1481
|
+
var PEPPOL_SCHEME_REGEX = /^\d{4}$/;
|
|
924
1482
|
var INTERVALS = ["day", "week", "month"];
|
|
1483
|
+
var SERVICES = ["e_reporting", "e_invoicing"];
|
|
925
1484
|
var ENTERPRISE_SIZES = ["micro", "pme", "eti", "ge"];
|
|
926
1485
|
var TYPE_OPERATIONS = ["services", "goods", "mixed"];
|
|
927
1486
|
var TRANSACTION_TYPES = [
|
|
@@ -932,29 +1491,40 @@ var TRANSACTION_TYPES = [
|
|
|
932
1491
|
"crossborder_inbound"
|
|
933
1492
|
];
|
|
934
1493
|
var EReportingEndpoint = class {
|
|
935
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
1494
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
936
1495
|
this.baseUrl = baseUrl;
|
|
937
1496
|
this.apiKey = apiKey;
|
|
938
1497
|
this.tokenStorage = tokenStorage;
|
|
939
1498
|
this.httpClient = httpClient;
|
|
1499
|
+
this.retryPolicy = retryPolicy;
|
|
940
1500
|
}
|
|
941
1501
|
baseUrl;
|
|
942
1502
|
apiKey;
|
|
943
1503
|
tokenStorage;
|
|
944
1504
|
httpClient;
|
|
1505
|
+
retryPolicy;
|
|
945
1506
|
// ---------------------------------------------------------------------
|
|
946
1507
|
// Enrollments
|
|
947
1508
|
// ---------------------------------------------------------------------
|
|
948
1509
|
/**
|
|
949
1510
|
* Create a compliance enrollment and provision the provider account. By
|
|
950
|
-
* default the
|
|
1511
|
+
* default the country regime is activated right away (France: DGFiP tax
|
|
1512
|
+
* report setting; Norway: Peppol registration); pass `autoActivate: false`
|
|
951
1513
|
* to only create the account.
|
|
952
1514
|
*
|
|
953
|
-
*
|
|
954
|
-
* the
|
|
1515
|
+
* The input shape depends on `country`: omitted/"FR" takes the France input
|
|
1516
|
+
* (unchanged, backwards compatible), "NO" the Norway input — see also the
|
|
1517
|
+
* createNorwayEnrollment() shortcut.
|
|
1518
|
+
*
|
|
1519
|
+
* Errors: 403 (HttpException, body code `service_not_active`) when the company
|
|
1520
|
+
* lacks the requested service; on provider failure 502 (HttpException) — the
|
|
1521
|
+
* enrollment is then persisted in `error_account`/`error_activation` state so
|
|
955
1522
|
* provisioning can be retried.
|
|
956
1523
|
*/
|
|
957
1524
|
async createEnrollment(input) {
|
|
1525
|
+
if (this.isNorwayEnrollmentInput(input)) {
|
|
1526
|
+
return this.createEnrollmentFromBody(this.buildNorwayEnrollmentBody(input));
|
|
1527
|
+
}
|
|
958
1528
|
this.validateEnrollmentInput(input);
|
|
959
1529
|
const body = {
|
|
960
1530
|
email: input.email,
|
|
@@ -964,6 +1534,7 @@ var EReportingEndpoint = class {
|
|
|
964
1534
|
reporting_start_date: this.formatDate(input.reportingStartDate)
|
|
965
1535
|
};
|
|
966
1536
|
if (input.country !== void 0) body["country"] = input.country;
|
|
1537
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
967
1538
|
if (input.regime !== void 0) body["regime"] = input.regime;
|
|
968
1539
|
if (input.siret !== void 0) body["siret"] = input.siret;
|
|
969
1540
|
if (input.siren !== void 0) body["siren"] = input.siren;
|
|
@@ -974,9 +1545,15 @@ var EReportingEndpoint = class {
|
|
|
974
1545
|
if (input.postalcode !== void 0) body["postalcode"] = input.postalcode;
|
|
975
1546
|
if (input.province !== void 0) body["province"] = input.province;
|
|
976
1547
|
if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1548
|
+
return this.createEnrollmentFromBody(body);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Enroll the company for Norway e-invoicing (EHF / Peppol BIS 3.0) — shortcut
|
|
1552
|
+
* for createEnrollment({ country: 'NO', ... }). The organisation number is
|
|
1553
|
+
* checked (mod-11) and normalized to 9 digits before sending.
|
|
1554
|
+
*/
|
|
1555
|
+
async createNorwayEnrollment(input) {
|
|
1556
|
+
return this.createEnrollment({ ...input, country: "NO" });
|
|
980
1557
|
}
|
|
981
1558
|
/** Paginated list of the company's enrollments (newest first). `perPage` is capped at 100 server-side. */
|
|
982
1559
|
async listEnrollments(page = 1, perPage = 25) {
|
|
@@ -1010,6 +1587,72 @@ var EReportingEndpoint = class {
|
|
|
1010
1587
|
const data = await this.parseJsonResponse(response);
|
|
1011
1588
|
return SireneLookupResult.fromArray(data);
|
|
1012
1589
|
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Look up companies in a national company register — currently Norway
|
|
1592
|
+
* (Brønnøysund Enhetsregisteret). `q` is an organisation number, a "NO…MVA"
|
|
1593
|
+
* VAT number or a company name; an exact number match yields one result.
|
|
1594
|
+
* Ideal for prefilling createNorwayEnrollment() and the buyer fields of an
|
|
1595
|
+
* e-invoice. For France use sireneLookup().
|
|
1596
|
+
*
|
|
1597
|
+
* Errors: 404 (HttpException) when nothing is found, 422 (ValidationException)
|
|
1598
|
+
* on invalid input / unsupported country, 429 (HttpException) when the
|
|
1599
|
+
* per-user rate limit (30/min) is exceeded.
|
|
1600
|
+
*/
|
|
1601
|
+
async registryLookup(country, q) {
|
|
1602
|
+
const normalizedCountry = this.normalizeLookupCountry(country);
|
|
1603
|
+
const query = q.trim();
|
|
1604
|
+
if (query === "") {
|
|
1605
|
+
throw new TaxoraException("q must not be empty.");
|
|
1606
|
+
}
|
|
1607
|
+
if (normalizedCountry === "NO") {
|
|
1608
|
+
const digits = NorwegianOrgNumber.normalize(query);
|
|
1609
|
+
if (/^\d{9}$/.test(digits) && !NorwegianOrgNumber.isValid(digits)) {
|
|
1610
|
+
throw new TaxoraException(`Invalid Norwegian organisation number "${query}" (check digit).`);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
const params = new URLSearchParams();
|
|
1614
|
+
params.set("country", normalizedCountry);
|
|
1615
|
+
params.set("q", query);
|
|
1616
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/registry-lookup?${params.toString()}`);
|
|
1617
|
+
const data = await this.parseJsonResponse(response);
|
|
1618
|
+
const results = Array.isArray(data["results"]) ? data["results"] : [];
|
|
1619
|
+
return results.filter((row) => typeof row === "object" && row !== null && !Array.isArray(row)).map((row) => RegistryCompany.fromArray(row));
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Check whether a participant (typically the buyer) can receive e-invoices
|
|
1623
|
+
* over Peppol. For Norway pass the organisation number (scheme defaults to
|
|
1624
|
+
* "0192"): `reachable === true` means the buyer is registered in ELMA and must
|
|
1625
|
+
* receive an EHF e-invoice. The directory resolves asynchronously —
|
|
1626
|
+
* `status === 'pending'` (`reachable === null`) means ask again in a few
|
|
1627
|
+
* seconds. Free of charge; definite answers are cached for 24h server-side.
|
|
1628
|
+
*
|
|
1629
|
+
* A Norwegian organisation number is checked (mod-11) and normalized before
|
|
1630
|
+
* sending. Errors: 422 (ValidationException) on invalid input, 502
|
|
1631
|
+
* (HttpException) on provider failure.
|
|
1632
|
+
*/
|
|
1633
|
+
async peppolLookup(country, id, scheme) {
|
|
1634
|
+
const normalizedCountry = this.normalizeLookupCountry(country);
|
|
1635
|
+
let participantId = id.trim();
|
|
1636
|
+
if (participantId === "") {
|
|
1637
|
+
throw new TaxoraException("id must not be empty.");
|
|
1638
|
+
}
|
|
1639
|
+
if (scheme !== void 0 && !PEPPOL_SCHEME_REGEX.test(scheme)) {
|
|
1640
|
+
throw new TaxoraException('scheme must be a 4-digit Peppol identifier scheme, e.g. "0192".');
|
|
1641
|
+
}
|
|
1642
|
+
if (normalizedCountry === "NO" && (scheme === void 0 || scheme === NorwegianOrgNumber.PEPPOL_SCHEME)) {
|
|
1643
|
+
if (!NorwegianOrgNumber.isValid(participantId)) {
|
|
1644
|
+
throw new TaxoraException(`Invalid Norwegian organisation number "${participantId}".`);
|
|
1645
|
+
}
|
|
1646
|
+
participantId = NorwegianOrgNumber.normalize(participantId);
|
|
1647
|
+
}
|
|
1648
|
+
const params = new URLSearchParams();
|
|
1649
|
+
params.set("country", normalizedCountry);
|
|
1650
|
+
params.set("id", participantId);
|
|
1651
|
+
if (scheme !== void 0) params.set("scheme", scheme);
|
|
1652
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/peppol-lookup?${params.toString()}`);
|
|
1653
|
+
const data = await this.parseJsonResponse(response);
|
|
1654
|
+
return PeppolLookupResult.fromArray(data);
|
|
1655
|
+
}
|
|
1013
1656
|
// ---------------------------------------------------------------------
|
|
1014
1657
|
// Transactions
|
|
1015
1658
|
// ---------------------------------------------------------------------
|
|
@@ -1033,6 +1676,7 @@ var EReportingEndpoint = class {
|
|
|
1033
1676
|
this.validateInvoiceNumber(input.invoiceNumber);
|
|
1034
1677
|
this.validateInvoiceLines(input.invoiceLines);
|
|
1035
1678
|
this.validateOptionalCodes(input);
|
|
1679
|
+
this.validateBuyerFields(input);
|
|
1036
1680
|
const body = {
|
|
1037
1681
|
compliance_enrollment_id: input.complianceEnrollmentId,
|
|
1038
1682
|
transaction_type: input.transactionType,
|
|
@@ -1048,13 +1692,23 @@ var EReportingEndpoint = class {
|
|
|
1048
1692
|
const data = await this.parseJsonResponse(response);
|
|
1049
1693
|
return ComplianceTransaction.fromArray(data);
|
|
1050
1694
|
}
|
|
1051
|
-
/** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type. */
|
|
1695
|
+
/** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type / enrollment / country. */
|
|
1052
1696
|
async listTransactions(filters = {}) {
|
|
1697
|
+
if (filters.complianceEnrollmentId !== void 0) {
|
|
1698
|
+
this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
|
|
1699
|
+
}
|
|
1700
|
+
if (filters.country !== void 0 && !COUNTRY_REGEX.test(filters.country)) {
|
|
1701
|
+
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1702
|
+
}
|
|
1053
1703
|
const params = new URLSearchParams();
|
|
1054
1704
|
if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
|
|
1055
1705
|
if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
|
|
1056
1706
|
if (filters.state !== void 0) params.set("state", filters.state);
|
|
1057
1707
|
if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
|
|
1708
|
+
if (filters.complianceEnrollmentId !== void 0) {
|
|
1709
|
+
params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
|
|
1710
|
+
}
|
|
1711
|
+
if (filters.country !== void 0) params.set("country", filters.country.toUpperCase());
|
|
1058
1712
|
if (filters.page !== void 0) params.set("page", String(filters.page));
|
|
1059
1713
|
if (filters.perPage !== void 0) params.set("per_page", String(filters.perPage));
|
|
1060
1714
|
const queryString = params.toString();
|
|
@@ -1076,6 +1730,7 @@ var EReportingEndpoint = class {
|
|
|
1076
1730
|
if (input.invoiceNumber !== void 0) this.validateInvoiceNumber(input.invoiceNumber);
|
|
1077
1731
|
if (input.invoiceLines !== void 0) this.validateInvoiceLines(input.invoiceLines);
|
|
1078
1732
|
this.validateOptionalCodes(input);
|
|
1733
|
+
this.validateBuyerFields(input);
|
|
1079
1734
|
const body = {};
|
|
1080
1735
|
if (input.transactionType !== void 0) body["transaction_type"] = input.transactionType;
|
|
1081
1736
|
if (input.invoiceNumber !== void 0) body["invoice_number"] = input.invoiceNumber;
|
|
@@ -1198,6 +1853,14 @@ var EReportingEndpoint = class {
|
|
|
1198
1853
|
* returned as HTTP 400 (surfaced as HttpException), not 422.
|
|
1199
1854
|
*/
|
|
1200
1855
|
async requestEReportingAccess(input = {}) {
|
|
1856
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1857
|
+
if (input.countries !== void 0) {
|
|
1858
|
+
input.countries.forEach((country, index) => {
|
|
1859
|
+
if (!COUNTRY_REGEX.test(country)) {
|
|
1860
|
+
throw new TaxoraException(`countries[${index}] must be a 2-character ISO 3166-1 alpha-2 code.`);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1201
1864
|
const body = {};
|
|
1202
1865
|
if (input.name !== void 0) body["name"] = input.name;
|
|
1203
1866
|
if (input.email !== void 0) body["email"] = input.email;
|
|
@@ -1205,6 +1868,8 @@ var EReportingEndpoint = class {
|
|
|
1205
1868
|
if (input.phone !== void 0) body["phone"] = input.phone;
|
|
1206
1869
|
if (input.message !== void 0) body["message"] = input.message;
|
|
1207
1870
|
if (input.language !== void 0) body["language"] = input.language;
|
|
1871
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1872
|
+
if (input.countries !== void 0) body["countries"] = input.countries.map((c) => c.toUpperCase());
|
|
1208
1873
|
const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/e-reporting-request`, body);
|
|
1209
1874
|
await this.parseJsonResponse(response);
|
|
1210
1875
|
}
|
|
@@ -1240,6 +1905,73 @@ var EReportingEndpoint = class {
|
|
|
1240
1905
|
if (input.country !== void 0 && input.country.length !== 2) {
|
|
1241
1906
|
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1242
1907
|
}
|
|
1908
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1909
|
+
}
|
|
1910
|
+
isNorwayEnrollmentInput(input) {
|
|
1911
|
+
return typeof input.country === "string" && input.country.trim().toUpperCase() === "NO";
|
|
1912
|
+
}
|
|
1913
|
+
/** Validates the Norway input and builds its wire body (org number normalized to 9 digits). */
|
|
1914
|
+
buildNorwayEnrollmentBody(input) {
|
|
1915
|
+
if (typeof input.orgNumber !== "string" || !NorwegianOrgNumber.isValid(input.orgNumber)) {
|
|
1916
|
+
throw new TaxoraException(
|
|
1917
|
+
"orgNumber must be a valid 9-digit Norwegian organisation number (mod-11 check digit)."
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
for (const field of ["companyName", "address", "city", "postalcode"]) {
|
|
1921
|
+
if (typeof input[field] !== "string" || input[field].trim() === "") {
|
|
1922
|
+
throw new TaxoraException(`${field} is required for a Norway enrollment.`);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
this.validateEmail(input.email, "email");
|
|
1926
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1927
|
+
if (input.legalCountry !== void 0 && !COUNTRY_REGEX.test(input.legalCountry)) {
|
|
1928
|
+
throw new TaxoraException("legalCountry must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1929
|
+
}
|
|
1930
|
+
const body = {
|
|
1931
|
+
country: "NO",
|
|
1932
|
+
org_number: NorwegianOrgNumber.normalize(input.orgNumber),
|
|
1933
|
+
company_name: input.companyName,
|
|
1934
|
+
address: input.address,
|
|
1935
|
+
city: input.city,
|
|
1936
|
+
postalcode: input.postalcode,
|
|
1937
|
+
email: input.email
|
|
1938
|
+
};
|
|
1939
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1940
|
+
if (input.regime !== void 0) body["regime"] = input.regime;
|
|
1941
|
+
if (input.vatNumber !== void 0) body["vat_number"] = input.vatNumber;
|
|
1942
|
+
if (input.province !== void 0) body["province"] = input.province;
|
|
1943
|
+
if (input.vatRegistered !== void 0) body["vat_registered"] = input.vatRegistered;
|
|
1944
|
+
if (input.enterpriseRegister !== void 0) body["enterprise_register"] = input.enterpriseRegister;
|
|
1945
|
+
if (input.reception !== void 0) body["reception"] = input.reception;
|
|
1946
|
+
if (input.legalCountry !== void 0) body["legal_country"] = input.legalCountry.toUpperCase();
|
|
1947
|
+
if (input.reportingStartDate !== void 0) {
|
|
1948
|
+
body["reporting_start_date"] = this.formatDate(input.reportingStartDate);
|
|
1949
|
+
}
|
|
1950
|
+
if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
|
|
1951
|
+
return body;
|
|
1952
|
+
}
|
|
1953
|
+
async createEnrollmentFromBody(body) {
|
|
1954
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/enrollments`, body);
|
|
1955
|
+
const data = await this.parseJsonResponse(response);
|
|
1956
|
+
return ComplianceEnrollment.fromArray(data);
|
|
1957
|
+
}
|
|
1958
|
+
validateService(service) {
|
|
1959
|
+
if (!SERVICES.includes(service)) {
|
|
1960
|
+
throw new TaxoraException(`Invalid service "${String(service)}". Expected one of: ${SERVICES.join(", ")}.`);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
validateEmail(email, name) {
|
|
1964
|
+
if (typeof email !== "string" || email.trim() === "" || !email.includes("@")) {
|
|
1965
|
+
throw new TaxoraException(`${name} must be a valid email address.`);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
/** Lookup routes take a 2-letter country; the API expects it upper-case. */
|
|
1969
|
+
normalizeLookupCountry(country) {
|
|
1970
|
+
const normalized = country.trim().toUpperCase();
|
|
1971
|
+
if (!COUNTRY_REGEX.test(normalized)) {
|
|
1972
|
+
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1973
|
+
}
|
|
1974
|
+
return normalized;
|
|
1243
1975
|
}
|
|
1244
1976
|
validateId(id, name) {
|
|
1245
1977
|
if (!Number.isInteger(id) || id <= 0) {
|
|
@@ -1279,6 +2011,33 @@ var EReportingEndpoint = class {
|
|
|
1279
2011
|
throw new TaxoraException("counterpartyCountry must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1280
2012
|
}
|
|
1281
2013
|
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Format checks for the buyer/document fields. Country rules (e.g. the Norway
|
|
2016
|
+
* org number being required) depend on the enrollment and stay server-side;
|
|
2017
|
+
* a register id is only check-digit validated when the buyer is Norwegian.
|
|
2018
|
+
*/
|
|
2019
|
+
validateBuyerFields(input) {
|
|
2020
|
+
if (input.counterpartyRegisterId !== void 0) {
|
|
2021
|
+
if (input.counterpartyRegisterId.length > 30) {
|
|
2022
|
+
throw new TaxoraException("counterpartyRegisterId must not exceed 30 characters.");
|
|
2023
|
+
}
|
|
2024
|
+
if (input.counterpartyCountry?.toUpperCase() === "NO" && !NorwegianOrgNumber.isValid(input.counterpartyRegisterId)) {
|
|
2025
|
+
throw new TaxoraException(
|
|
2026
|
+
"counterpartyRegisterId must be a valid 9-digit Norwegian organisation number (mod-11 check digit)."
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (input.counterpartyEmail !== void 0) this.validateEmail(input.counterpartyEmail, "counterpartyEmail");
|
|
2031
|
+
if (input.buyerReference !== void 0 && input.buyerReference.length > 100) {
|
|
2032
|
+
throw new TaxoraException("buyerReference must not exceed 100 characters.");
|
|
2033
|
+
}
|
|
2034
|
+
if (input.amendedNumber !== void 0 && input.amendedNumber.length > 50) {
|
|
2035
|
+
throw new TaxoraException("amendedNumber must not exceed 50 characters.");
|
|
2036
|
+
}
|
|
2037
|
+
if (input.extraInfo !== void 0 && input.extraInfo.length > 1e3) {
|
|
2038
|
+
throw new TaxoraException("extraInfo must not exceed 1000 characters.");
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1282
2041
|
// ---------------------------------------------------------------------
|
|
1283
2042
|
// Body building (camelCase input → snake_case wire format)
|
|
1284
2043
|
// ---------------------------------------------------------------------
|
|
@@ -1315,6 +2074,16 @@ var EReportingEndpoint = class {
|
|
|
1315
2074
|
if (input.remittanceInformation !== void 0) body["remittance_information"] = input.remittanceInformation;
|
|
1316
2075
|
if (input.paymentMethodText !== void 0) body["payment_method_text"] = input.paymentMethodText;
|
|
1317
2076
|
if (input.paymentTerms !== void 0) body["payment_terms"] = input.paymentTerms;
|
|
2077
|
+
if (input.counterpartyRegisterId !== void 0) body["counterparty_register_id"] = input.counterpartyRegisterId;
|
|
2078
|
+
if (input.counterpartyAddress !== void 0) body["counterparty_address"] = input.counterpartyAddress;
|
|
2079
|
+
if (input.counterpartyCity !== void 0) body["counterparty_city"] = input.counterpartyCity;
|
|
2080
|
+
if (input.counterpartyPostalcode !== void 0) body["counterparty_postalcode"] = input.counterpartyPostalcode;
|
|
2081
|
+
if (input.counterpartyEmail !== void 0) body["counterparty_email"] = input.counterpartyEmail;
|
|
2082
|
+
if (input.buyerReference !== void 0) body["buyer_reference"] = input.buyerReference;
|
|
2083
|
+
if (input.isCreditNote !== void 0) body["is_credit_note"] = input.isCreditNote;
|
|
2084
|
+
if (input.amendedNumber !== void 0) body["amended_number"] = input.amendedNumber;
|
|
2085
|
+
if (input.amendedDate !== void 0) body["amended_date"] = this.formatDate(input.amendedDate);
|
|
2086
|
+
if (input.extraInfo !== void 0) body["extra_info"] = input.extraInfo;
|
|
1318
2087
|
}
|
|
1319
2088
|
// ---------------------------------------------------------------------
|
|
1320
2089
|
// HTTP plumbing (kept in sync with SmartEnrichmentEndpoint)
|
|
@@ -1335,7 +2104,8 @@ var EReportingEndpoint = class {
|
|
|
1335
2104
|
async sendRequest(method, url, body) {
|
|
1336
2105
|
const options = { headers: this.buildHeaders() };
|
|
1337
2106
|
if (body) options.body = JSON.stringify(body);
|
|
1338
|
-
|
|
2107
|
+
const send = () => this.httpClient.request(method, url, options);
|
|
2108
|
+
return method === "GET" ? withResponseRetries(this.retryPolicy, "e-reporting request", send) : send();
|
|
1339
2109
|
}
|
|
1340
2110
|
/** Multipart upload: Content-Type is left to fetch so the boundary is set correctly. */
|
|
1341
2111
|
async sendMultipartRequest(method, url, form) {
|
|
@@ -1348,16 +2118,22 @@ var EReportingEndpoint = class {
|
|
|
1348
2118
|
throw this.buildValidationException(responseText);
|
|
1349
2119
|
}
|
|
1350
2120
|
if (!response.ok) {
|
|
1351
|
-
throw new HttpException(
|
|
2121
|
+
throw new HttpException(
|
|
2122
|
+
describeApiError(responseText, response.status),
|
|
2123
|
+
response.status,
|
|
2124
|
+
responseText,
|
|
2125
|
+
{},
|
|
2126
|
+
response.headers.get("retry-after")
|
|
2127
|
+
);
|
|
1352
2128
|
}
|
|
1353
2129
|
let parsed;
|
|
1354
2130
|
try {
|
|
1355
2131
|
parsed = JSON.parse(responseText);
|
|
1356
2132
|
} catch {
|
|
1357
|
-
throw new HttpException("Invalid JSON response",
|
|
2133
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
1358
2134
|
}
|
|
1359
2135
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1360
|
-
throw new HttpException("Unexpected response format",
|
|
2136
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
1361
2137
|
}
|
|
1362
2138
|
const obj = parsed;
|
|
1363
2139
|
if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
|
|
@@ -1375,7 +2151,13 @@ var EReportingEndpoint = class {
|
|
|
1375
2151
|
throw this.buildValidationException(responseText);
|
|
1376
2152
|
}
|
|
1377
2153
|
if (!response.ok) {
|
|
1378
|
-
throw new HttpException(
|
|
2154
|
+
throw new HttpException(
|
|
2155
|
+
describeApiError(responseText, response.status),
|
|
2156
|
+
response.status,
|
|
2157
|
+
responseText,
|
|
2158
|
+
{},
|
|
2159
|
+
response.headers.get("retry-after")
|
|
2160
|
+
);
|
|
1379
2161
|
}
|
|
1380
2162
|
}
|
|
1381
2163
|
/**
|
|
@@ -1419,6 +2201,29 @@ var EReportingEndpoint = class {
|
|
|
1419
2201
|
}
|
|
1420
2202
|
};
|
|
1421
2203
|
|
|
2204
|
+
// src/enums/SmartEnrichmentMode.ts
|
|
2205
|
+
var SmartEnrichmentMode = {
|
|
2206
|
+
DEFAULT: "default",
|
|
2207
|
+
COMPLEX: "complex"
|
|
2208
|
+
};
|
|
2209
|
+
var VALUES8 = Object.values(SmartEnrichmentMode);
|
|
2210
|
+
function toSmartEnrichmentMode(value) {
|
|
2211
|
+
if (typeof value === "string") {
|
|
2212
|
+
const normalized = value.trim().toLowerCase();
|
|
2213
|
+
if (VALUES8.includes(normalized)) {
|
|
2214
|
+
return normalized;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return void 0;
|
|
2218
|
+
}
|
|
2219
|
+
var DESCRIPTIONS8 = {
|
|
2220
|
+
[SmartEnrichmentMode.DEFAULT]: "One provider searches; a second is consulted only if it finds nothing.",
|
|
2221
|
+
[SmartEnrichmentMode.COMPLEX]: "Two providers search independently and are cross-checked against the tax authority. Costs more."
|
|
2222
|
+
};
|
|
2223
|
+
function describeSmartEnrichmentMode(mode) {
|
|
2224
|
+
return DESCRIPTIONS8[mode];
|
|
2225
|
+
}
|
|
2226
|
+
|
|
1422
2227
|
// src/enums/SmartEnrichmentStatus.ts
|
|
1423
2228
|
var SmartEnrichmentStatus = {
|
|
1424
2229
|
FOUND: "found",
|
|
@@ -1430,17 +2235,17 @@ var SmartEnrichmentStatus = {
|
|
|
1430
2235
|
FAILED: "failed",
|
|
1431
2236
|
UNKNOWN: "unknown"
|
|
1432
2237
|
};
|
|
1433
|
-
var
|
|
2238
|
+
var VALUES9 = Object.values(SmartEnrichmentStatus);
|
|
1434
2239
|
function toSmartEnrichmentStatus(value) {
|
|
1435
2240
|
if (typeof value === "string") {
|
|
1436
2241
|
const normalized = value.trim().toLowerCase();
|
|
1437
|
-
if (
|
|
2242
|
+
if (VALUES9.includes(normalized)) {
|
|
1438
2243
|
return normalized;
|
|
1439
2244
|
}
|
|
1440
2245
|
}
|
|
1441
2246
|
return SmartEnrichmentStatus.UNKNOWN;
|
|
1442
2247
|
}
|
|
1443
|
-
var
|
|
2248
|
+
var DESCRIPTIONS9 = {
|
|
1444
2249
|
[SmartEnrichmentStatus.FOUND]: "A VAT number was resolved and confirmed.",
|
|
1445
2250
|
[SmartEnrichmentStatus.NO_VAT_EXISTS]: "The company was identified but has no VAT number.",
|
|
1446
2251
|
[SmartEnrichmentStatus.NOT_FOUND]: "No sufficiently-confident match could be resolved.",
|
|
@@ -1451,12 +2256,12 @@ var DESCRIPTIONS5 = {
|
|
|
1451
2256
|
[SmartEnrichmentStatus.UNKNOWN]: "Unknown status."
|
|
1452
2257
|
};
|
|
1453
2258
|
function describeSmartEnrichmentStatus(status) {
|
|
1454
|
-
return
|
|
2259
|
+
return DESCRIPTIONS9[status];
|
|
1455
2260
|
}
|
|
1456
2261
|
|
|
1457
2262
|
// src/dto/SmartEnrichmentResource.ts
|
|
1458
2263
|
var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
1459
|
-
constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source) {
|
|
2264
|
+
constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source, mode = void 0, providerVerdicts = [], addressQuality = void 0) {
|
|
1460
2265
|
this.status = status;
|
|
1461
2266
|
this.vatNumber = vatNumber;
|
|
1462
2267
|
this.vatType = vatType;
|
|
@@ -1465,6 +2270,9 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1465
2270
|
this.matchedAddress = matchedAddress;
|
|
1466
2271
|
this.country = country;
|
|
1467
2272
|
this.source = source;
|
|
2273
|
+
this.mode = mode;
|
|
2274
|
+
this.providerVerdicts = providerVerdicts;
|
|
2275
|
+
this.addressQuality = addressQuality;
|
|
1468
2276
|
}
|
|
1469
2277
|
status;
|
|
1470
2278
|
vatNumber;
|
|
@@ -1474,6 +2282,9 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1474
2282
|
matchedAddress;
|
|
1475
2283
|
country;
|
|
1476
2284
|
source;
|
|
2285
|
+
mode;
|
|
2286
|
+
providerVerdicts;
|
|
2287
|
+
addressQuality;
|
|
1477
2288
|
static fromArray(data) {
|
|
1478
2289
|
const str = (key) => {
|
|
1479
2290
|
const v = data[key];
|
|
@@ -1483,6 +2294,8 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1483
2294
|
const v = data[key];
|
|
1484
2295
|
return typeof v === "number" ? v : void 0;
|
|
1485
2296
|
};
|
|
2297
|
+
const verdicts = data["providerVerdicts"];
|
|
2298
|
+
const quality = data["addressQuality"];
|
|
1486
2299
|
return new _SmartEnrichmentResource(
|
|
1487
2300
|
toSmartEnrichmentStatus(data["status"]),
|
|
1488
2301
|
str("vatNumber"),
|
|
@@ -1491,7 +2304,10 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1491
2304
|
str("matchedCompanyName"),
|
|
1492
2305
|
str("matchedAddress"),
|
|
1493
2306
|
str("country"),
|
|
1494
|
-
str("source")
|
|
2307
|
+
str("source"),
|
|
2308
|
+
toSmartEnrichmentMode(data["mode"]),
|
|
2309
|
+
Array.isArray(verdicts) ? verdicts.filter((v) => typeof v === "object" && v !== null) : [],
|
|
2310
|
+
typeof quality === "object" && quality !== null ? quality : void 0
|
|
1495
2311
|
);
|
|
1496
2312
|
}
|
|
1497
2313
|
toArray() {
|
|
@@ -1503,7 +2319,10 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1503
2319
|
matchedCompanyName: this.matchedCompanyName ?? null,
|
|
1504
2320
|
matchedAddress: this.matchedAddress ?? null,
|
|
1505
2321
|
country: this.country ?? null,
|
|
1506
|
-
source: this.source ?? null
|
|
2322
|
+
source: this.source ?? null,
|
|
2323
|
+
mode: this.mode ?? null,
|
|
2324
|
+
providerVerdicts: this.providerVerdicts,
|
|
2325
|
+
addressQuality: this.addressQuality ?? null
|
|
1507
2326
|
};
|
|
1508
2327
|
}
|
|
1509
2328
|
};
|
|
@@ -1655,13 +2474,13 @@ var SmartEnrichmentHistoryPage = class _SmartEnrichmentHistoryPage {
|
|
|
1655
2474
|
};
|
|
1656
2475
|
|
|
1657
2476
|
// src/dto/SmartEnrichmentStatistics.ts
|
|
1658
|
-
function
|
|
2477
|
+
function asString9(value, fallback = "") {
|
|
1659
2478
|
return typeof value === "string" ? value : fallback;
|
|
1660
2479
|
}
|
|
1661
2480
|
function asNumber6(value) {
|
|
1662
2481
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
1663
2482
|
}
|
|
1664
|
-
function
|
|
2483
|
+
function asRecord2(value) {
|
|
1665
2484
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1666
2485
|
}
|
|
1667
2486
|
function asRecords2(value) {
|
|
@@ -1684,32 +2503,32 @@ var SmartEnrichmentStatistics = class _SmartEnrichmentStatistics {
|
|
|
1684
2503
|
byOutcome;
|
|
1685
2504
|
confidenceBuckets;
|
|
1686
2505
|
static fromArray(data) {
|
|
1687
|
-
const range =
|
|
1688
|
-
const totals =
|
|
2506
|
+
const range = asRecord2(data["range"]);
|
|
2507
|
+
const totals = asRecord2(data["totals"]);
|
|
1689
2508
|
const timeSeries = asRecords2(data["time_series"]).map((row) => ({
|
|
1690
|
-
bucket:
|
|
2509
|
+
bucket: asString9(row["bucket"]),
|
|
1691
2510
|
jobs: asNumber6(row["jobs"]),
|
|
1692
2511
|
items: asNumber6(row["items"]),
|
|
1693
2512
|
found: asNumber6(row["found"])
|
|
1694
2513
|
}));
|
|
1695
2514
|
const bySource = asRecords2(data["by_source"]).map((row) => ({
|
|
1696
|
-
source:
|
|
2515
|
+
source: asString9(row["source"]),
|
|
1697
2516
|
jobs: asNumber6(row["jobs"]),
|
|
1698
2517
|
found: asNumber6(row["found"])
|
|
1699
2518
|
}));
|
|
1700
2519
|
const byOutcome = asRecords2(data["by_outcome"]).map((row) => ({
|
|
1701
|
-
outcome:
|
|
2520
|
+
outcome: asString9(row["outcome"]),
|
|
1702
2521
|
count: asNumber6(row["count"])
|
|
1703
2522
|
}));
|
|
1704
2523
|
const confidenceBuckets = asRecords2(data["confidence_buckets"]).map((row) => ({
|
|
1705
|
-
bucket:
|
|
2524
|
+
bucket: asString9(row["bucket"]),
|
|
1706
2525
|
count: asNumber6(row["count"])
|
|
1707
2526
|
}));
|
|
1708
2527
|
return new _SmartEnrichmentStatistics(
|
|
1709
2528
|
{
|
|
1710
|
-
dateFrom:
|
|
1711
|
-
dateTo:
|
|
1712
|
-
interval:
|
|
2529
|
+
dateFrom: asString9(range["date_from"]),
|
|
2530
|
+
dateTo: asString9(range["date_to"]),
|
|
2531
|
+
interval: asString9(range["interval"])
|
|
1713
2532
|
},
|
|
1714
2533
|
{
|
|
1715
2534
|
jobs: asNumber6(totals["jobs"]),
|
|
@@ -1731,16 +2550,18 @@ var STATISTICS_INTERVALS = ["day", "week", "month"];
|
|
|
1731
2550
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
1732
2551
|
var DEFAULT_POLL_TIMEOUT_MS = 12e4;
|
|
1733
2552
|
var SmartEnrichmentEndpoint = class {
|
|
1734
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
2553
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
1735
2554
|
this.baseUrl = baseUrl;
|
|
1736
2555
|
this.apiKey = apiKey;
|
|
1737
2556
|
this.tokenStorage = tokenStorage;
|
|
1738
2557
|
this.httpClient = httpClient;
|
|
2558
|
+
this.retryPolicy = retryPolicy;
|
|
1739
2559
|
}
|
|
1740
2560
|
baseUrl;
|
|
1741
2561
|
apiKey;
|
|
1742
2562
|
tokenStorage;
|
|
1743
2563
|
httpClient;
|
|
2564
|
+
retryPolicy;
|
|
1744
2565
|
/**
|
|
1745
2566
|
* Single reverse lookup. Returns a confident result synchronously, or a `processing`
|
|
1746
2567
|
* job (poll with get(job.jobId) or waitForCompletion(job.jobId)) when deeper resolution
|
|
@@ -1755,6 +2576,7 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1755
2576
|
if (input.street !== void 0) body["street"] = input.street;
|
|
1756
2577
|
if (input.postalCode !== void 0) body["postalCode"] = input.postalCode;
|
|
1757
2578
|
if (input.city !== void 0) body["city"] = input.city;
|
|
2579
|
+
if (input.mode !== void 0) body["mode"] = input.mode;
|
|
1758
2580
|
const response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment`, body);
|
|
1759
2581
|
const data = await this.parseJsonResponse(response, [200, 202]);
|
|
1760
2582
|
return SmartEnrichmentJob.fromArray(data);
|
|
@@ -1762,13 +2584,18 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1762
2584
|
/**
|
|
1763
2585
|
* Bulk batch — always async. Returns a `processing` job; results arrive via the
|
|
1764
2586
|
* `enrichment.completed` webhook and are retrievable via get(job.jobId).
|
|
2587
|
+
*
|
|
2588
|
+
* @param mode Search depth for every row in the batch. A row carrying its own `mode` overrides
|
|
2589
|
+
* it, so a caller can pay for `complex` on just the rows that need it.
|
|
1765
2590
|
*/
|
|
1766
|
-
async bulkLookup(items) {
|
|
2591
|
+
async bulkLookup(items, mode) {
|
|
1767
2592
|
if (items.length === 0) {
|
|
1768
2593
|
throw new TaxoraException("bulkLookup requires at least one item.");
|
|
1769
2594
|
}
|
|
1770
2595
|
items.forEach((item, index) => this.validateInput(item, `items[${index}]`));
|
|
1771
|
-
const
|
|
2596
|
+
const body = { items };
|
|
2597
|
+
if (mode !== void 0) body["mode"] = mode;
|
|
2598
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment/bulk`, body);
|
|
1772
2599
|
const data = await this.parseJsonResponse(response, [200, 202]);
|
|
1773
2600
|
return SmartEnrichmentJob.fromArray(data);
|
|
1774
2601
|
}
|
|
@@ -1910,24 +2737,34 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1910
2737
|
async sendRequest(method, url, body) {
|
|
1911
2738
|
const options = { headers: this.buildHeaders() };
|
|
1912
2739
|
if (body) options.body = JSON.stringify(body);
|
|
1913
|
-
|
|
2740
|
+
const send = () => this.httpClient.request(method, url, options);
|
|
2741
|
+
return method === "GET" ? withResponseRetries(this.retryPolicy, "Smart Enrichment request", send) : send();
|
|
1914
2742
|
}
|
|
1915
2743
|
async parseJsonResponse(response, acceptedStatuses = [200]) {
|
|
1916
2744
|
const responseText = await response.text();
|
|
1917
2745
|
if (response.status === 422) {
|
|
1918
|
-
throw new ValidationException(
|
|
2746
|
+
throw new ValidationException(
|
|
2747
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2748
|
+
this.parseValidationErrors(responseText)
|
|
2749
|
+
);
|
|
1919
2750
|
}
|
|
1920
2751
|
if (!response.ok && !acceptedStatuses.includes(response.status)) {
|
|
1921
|
-
throw new HttpException(
|
|
2752
|
+
throw new HttpException(
|
|
2753
|
+
describeApiError(responseText, response.status),
|
|
2754
|
+
response.status,
|
|
2755
|
+
responseText,
|
|
2756
|
+
{},
|
|
2757
|
+
response.headers.get("retry-after")
|
|
2758
|
+
);
|
|
1922
2759
|
}
|
|
1923
2760
|
let parsed;
|
|
1924
2761
|
try {
|
|
1925
2762
|
parsed = JSON.parse(responseText);
|
|
1926
2763
|
} catch {
|
|
1927
|
-
throw new HttpException("Invalid JSON response",
|
|
2764
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
1928
2765
|
}
|
|
1929
2766
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1930
|
-
throw new HttpException("Unexpected response format",
|
|
2767
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
1931
2768
|
}
|
|
1932
2769
|
const obj = parsed;
|
|
1933
2770
|
if (obj["data"] !== void 0 && (typeof obj["data"] === "object" || Array.isArray(obj["data"]))) {
|
|
@@ -1943,19 +2780,28 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1943
2780
|
async parseRawJson(response) {
|
|
1944
2781
|
const responseText = await response.text();
|
|
1945
2782
|
if (response.status === 422) {
|
|
1946
|
-
throw new ValidationException(
|
|
2783
|
+
throw new ValidationException(
|
|
2784
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2785
|
+
this.parseValidationErrors(responseText)
|
|
2786
|
+
);
|
|
1947
2787
|
}
|
|
1948
2788
|
if (!response.ok) {
|
|
1949
|
-
throw new HttpException(
|
|
2789
|
+
throw new HttpException(
|
|
2790
|
+
describeApiError(responseText, response.status),
|
|
2791
|
+
response.status,
|
|
2792
|
+
responseText,
|
|
2793
|
+
{},
|
|
2794
|
+
response.headers.get("retry-after")
|
|
2795
|
+
);
|
|
1950
2796
|
}
|
|
1951
2797
|
let parsed;
|
|
1952
2798
|
try {
|
|
1953
2799
|
parsed = JSON.parse(responseText);
|
|
1954
2800
|
} catch {
|
|
1955
|
-
throw new HttpException("Invalid JSON response",
|
|
2801
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
1956
2802
|
}
|
|
1957
2803
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1958
|
-
throw new HttpException("Unexpected response format",
|
|
2804
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
1959
2805
|
}
|
|
1960
2806
|
return parsed;
|
|
1961
2807
|
}
|
|
@@ -1966,10 +2812,19 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1966
2812
|
async parseCsvResponse(response) {
|
|
1967
2813
|
const responseText = await response.text();
|
|
1968
2814
|
if (response.status === 422) {
|
|
1969
|
-
throw new ValidationException(
|
|
2815
|
+
throw new ValidationException(
|
|
2816
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2817
|
+
this.parseValidationErrors(responseText)
|
|
2818
|
+
);
|
|
1970
2819
|
}
|
|
1971
2820
|
if (!response.ok) {
|
|
1972
|
-
throw new HttpException(
|
|
2821
|
+
throw new HttpException(
|
|
2822
|
+
describeApiError(responseText, response.status),
|
|
2823
|
+
response.status,
|
|
2824
|
+
responseText,
|
|
2825
|
+
{},
|
|
2826
|
+
response.headers.get("retry-after")
|
|
2827
|
+
);
|
|
1973
2828
|
}
|
|
1974
2829
|
return responseText;
|
|
1975
2830
|
}
|
|
@@ -2006,14 +2861,14 @@ var VatState = {
|
|
|
2006
2861
|
function getFailedVatStates() {
|
|
2007
2862
|
return [VatState.INVALID, VatState.FRAUD];
|
|
2008
2863
|
}
|
|
2009
|
-
var
|
|
2864
|
+
var DESCRIPTIONS10 = {
|
|
2010
2865
|
[VatState.VALID]: "The VAT number is valid and active.",
|
|
2011
2866
|
[VatState.INVALID]: "The VAT number is invalid or not registered.",
|
|
2012
2867
|
[VatState.FRAUD]: "The VAT number has been flagged as potentially fraudulent.",
|
|
2013
2868
|
[VatState.UNKNOWN]: "The VAT number state could not be determined."
|
|
2014
2869
|
};
|
|
2015
2870
|
function describeVatState(state) {
|
|
2016
|
-
return
|
|
2871
|
+
return DESCRIPTIONS10[state];
|
|
2017
2872
|
}
|
|
2018
2873
|
|
|
2019
2874
|
// src/dto/CompanyAddress.ts
|
|
@@ -2478,16 +3333,18 @@ var VatValidationAddressInput = class _VatValidationAddressInput {
|
|
|
2478
3333
|
// src/endpoints/VatEndpoint.ts
|
|
2479
3334
|
var DATE_REGEX2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
2480
3335
|
var VatEndpoint = class {
|
|
2481
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
3336
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
2482
3337
|
this.baseUrl = baseUrl;
|
|
2483
3338
|
this.apiKey = apiKey;
|
|
2484
3339
|
this.tokenStorage = tokenStorage;
|
|
2485
3340
|
this.httpClient = httpClient;
|
|
3341
|
+
this.retryPolicy = retryPolicy;
|
|
2486
3342
|
}
|
|
2487
3343
|
baseUrl;
|
|
2488
3344
|
apiKey;
|
|
2489
3345
|
tokenStorage;
|
|
2490
3346
|
httpClient;
|
|
3347
|
+
retryPolicy;
|
|
2491
3348
|
async validate(vatUid, companyName, provider, addressInput) {
|
|
2492
3349
|
const body = { vat_uid: vatUid };
|
|
2493
3350
|
if (companyName) body["company_name"] = companyName;
|
|
@@ -2495,44 +3352,44 @@ var VatEndpoint = class {
|
|
|
2495
3352
|
if (addressInput) {
|
|
2496
3353
|
body["address_input"] = addressInput instanceof VatValidationAddressInput ? addressInput.toArray() : addressInput;
|
|
2497
3354
|
}
|
|
2498
|
-
return this.
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
if (attemptsLeft <= 1) {
|
|
2504
|
-
const text = await response.text();
|
|
2505
|
-
throw new HttpException("VAT validation timed out after multiple attempts", 504, text);
|
|
2506
|
-
}
|
|
2507
|
-
return this.validateWithGatewayRetry(body, attemptsLeft - 1);
|
|
2508
|
-
}
|
|
2509
|
-
const data = await this.parseJsonResponse(response);
|
|
2510
|
-
return VatResource.fromArray(data);
|
|
3355
|
+
return withRetries(this.retryPolicy, "VAT validation", async () => {
|
|
3356
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate`, body);
|
|
3357
|
+
const data = await this.parseJsonResponse(response);
|
|
3358
|
+
return VatResource.fromArray(data);
|
|
3359
|
+
});
|
|
2511
3360
|
}
|
|
2512
3361
|
async validateMultiple(vatUids, companyNames, provider) {
|
|
2513
3362
|
const body = { vat_uids: vatUids };
|
|
2514
3363
|
if (companyNames) body["company_names"] = companyNames;
|
|
2515
3364
|
if (provider) body["source"] = provider;
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
3365
|
+
return withRetries(this.retryPolicy, "VAT validation", async () => {
|
|
3366
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/multiple`, body);
|
|
3367
|
+
const data = await this.parseJsonResponse(response);
|
|
3368
|
+
return VatCollection.fromResponse(data);
|
|
3369
|
+
});
|
|
2519
3370
|
}
|
|
2520
3371
|
async validateSchema(vatUid) {
|
|
2521
3372
|
const body = { vat_uid: vatUid };
|
|
2522
|
-
|
|
2523
|
-
|
|
3373
|
+
return withRetries(this.retryPolicy, "VAT schema check", async () => {
|
|
3374
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/schema`, body);
|
|
3375
|
+
return this.parseJsonResponse(response);
|
|
3376
|
+
});
|
|
2524
3377
|
}
|
|
2525
3378
|
async state(vatUid) {
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
3379
|
+
return withRetries(this.retryPolicy, "VAT state lookup", async () => {
|
|
3380
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/vat/state/${encodeURIComponent(vatUid)}`);
|
|
3381
|
+
const data = await this.parseJsonResponse(response);
|
|
3382
|
+
return VatResource.fromArray(data);
|
|
3383
|
+
});
|
|
2529
3384
|
}
|
|
2530
3385
|
async history(vatUid) {
|
|
2531
3386
|
let url = `${this.baseUrl}/vat/history`;
|
|
2532
3387
|
if (vatUid) url += `?vat_uid=${encodeURIComponent(vatUid)}`;
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
3388
|
+
return withRetries(this.retryPolicy, "VAT history request", async () => {
|
|
3389
|
+
const response = await this.sendRequest("GET", url);
|
|
3390
|
+
const data = await this.parseJsonResponse(response);
|
|
3391
|
+
return VatCollection.fromResponse(data);
|
|
3392
|
+
});
|
|
2536
3393
|
}
|
|
2537
3394
|
async search(term, perPage) {
|
|
2538
3395
|
const params = new URLSearchParams();
|
|
@@ -2540,16 +3397,20 @@ var VatEndpoint = class {
|
|
|
2540
3397
|
if (perPage !== void 0) params.set("per_page", String(perPage));
|
|
2541
3398
|
const queryString = params.toString();
|
|
2542
3399
|
const url = `${this.baseUrl}/vat/search${queryString ? `?${queryString}` : ""}`;
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
3400
|
+
return withRetries(this.retryPolicy, "VAT search", async () => {
|
|
3401
|
+
const response = await this.sendRequest("GET", url);
|
|
3402
|
+
const data = await this.parseJsonResponse(response);
|
|
3403
|
+
return VatCollection.fromResponse(data);
|
|
3404
|
+
});
|
|
2546
3405
|
}
|
|
2547
3406
|
async certificate(uuid, lang) {
|
|
2548
3407
|
let url = `${this.baseUrl}/vat/certificate/${encodeURIComponent(uuid)}`;
|
|
2549
3408
|
if (lang) url += `?lang=${lang}`;
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
3409
|
+
return withRetries(this.retryPolicy, "VAT certificate download", async () => {
|
|
3410
|
+
const response = await this.sendBinaryRequest("GET", url);
|
|
3411
|
+
const buffer = await response.arrayBuffer();
|
|
3412
|
+
return new Uint8Array(buffer);
|
|
3413
|
+
});
|
|
2553
3414
|
}
|
|
2554
3415
|
async certificatesBulkExport(fromDate, toDate, countries, lang) {
|
|
2555
3416
|
return this.exportRequest(`${this.baseUrl}/vat/certificates/bulk-export`, fromDate, toDate, countries, lang);
|
|
@@ -2573,9 +3434,11 @@ var VatEndpoint = class {
|
|
|
2573
3434
|
}
|
|
2574
3435
|
async downloadBulkExport(exportId) {
|
|
2575
3436
|
const url = `${this.baseUrl}/vat/certificates/bulk-export/${encodeURIComponent(exportId)}/download`;
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
3437
|
+
return withRetries(this.retryPolicy, "VAT export download", async () => {
|
|
3438
|
+
const response = await this.sendBinaryRequest("GET", url);
|
|
3439
|
+
const buffer = await response.arrayBuffer();
|
|
3440
|
+
return new Uint8Array(buffer);
|
|
3441
|
+
});
|
|
2579
3442
|
}
|
|
2580
3443
|
buildHeaders() {
|
|
2581
3444
|
const headers = {
|
|
@@ -2599,7 +3462,13 @@ var VatEndpoint = class {
|
|
|
2599
3462
|
const response = await this.httpClient.request(method, url, { headers });
|
|
2600
3463
|
if (!response.ok) {
|
|
2601
3464
|
const text = await response.text();
|
|
2602
|
-
throw new HttpException(
|
|
3465
|
+
throw new HttpException(
|
|
3466
|
+
describeApiError(text, response.status),
|
|
3467
|
+
response.status,
|
|
3468
|
+
text,
|
|
3469
|
+
{},
|
|
3470
|
+
response.headers.get("retry-after")
|
|
3471
|
+
);
|
|
2603
3472
|
}
|
|
2604
3473
|
return response;
|
|
2605
3474
|
}
|
|
@@ -2614,19 +3483,25 @@ var VatEndpoint = class {
|
|
|
2614
3483
|
}
|
|
2615
3484
|
} catch {
|
|
2616
3485
|
}
|
|
2617
|
-
throw new ValidationException("Validation failed", errors);
|
|
3486
|
+
throw new ValidationException(jsonErrorMessage(responseText) ?? "Validation failed", errors);
|
|
2618
3487
|
}
|
|
2619
3488
|
if (!response.ok && !acceptedStatuses.includes(response.status)) {
|
|
2620
|
-
throw new HttpException(
|
|
3489
|
+
throw new HttpException(
|
|
3490
|
+
describeApiError(responseText, response.status),
|
|
3491
|
+
response.status,
|
|
3492
|
+
responseText,
|
|
3493
|
+
{},
|
|
3494
|
+
response.headers.get("retry-after")
|
|
3495
|
+
);
|
|
2621
3496
|
}
|
|
2622
3497
|
let parsed;
|
|
2623
3498
|
try {
|
|
2624
3499
|
parsed = JSON.parse(responseText);
|
|
2625
3500
|
} catch {
|
|
2626
|
-
throw new HttpException("Invalid JSON response",
|
|
3501
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
2627
3502
|
}
|
|
2628
3503
|
if (typeof parsed !== "object" || parsed === null) {
|
|
2629
|
-
throw new HttpException("Unexpected response format",
|
|
3504
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
2630
3505
|
}
|
|
2631
3506
|
const obj = parsed;
|
|
2632
3507
|
if (obj["data"] !== void 0 && (typeof obj["data"] === "object" || Array.isArray(obj["data"]))) {
|
|
@@ -2725,7 +3600,7 @@ var AuthRetryHttpClient = class {
|
|
|
2725
3600
|
};
|
|
2726
3601
|
|
|
2727
3602
|
// src/version.ts
|
|
2728
|
-
var SDK_VERSION = "1.
|
|
3603
|
+
var SDK_VERSION = "1.3.0";
|
|
2729
3604
|
|
|
2730
3605
|
// src/http/SdkVersionHttpClient.ts
|
|
2731
3606
|
var SDK_VERSION_HEADER = "X-Taxora-SDK-Version";
|
|
@@ -2757,14 +3632,21 @@ var TaxoraClient = class {
|
|
|
2757
3632
|
const environment = options.environment ?? Environment.SANDBOX;
|
|
2758
3633
|
const tokenStorage = options.tokenStorage ?? new InMemoryTokenStorage();
|
|
2759
3634
|
const httpClient = options.httpClient ?? new FetchHttpClient();
|
|
3635
|
+
const retryPolicy = options.retryPolicy ?? new RetryPolicy();
|
|
2760
3636
|
const versionedClient = new SdkVersionHttpClient(httpClient);
|
|
2761
3637
|
this.baseUrl = getBaseUrl(environment);
|
|
2762
|
-
this.auth = new AuthEndpoint(this.baseUrl, options.apiKey, tokenStorage, versionedClient);
|
|
3638
|
+
this.auth = new AuthEndpoint(this.baseUrl, options.apiKey, tokenStorage, versionedClient, retryPolicy);
|
|
2763
3639
|
const retryClient = new AuthRetryHttpClient(versionedClient, tokenStorage, () => this.auth.refresh());
|
|
2764
|
-
this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
|
|
2765
|
-
this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
|
|
2766
|
-
this.smartEnrichment = new SmartEnrichmentEndpoint(
|
|
2767
|
-
|
|
3640
|
+
this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3641
|
+
this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3642
|
+
this.smartEnrichment = new SmartEnrichmentEndpoint(
|
|
3643
|
+
this.baseUrl,
|
|
3644
|
+
options.apiKey,
|
|
3645
|
+
tokenStorage,
|
|
3646
|
+
retryClient,
|
|
3647
|
+
retryPolicy
|
|
3648
|
+
);
|
|
3649
|
+
this.eReporting = new EReportingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
2768
3650
|
}
|
|
2769
3651
|
};
|
|
2770
3652
|
|
|
@@ -2785,6 +3667,125 @@ var Language = {
|
|
|
2785
3667
|
ENGLISH: "en",
|
|
2786
3668
|
GERMAN: "de"
|
|
2787
3669
|
};
|
|
3670
|
+
|
|
3671
|
+
// src/webhooks/WebhookSignature.ts
|
|
3672
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3673
|
+
var WEBHOOK_SIGNATURE_HEADER = "Taxora-Signature";
|
|
3674
|
+
var WEBHOOK_ID_HEADER = "Taxora-Webhook-Id";
|
|
3675
|
+
var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
3676
|
+
var SCHEME = "v1";
|
|
3677
|
+
var WebhookSignatureVerificationError = class extends TaxoraException {
|
|
3678
|
+
reason;
|
|
3679
|
+
constructor(message, reason, context = {}) {
|
|
3680
|
+
super(message, { ...context, reason });
|
|
3681
|
+
this.name = "WebhookSignatureVerificationError";
|
|
3682
|
+
this.reason = reason;
|
|
3683
|
+
}
|
|
3684
|
+
};
|
|
3685
|
+
function parseWebhookSignatureHeader(header) {
|
|
3686
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
3687
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
3688
|
+
throw new WebhookSignatureVerificationError(
|
|
3689
|
+
`Missing ${WEBHOOK_SIGNATURE_HEADER} header. Is signing enabled for this webhook endpoint?`,
|
|
3690
|
+
"missing_header"
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3693
|
+
let timestamp = null;
|
|
3694
|
+
const signatures = [];
|
|
3695
|
+
for (const part of value.split(",")) {
|
|
3696
|
+
const separator = part.indexOf("=");
|
|
3697
|
+
if (separator === -1) continue;
|
|
3698
|
+
const key = part.slice(0, separator).trim();
|
|
3699
|
+
const val = part.slice(separator + 1).trim();
|
|
3700
|
+
if (key === "t" && /^\d+$/.test(val)) {
|
|
3701
|
+
timestamp = Number(val);
|
|
3702
|
+
} else if (key === SCHEME && /^[0-9a-f]{64}$/i.test(val)) {
|
|
3703
|
+
signatures.push(val.toLowerCase());
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
if (timestamp === null || signatures.length === 0) {
|
|
3707
|
+
throw new WebhookSignatureVerificationError(
|
|
3708
|
+
`Malformed ${WEBHOOK_SIGNATURE_HEADER} header \u2014 expected "t=<unix>,v1=<hex>".`,
|
|
3709
|
+
"malformed_header"
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
return { timestamp, signatures };
|
|
3713
|
+
}
|
|
3714
|
+
function computeWebhookSignature(rawBody, timestamp, secret) {
|
|
3715
|
+
return createHmac("sha256", secret).update(`${timestamp}.`).update(toBytes(rawBody)).digest("hex");
|
|
3716
|
+
}
|
|
3717
|
+
function generateWebhookSignatureHeader(rawBody, secret, timestamp = Math.floor(Date.now() / 1e3)) {
|
|
3718
|
+
const secrets = normalizeSecrets(secret);
|
|
3719
|
+
return [`t=${timestamp}`, ...secrets.map((s) => `${SCHEME}=${computeWebhookSignature(rawBody, timestamp, s)}`)].join(
|
|
3720
|
+
","
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
function verifyWebhookSignature(rawBody, header, secret, options = {}) {
|
|
3724
|
+
const secrets = normalizeSecrets(secret);
|
|
3725
|
+
const bytes = toBytes(rawBody);
|
|
3726
|
+
const { timestamp, signatures } = parseWebhookSignatureHeader(header);
|
|
3727
|
+
const tolerance = options.tolerance ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
3728
|
+
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
3729
|
+
if (tolerance > 0 && Math.abs(now - timestamp) > tolerance) {
|
|
3730
|
+
throw new WebhookSignatureVerificationError(
|
|
3731
|
+
`Timestamp outside the tolerance window (${tolerance}s). Possible replay \u2014 or check the server clock.`,
|
|
3732
|
+
"timestamp_outside_tolerance",
|
|
3733
|
+
{ timestamp, now, tolerance }
|
|
3734
|
+
);
|
|
3735
|
+
}
|
|
3736
|
+
const received = signatures.map((s) => Buffer.from(s, "hex"));
|
|
3737
|
+
for (const s of secrets) {
|
|
3738
|
+
const expected = Buffer.from(computeWebhookSignature(bytes, timestamp, s), "hex");
|
|
3739
|
+
let matched = false;
|
|
3740
|
+
for (const candidate of received) {
|
|
3741
|
+
if (candidate.length === expected.length && timingSafeEqual(candidate, expected)) {
|
|
3742
|
+
matched = true;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
if (matched) return true;
|
|
3746
|
+
}
|
|
3747
|
+
throw new WebhookSignatureVerificationError(
|
|
3748
|
+
'No signature matches the expected signature for this payload. Make sure you pass the raw request body (not re-serialised JSON) and the full secret including the "whsec_" prefix.',
|
|
3749
|
+
"no_matching_signature"
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
function constructWebhookEvent(rawBody, header, secret, options = {}) {
|
|
3753
|
+
verifyWebhookSignature(rawBody, header, secret, options);
|
|
3754
|
+
let parsed;
|
|
3755
|
+
try {
|
|
3756
|
+
parsed = JSON.parse(Buffer.from(toBytes(rawBody)).toString("utf8"));
|
|
3757
|
+
} catch {
|
|
3758
|
+
throw new WebhookSignatureVerificationError("Webhook payload is not valid JSON.", "invalid_payload");
|
|
3759
|
+
}
|
|
3760
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.id !== "string" || typeof parsed.event !== "string" || typeof parsed.data !== "object") {
|
|
3761
|
+
throw new WebhookSignatureVerificationError(
|
|
3762
|
+
"Webhook payload does not have the expected { id, event, timestamp, data } shape.",
|
|
3763
|
+
"invalid_payload"
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3766
|
+
return parsed;
|
|
3767
|
+
}
|
|
3768
|
+
function normalizeSecrets(secret) {
|
|
3769
|
+
const list = (typeof secret === "string" ? [secret] : [...secret]).filter(
|
|
3770
|
+
(s) => typeof s === "string" && s !== ""
|
|
3771
|
+
);
|
|
3772
|
+
if (list.length === 0) {
|
|
3773
|
+
throw new WebhookSignatureVerificationError(
|
|
3774
|
+
"No webhook signing secret given. Copy it from the Taxora dashboard (Developer \u2192 Webhooks).",
|
|
3775
|
+
"missing_secret"
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
return list;
|
|
3779
|
+
}
|
|
3780
|
+
function toBytes(rawBody) {
|
|
3781
|
+
if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
|
|
3782
|
+
if (rawBody instanceof Uint8Array) return rawBody;
|
|
3783
|
+
if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
|
|
3784
|
+
throw new WebhookSignatureVerificationError(
|
|
3785
|
+
"Webhook body must be the raw request body (string, Buffer or Uint8Array). It looks like it was already parsed as JSON \u2014 use express.raw() / request.text() instead of a JSON body parser.",
|
|
3786
|
+
"invalid_body"
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
2788
3789
|
export {
|
|
2789
3790
|
ApiVersion,
|
|
2790
3791
|
AuthEndpoint,
|
|
@@ -2792,9 +3793,11 @@ export {
|
|
|
2792
3793
|
AuthenticationException,
|
|
2793
3794
|
CompanyAddress,
|
|
2794
3795
|
CompanyEndpoint,
|
|
3796
|
+
ComplianceDocumentType,
|
|
2795
3797
|
ComplianceEnrollment,
|
|
2796
3798
|
ComplianceEnrollmentPage,
|
|
2797
3799
|
ComplianceEnrollmentStatus,
|
|
3800
|
+
ComplianceService,
|
|
2798
3801
|
ComplianceTaxReport,
|
|
2799
3802
|
ComplianceTaxReportPage,
|
|
2800
3803
|
ComplianceTaxReportState,
|
|
@@ -2802,6 +3805,7 @@ export {
|
|
|
2802
3805
|
ComplianceTransactionPage,
|
|
2803
3806
|
ComplianceTransactionState,
|
|
2804
3807
|
ComplianceTransactionType,
|
|
3808
|
+
DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
|
|
2805
3809
|
EReportingEndpoint,
|
|
2806
3810
|
Environment,
|
|
2807
3811
|
FetchHttpClient,
|
|
@@ -2810,8 +3814,13 @@ export {
|
|
|
2810
3814
|
InMemoryTokenStorage,
|
|
2811
3815
|
Language,
|
|
2812
3816
|
LoginIdentifier,
|
|
3817
|
+
NorwegianOrgNumber,
|
|
3818
|
+
PeppolLookupResult,
|
|
3819
|
+
PeppolLookupStatus,
|
|
2813
3820
|
ProviderDocument,
|
|
2814
3821
|
ProviderDocumentLine,
|
|
3822
|
+
RegistryCompany,
|
|
3823
|
+
RetryPolicy,
|
|
2815
3824
|
RevenueStatistics,
|
|
2816
3825
|
ScoreBreakdown,
|
|
2817
3826
|
SireneLookupResult,
|
|
@@ -2819,6 +3828,7 @@ export {
|
|
|
2819
3828
|
SmartEnrichmentHistoryPage,
|
|
2820
3829
|
SmartEnrichmentHistoryRow,
|
|
2821
3830
|
SmartEnrichmentJob,
|
|
3831
|
+
SmartEnrichmentMode,
|
|
2822
3832
|
SmartEnrichmentResource,
|
|
2823
3833
|
SmartEnrichmentStatistics,
|
|
2824
3834
|
SmartEnrichmentStatus,
|
|
@@ -2834,18 +3844,35 @@ export {
|
|
|
2834
3844
|
VatResource,
|
|
2835
3845
|
VatState,
|
|
2836
3846
|
VatValidationAddressInput,
|
|
3847
|
+
WEBHOOK_ID_HEADER,
|
|
3848
|
+
WEBHOOK_SIGNATURE_HEADER,
|
|
3849
|
+
WebhookSignatureVerificationError,
|
|
3850
|
+
computeWebhookSignature,
|
|
3851
|
+
constructWebhookEvent,
|
|
3852
|
+
describeApiError,
|
|
3853
|
+
describeComplianceDocumentType,
|
|
2837
3854
|
describeComplianceEnrollmentStatus,
|
|
3855
|
+
describeComplianceService,
|
|
2838
3856
|
describeComplianceTaxReportState,
|
|
2839
3857
|
describeComplianceTransactionState,
|
|
2840
3858
|
describeComplianceTransactionType,
|
|
3859
|
+
describePeppolLookupStatus,
|
|
3860
|
+
describeSmartEnrichmentMode,
|
|
2841
3861
|
describeSmartEnrichmentStatus,
|
|
2842
3862
|
describeVatState,
|
|
3863
|
+
generateWebhookSignatureHeader,
|
|
2843
3864
|
getBaseUrl,
|
|
2844
3865
|
getFailedVatStates,
|
|
3866
|
+
parseWebhookSignatureHeader,
|
|
3867
|
+
toComplianceDocumentType,
|
|
2845
3868
|
toComplianceEnrollmentStatus,
|
|
3869
|
+
toComplianceService,
|
|
2846
3870
|
toComplianceTaxReportState,
|
|
2847
3871
|
toComplianceTransactionState,
|
|
2848
3872
|
toComplianceTransactionType,
|
|
2849
|
-
|
|
3873
|
+
toPeppolLookupStatus,
|
|
3874
|
+
toSmartEnrichmentMode,
|
|
3875
|
+
toSmartEnrichmentStatus,
|
|
3876
|
+
verifyWebhookSignature
|
|
2850
3877
|
};
|
|
2851
3878
|
//# sourceMappingURL=index.js.map
|