@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.cjs
CHANGED
|
@@ -26,9 +26,11 @@ __export(index_exports, {
|
|
|
26
26
|
AuthenticationException: () => AuthenticationException,
|
|
27
27
|
CompanyAddress: () => CompanyAddress,
|
|
28
28
|
CompanyEndpoint: () => CompanyEndpoint,
|
|
29
|
+
ComplianceDocumentType: () => ComplianceDocumentType,
|
|
29
30
|
ComplianceEnrollment: () => ComplianceEnrollment,
|
|
30
31
|
ComplianceEnrollmentPage: () => ComplianceEnrollmentPage,
|
|
31
32
|
ComplianceEnrollmentStatus: () => ComplianceEnrollmentStatus,
|
|
33
|
+
ComplianceService: () => ComplianceService,
|
|
32
34
|
ComplianceTaxReport: () => ComplianceTaxReport,
|
|
33
35
|
ComplianceTaxReportPage: () => ComplianceTaxReportPage,
|
|
34
36
|
ComplianceTaxReportState: () => ComplianceTaxReportState,
|
|
@@ -36,6 +38,7 @@ __export(index_exports, {
|
|
|
36
38
|
ComplianceTransactionPage: () => ComplianceTransactionPage,
|
|
37
39
|
ComplianceTransactionState: () => ComplianceTransactionState,
|
|
38
40
|
ComplianceTransactionType: () => ComplianceTransactionType,
|
|
41
|
+
DEFAULT_WEBHOOK_TOLERANCE_SECONDS: () => DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
|
|
39
42
|
EReportingEndpoint: () => EReportingEndpoint,
|
|
40
43
|
Environment: () => Environment,
|
|
41
44
|
FetchHttpClient: () => FetchHttpClient,
|
|
@@ -44,8 +47,13 @@ __export(index_exports, {
|
|
|
44
47
|
InMemoryTokenStorage: () => InMemoryTokenStorage,
|
|
45
48
|
Language: () => Language,
|
|
46
49
|
LoginIdentifier: () => LoginIdentifier,
|
|
50
|
+
NorwegianOrgNumber: () => NorwegianOrgNumber,
|
|
51
|
+
PeppolLookupResult: () => PeppolLookupResult,
|
|
52
|
+
PeppolLookupStatus: () => PeppolLookupStatus,
|
|
47
53
|
ProviderDocument: () => ProviderDocument,
|
|
48
54
|
ProviderDocumentLine: () => ProviderDocumentLine,
|
|
55
|
+
RegistryCompany: () => RegistryCompany,
|
|
56
|
+
RetryPolicy: () => RetryPolicy,
|
|
49
57
|
RevenueStatistics: () => RevenueStatistics,
|
|
50
58
|
ScoreBreakdown: () => ScoreBreakdown,
|
|
51
59
|
SireneLookupResult: () => SireneLookupResult,
|
|
@@ -53,6 +61,7 @@ __export(index_exports, {
|
|
|
53
61
|
SmartEnrichmentHistoryPage: () => SmartEnrichmentHistoryPage,
|
|
54
62
|
SmartEnrichmentHistoryRow: () => SmartEnrichmentHistoryRow,
|
|
55
63
|
SmartEnrichmentJob: () => SmartEnrichmentJob,
|
|
64
|
+
SmartEnrichmentMode: () => SmartEnrichmentMode,
|
|
56
65
|
SmartEnrichmentResource: () => SmartEnrichmentResource,
|
|
57
66
|
SmartEnrichmentStatistics: () => SmartEnrichmentStatistics,
|
|
58
67
|
SmartEnrichmentStatus: () => SmartEnrichmentStatus,
|
|
@@ -68,19 +77,36 @@ __export(index_exports, {
|
|
|
68
77
|
VatResource: () => VatResource,
|
|
69
78
|
VatState: () => VatState,
|
|
70
79
|
VatValidationAddressInput: () => VatValidationAddressInput,
|
|
80
|
+
WEBHOOK_ID_HEADER: () => WEBHOOK_ID_HEADER,
|
|
81
|
+
WEBHOOK_SIGNATURE_HEADER: () => WEBHOOK_SIGNATURE_HEADER,
|
|
82
|
+
WebhookSignatureVerificationError: () => WebhookSignatureVerificationError,
|
|
83
|
+
computeWebhookSignature: () => computeWebhookSignature,
|
|
84
|
+
constructWebhookEvent: () => constructWebhookEvent,
|
|
85
|
+
describeApiError: () => describeApiError,
|
|
86
|
+
describeComplianceDocumentType: () => describeComplianceDocumentType,
|
|
71
87
|
describeComplianceEnrollmentStatus: () => describeComplianceEnrollmentStatus,
|
|
88
|
+
describeComplianceService: () => describeComplianceService,
|
|
72
89
|
describeComplianceTaxReportState: () => describeComplianceTaxReportState,
|
|
73
90
|
describeComplianceTransactionState: () => describeComplianceTransactionState,
|
|
74
91
|
describeComplianceTransactionType: () => describeComplianceTransactionType,
|
|
92
|
+
describePeppolLookupStatus: () => describePeppolLookupStatus,
|
|
93
|
+
describeSmartEnrichmentMode: () => describeSmartEnrichmentMode,
|
|
75
94
|
describeSmartEnrichmentStatus: () => describeSmartEnrichmentStatus,
|
|
76
95
|
describeVatState: () => describeVatState,
|
|
96
|
+
generateWebhookSignatureHeader: () => generateWebhookSignatureHeader,
|
|
77
97
|
getBaseUrl: () => getBaseUrl,
|
|
78
98
|
getFailedVatStates: () => getFailedVatStates,
|
|
99
|
+
parseWebhookSignatureHeader: () => parseWebhookSignatureHeader,
|
|
100
|
+
toComplianceDocumentType: () => toComplianceDocumentType,
|
|
79
101
|
toComplianceEnrollmentStatus: () => toComplianceEnrollmentStatus,
|
|
102
|
+
toComplianceService: () => toComplianceService,
|
|
80
103
|
toComplianceTaxReportState: () => toComplianceTaxReportState,
|
|
81
104
|
toComplianceTransactionState: () => toComplianceTransactionState,
|
|
82
105
|
toComplianceTransactionType: () => toComplianceTransactionType,
|
|
83
|
-
|
|
106
|
+
toPeppolLookupStatus: () => toPeppolLookupStatus,
|
|
107
|
+
toSmartEnrichmentMode: () => toSmartEnrichmentMode,
|
|
108
|
+
toSmartEnrichmentStatus: () => toSmartEnrichmentStatus,
|
|
109
|
+
verifyWebhookSignature: () => verifyWebhookSignature
|
|
84
110
|
});
|
|
85
111
|
module.exports = __toCommonJS(index_exports);
|
|
86
112
|
|
|
@@ -146,37 +172,293 @@ var AuthenticationException = class extends TaxoraException {
|
|
|
146
172
|
}
|
|
147
173
|
};
|
|
148
174
|
|
|
175
|
+
// src/exceptions/apiErrorMessage.ts
|
|
176
|
+
var MAX_LENGTH = 500;
|
|
177
|
+
var MAX_PLAIN_LENGTH = 200;
|
|
178
|
+
var REASON_PHRASES = {
|
|
179
|
+
400: "Bad Request",
|
|
180
|
+
401: "Unauthorized",
|
|
181
|
+
403: "Forbidden",
|
|
182
|
+
404: "Not Found",
|
|
183
|
+
405: "Method Not Allowed",
|
|
184
|
+
408: "Request Timeout",
|
|
185
|
+
409: "Conflict",
|
|
186
|
+
413: "Payload Too Large",
|
|
187
|
+
422: "Unprocessable Entity",
|
|
188
|
+
429: "Too Many Requests",
|
|
189
|
+
500: "Internal Server Error",
|
|
190
|
+
502: "Bad Gateway",
|
|
191
|
+
503: "Service Unavailable",
|
|
192
|
+
504: "Gateway Timeout"
|
|
193
|
+
};
|
|
194
|
+
function describeApiError(body, statusCode) {
|
|
195
|
+
return jsonErrorMessage(body) ?? plainErrorMessage(body) ?? statusErrorMessage(statusCode);
|
|
196
|
+
}
|
|
197
|
+
function detailApiError(body, statusCode) {
|
|
198
|
+
return jsonErrorMessage(body) ?? plainErrorMessage(body) ?? statusPhrase(statusCode);
|
|
199
|
+
}
|
|
200
|
+
function jsonErrorMessage(body) {
|
|
201
|
+
if (body === null || body === void 0 || body.trim() === "") return null;
|
|
202
|
+
let parsed;
|
|
203
|
+
try {
|
|
204
|
+
parsed = JSON.parse(body);
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
209
|
+
const payload = parsed;
|
|
210
|
+
for (const key of ["message", "error"]) {
|
|
211
|
+
const value = payload[key];
|
|
212
|
+
if (typeof value === "string") {
|
|
213
|
+
const message = sanitize(value);
|
|
214
|
+
if (message !== null) return message;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const errors = payload["errors"];
|
|
218
|
+
if (typeof errors === "object" && errors !== null) {
|
|
219
|
+
return fromErrorBag(errors);
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function statusErrorMessage(statusCode) {
|
|
224
|
+
return `Taxora API request failed (${statusPhrase(statusCode)}).`;
|
|
225
|
+
}
|
|
226
|
+
function statusPhrase(statusCode) {
|
|
227
|
+
const phrase = REASON_PHRASES[statusCode];
|
|
228
|
+
return `HTTP ${statusCode}${phrase ? ` ${phrase}` : ""}`;
|
|
229
|
+
}
|
|
230
|
+
function plainErrorMessage(body) {
|
|
231
|
+
if (body === null || body === void 0) return null;
|
|
232
|
+
const value = body.replace(/\s+/g, " ").trim();
|
|
233
|
+
if (value === "" || value.length > MAX_PLAIN_LENGTH) return null;
|
|
234
|
+
if (value.includes("<") || value.includes("{")) return null;
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
function fromErrorBag(errors) {
|
|
238
|
+
for (const [field, messages] of Object.entries(errors)) {
|
|
239
|
+
for (const message of Array.isArray(messages) ? messages : [messages]) {
|
|
240
|
+
if (typeof message !== "string") continue;
|
|
241
|
+
const sanitized = sanitize(message);
|
|
242
|
+
if (sanitized === null) continue;
|
|
243
|
+
return field !== "" ? `${field}: ${sanitized}` : sanitized;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
function sanitize(value) {
|
|
249
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
250
|
+
if (collapsed === "") return null;
|
|
251
|
+
if (/<!doctype\s+html|<html[\s>]/i.test(collapsed)) return null;
|
|
252
|
+
return collapsed.length > MAX_LENGTH ? `${collapsed.slice(0, MAX_LENGTH - 1)}\u2026` : collapsed;
|
|
253
|
+
}
|
|
254
|
+
|
|
149
255
|
// src/exceptions/HttpException.ts
|
|
150
|
-
var HttpException = class extends TaxoraException {
|
|
256
|
+
var HttpException = class _HttpException extends TaxoraException {
|
|
151
257
|
statusCode;
|
|
152
258
|
responseBody;
|
|
153
|
-
|
|
259
|
+
/** Raw `Retry-After` header of the failed response, when it carried one. */
|
|
260
|
+
retryAfter;
|
|
261
|
+
constructor(message, statusCode, responseBody, context = {}, retryAfter = null) {
|
|
154
262
|
super(message, context);
|
|
155
263
|
this.name = "HttpException";
|
|
156
264
|
this.statusCode = statusCode;
|
|
157
265
|
this.responseBody = responseBody;
|
|
266
|
+
this.retryAfter = retryAfter;
|
|
158
267
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
159
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* The final failure after the SDK retried a transient gateway error, e.g.
|
|
271
|
+
* "Taxora VAT validation failed after 3 attempts (HTTP 504 Gateway Timeout)."
|
|
272
|
+
*/
|
|
273
|
+
static afterAttempts(operation, attempts, last) {
|
|
274
|
+
const detail = detailApiError(last.getResponseBody(), last.getStatusCode());
|
|
275
|
+
return new _HttpException(
|
|
276
|
+
`Taxora ${operation} failed after ${attempts} attempts (${detail}).`,
|
|
277
|
+
last.getStatusCode(),
|
|
278
|
+
last.getResponseBody(),
|
|
279
|
+
{ ...last.context, attempts, cause: last },
|
|
280
|
+
last.getRetryAfter()
|
|
281
|
+
);
|
|
282
|
+
}
|
|
160
283
|
getStatusCode() {
|
|
161
284
|
return this.statusCode;
|
|
162
285
|
}
|
|
163
286
|
getResponseBody() {
|
|
164
287
|
return this.responseBody;
|
|
165
288
|
}
|
|
289
|
+
getRetryAfter() {
|
|
290
|
+
return this.retryAfter;
|
|
291
|
+
}
|
|
166
292
|
};
|
|
167
293
|
|
|
294
|
+
// src/http/RetryPolicy.ts
|
|
295
|
+
var DEFAULT_RETRYABLE_STATUS_CODES = [502, 503, 504];
|
|
296
|
+
var RETRY_AFTER_ONLY_STATUS_CODES = [429];
|
|
297
|
+
var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
298
|
+
"ECONNRESET",
|
|
299
|
+
"ECONNREFUSED",
|
|
300
|
+
"ECONNABORTED",
|
|
301
|
+
"EPIPE",
|
|
302
|
+
"ETIMEDOUT",
|
|
303
|
+
"EAI_AGAIN",
|
|
304
|
+
"EHOSTUNREACH",
|
|
305
|
+
"ENETUNREACH",
|
|
306
|
+
"ENETDOWN",
|
|
307
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
308
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
309
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
310
|
+
"UND_ERR_SOCKET"
|
|
311
|
+
]);
|
|
312
|
+
var RetryPolicy = class _RetryPolicy {
|
|
313
|
+
maxAttempts;
|
|
314
|
+
initialDelayMs;
|
|
315
|
+
backoffMultiplier;
|
|
316
|
+
maxDelayMs;
|
|
317
|
+
retryableStatusCodes;
|
|
318
|
+
respectRetryAfter;
|
|
319
|
+
maxRetryAfterMs;
|
|
320
|
+
retryOnNetworkErrors;
|
|
321
|
+
sleeper;
|
|
322
|
+
constructor(options = {}) {
|
|
323
|
+
this.maxAttempts = options.maxAttempts ?? 3;
|
|
324
|
+
this.initialDelayMs = options.initialDelayMs ?? 500;
|
|
325
|
+
this.backoffMultiplier = options.backoffMultiplier ?? 2;
|
|
326
|
+
this.maxDelayMs = options.maxDelayMs ?? 5e3;
|
|
327
|
+
this.retryableStatusCodes = options.retryableStatusCodes ?? DEFAULT_RETRYABLE_STATUS_CODES;
|
|
328
|
+
this.respectRetryAfter = options.respectRetryAfter ?? true;
|
|
329
|
+
this.maxRetryAfterMs = options.maxRetryAfterMs ?? 1e4;
|
|
330
|
+
this.retryOnNetworkErrors = options.retryOnNetworkErrors ?? true;
|
|
331
|
+
this.sleeper = options.sleeper;
|
|
332
|
+
if (this.maxAttempts < 1) throw new Error("maxAttempts must be at least 1.");
|
|
333
|
+
if (this.initialDelayMs < 0 || this.maxDelayMs < 0) throw new Error("Delays must not be negative.");
|
|
334
|
+
if (this.backoffMultiplier < 1) throw new Error("backoffMultiplier must be at least 1.");
|
|
335
|
+
}
|
|
336
|
+
/** No retrying at all — every failure is surfaced on the first attempt. */
|
|
337
|
+
static disabled() {
|
|
338
|
+
return new _RetryPolicy({ maxAttempts: 1 });
|
|
339
|
+
}
|
|
340
|
+
/** Retry immediately, without waiting (mainly useful in tests). */
|
|
341
|
+
static withoutDelay(maxAttempts = 3) {
|
|
342
|
+
return new _RetryPolicy({ maxAttempts, initialDelayMs: 0, maxDelayMs: 0 });
|
|
343
|
+
}
|
|
344
|
+
isRetryable(statusCode, retryAfterHeader) {
|
|
345
|
+
const retryAfterMs = this.retryAfterMs(retryAfterHeader);
|
|
346
|
+
if (retryAfterMs !== null && retryAfterMs > this.maxRetryAfterMs) return false;
|
|
347
|
+
if (this.retryableStatusCodes.includes(statusCode)) return true;
|
|
348
|
+
return retryAfterMs !== null && RETRY_AFTER_ONLY_STATUS_CODES.includes(statusCode);
|
|
349
|
+
}
|
|
350
|
+
/** @param attempt Number of attempts made so far (1 = the first one just failed). */
|
|
351
|
+
shouldRetry(statusCode, attempt, retryAfterHeader) {
|
|
352
|
+
return attempt < this.maxAttempts && this.isRetryable(statusCode, retryAfterHeader);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* A connection-level failure: the request never got an answer, so repeating it
|
|
356
|
+
* is as safe as repeating a gateway timeout.
|
|
357
|
+
*/
|
|
358
|
+
isRetryableError(error) {
|
|
359
|
+
if (!this.retryOnNetworkErrors || !(error instanceof Error)) return false;
|
|
360
|
+
if (error.name === "AbortError" || error.name === "TimeoutError") return true;
|
|
361
|
+
const code = error.code ?? error.cause?.code;
|
|
362
|
+
if (typeof code === "string" && NETWORK_ERROR_CODES.has(code)) return true;
|
|
363
|
+
return error instanceof TypeError && /fetch failed|network|failed to fetch/i.test(error.message);
|
|
364
|
+
}
|
|
365
|
+
shouldRetryError(error, attempt) {
|
|
366
|
+
return attempt < this.maxAttempts && this.isRetryableError(error);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The wait the API asked for, in milliseconds — seconds or HTTP-date form — or
|
|
370
|
+
* null when there is none, it cannot be parsed, or the header is ignored.
|
|
371
|
+
*/
|
|
372
|
+
retryAfterMs(header, now = /* @__PURE__ */ new Date()) {
|
|
373
|
+
if (!this.respectRetryAfter || header === null || header === void 0) return null;
|
|
374
|
+
const value = header.trim();
|
|
375
|
+
if (value === "") return null;
|
|
376
|
+
if (/^\d+$/.test(value)) return Number(value) * 1e3;
|
|
377
|
+
const date = Date.parse(value);
|
|
378
|
+
if (Number.isNaN(date)) return null;
|
|
379
|
+
return Math.max(0, date - now.getTime());
|
|
380
|
+
}
|
|
381
|
+
/** The wait before the next attempt: what the API asked for, else exponential backoff. */
|
|
382
|
+
delayMsForAttempt(attempt, retryAfterHeader) {
|
|
383
|
+
const retryAfterMs = this.retryAfterMs(retryAfterHeader);
|
|
384
|
+
if (retryAfterMs !== null) return Math.min(retryAfterMs, this.maxRetryAfterMs);
|
|
385
|
+
if (this.initialDelayMs === 0) return 0;
|
|
386
|
+
const delay = Math.round(this.initialDelayMs * this.backoffMultiplier ** Math.max(0, attempt - 1));
|
|
387
|
+
return Math.max(0, Math.min(delay, this.maxDelayMs));
|
|
388
|
+
}
|
|
389
|
+
async sleepBeforeRetry(attempt, retryAfterHeader) {
|
|
390
|
+
const delayMs = this.delayMsForAttempt(attempt, retryAfterHeader);
|
|
391
|
+
if (delayMs <= 0) return;
|
|
392
|
+
if (this.sleeper) {
|
|
393
|
+
await this.sleeper(delayMs);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/http/withRetries.ts
|
|
401
|
+
async function withRetries(policy, operation, request) {
|
|
402
|
+
let attempt = 1;
|
|
403
|
+
for (; ; ) {
|
|
404
|
+
try {
|
|
405
|
+
return await request();
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (!(error instanceof HttpException)) {
|
|
408
|
+
if (!policy.shouldRetryError(error, attempt)) throw error;
|
|
409
|
+
await policy.sleepBeforeRetry(attempt);
|
|
410
|
+
attempt++;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const retryAfter = error.getRetryAfter();
|
|
414
|
+
if (!policy.shouldRetry(error.getStatusCode(), attempt, retryAfter)) {
|
|
415
|
+
throw attempt > 1 && policy.isRetryable(error.getStatusCode(), retryAfter) ? HttpException.afterAttempts(operation, attempt, error) : error;
|
|
416
|
+
}
|
|
417
|
+
await policy.sleepBeforeRetry(attempt, retryAfter);
|
|
418
|
+
attempt++;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async function withResponseRetries(policy, operation, send) {
|
|
423
|
+
let attempt = 1;
|
|
424
|
+
for (; ; ) {
|
|
425
|
+
let response;
|
|
426
|
+
try {
|
|
427
|
+
response = await send();
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (!policy.shouldRetryError(error, attempt)) throw error;
|
|
430
|
+
await policy.sleepBeforeRetry(attempt);
|
|
431
|
+
attempt++;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
const retryAfter = response.headers.get("retry-after");
|
|
435
|
+
if (!policy.shouldRetry(response.status, attempt, retryAfter)) {
|
|
436
|
+
if (attempt > 1 && policy.isRetryable(response.status, retryAfter)) {
|
|
437
|
+
const body = await response.text();
|
|
438
|
+
const last = new HttpException(describeApiError(body, response.status), response.status, body, {}, retryAfter);
|
|
439
|
+
throw HttpException.afterAttempts(operation, attempt, last);
|
|
440
|
+
}
|
|
441
|
+
return response;
|
|
442
|
+
}
|
|
443
|
+
await policy.sleepBeforeRetry(attempt, retryAfter);
|
|
444
|
+
attempt++;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
168
448
|
// src/endpoints/AuthEndpoint.ts
|
|
169
449
|
var AuthEndpoint = class {
|
|
170
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
450
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
171
451
|
this.baseUrl = baseUrl;
|
|
172
452
|
this.apiKey = apiKey;
|
|
173
453
|
this.tokenStorage = tokenStorage;
|
|
174
454
|
this.httpClient = httpClient;
|
|
455
|
+
this.retryPolicy = retryPolicy;
|
|
175
456
|
}
|
|
176
457
|
baseUrl;
|
|
177
458
|
apiKey;
|
|
178
459
|
tokenStorage;
|
|
179
460
|
httpClient;
|
|
461
|
+
retryPolicy;
|
|
180
462
|
async login(identifier, password, device, loginIdentifier = LoginIdentifier.EMAIL) {
|
|
181
463
|
const deviceName = device ?? (0, import_os.hostname)();
|
|
182
464
|
const body = {
|
|
@@ -189,24 +471,32 @@ var AuthEndpoint = class {
|
|
|
189
471
|
} else {
|
|
190
472
|
body["email"] = identifier;
|
|
191
473
|
}
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const responseText = await response.text();
|
|
200
|
-
if (response.status === 401) {
|
|
201
|
-
throw new AuthenticationException("Authentication failed: invalid credentials", {
|
|
202
|
-
status: 401,
|
|
203
|
-
body: responseText
|
|
474
|
+
const data = await withRetries(this.retryPolicy, "login", async () => {
|
|
475
|
+
const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/login`, {
|
|
476
|
+
headers: {
|
|
477
|
+
"Content-Type": "application/json",
|
|
478
|
+
"x-api-key": this.apiKey
|
|
479
|
+
},
|
|
480
|
+
body: JSON.stringify(body)
|
|
204
481
|
});
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
482
|
+
const responseText = await response.text();
|
|
483
|
+
if (response.status === 401) {
|
|
484
|
+
throw new AuthenticationException("Authentication failed: invalid credentials", {
|
|
485
|
+
status: 401,
|
|
486
|
+
body: responseText
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (!response.ok) {
|
|
490
|
+
throw new HttpException(
|
|
491
|
+
describeApiError(responseText, response.status),
|
|
492
|
+
response.status,
|
|
493
|
+
responseText,
|
|
494
|
+
{},
|
|
495
|
+
response.headers.get("retry-after")
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
return this.parseResponse(responseText, response.status);
|
|
499
|
+
});
|
|
210
500
|
const token = Token.fromArray(data);
|
|
211
501
|
this.tokenStorage.set(token);
|
|
212
502
|
return token;
|
|
@@ -223,28 +513,36 @@ var AuthEndpoint = class {
|
|
|
223
513
|
if (storedToken) {
|
|
224
514
|
headers["Authorization"] = `Bearer ${storedToken.accessToken}`;
|
|
225
515
|
}
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
516
|
+
const data = await withRetries(this.retryPolicy, "token refresh", async () => {
|
|
517
|
+
const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/refresh`, { headers });
|
|
518
|
+
const responseText = await response.text();
|
|
519
|
+
if (response.status === 401) {
|
|
520
|
+
throw new AuthenticationException("Token refresh failed: unauthorized", { status: 401, body: responseText });
|
|
521
|
+
}
|
|
522
|
+
if (!response.ok) {
|
|
523
|
+
throw new HttpException(
|
|
524
|
+
describeApiError(responseText, response.status),
|
|
525
|
+
response.status,
|
|
526
|
+
responseText,
|
|
527
|
+
{},
|
|
528
|
+
response.headers.get("retry-after")
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return this.parseResponse(responseText, response.status);
|
|
532
|
+
});
|
|
235
533
|
const token = Token.fromArray(data);
|
|
236
534
|
this.tokenStorage.set(token);
|
|
237
535
|
return token;
|
|
238
536
|
}
|
|
239
|
-
parseResponse(responseText) {
|
|
537
|
+
parseResponse(responseText, statusCode) {
|
|
240
538
|
let parsed;
|
|
241
539
|
try {
|
|
242
540
|
parsed = JSON.parse(responseText);
|
|
243
541
|
} catch {
|
|
244
|
-
throw new HttpException("Invalid JSON response",
|
|
542
|
+
throw new HttpException("Invalid JSON response", statusCode, responseText);
|
|
245
543
|
}
|
|
246
544
|
if (typeof parsed !== "object" || parsed === null) {
|
|
247
|
-
throw new HttpException("Unexpected response format",
|
|
545
|
+
throw new HttpException("Unexpected response format", statusCode, responseText);
|
|
248
546
|
}
|
|
249
547
|
const obj = parsed;
|
|
250
548
|
if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
|
|
@@ -268,16 +566,18 @@ function normalizeCompanyResource(data) {
|
|
|
268
566
|
|
|
269
567
|
// src/endpoints/CompanyEndpoint.ts
|
|
270
568
|
var CompanyEndpoint = class {
|
|
271
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
569
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
272
570
|
this.baseUrl = baseUrl;
|
|
273
571
|
this.apiKey = apiKey;
|
|
274
572
|
this.tokenStorage = tokenStorage;
|
|
275
573
|
this.httpClient = httpClient;
|
|
574
|
+
this.retryPolicy = retryPolicy;
|
|
276
575
|
}
|
|
277
576
|
baseUrl;
|
|
278
577
|
apiKey;
|
|
279
578
|
tokenStorage;
|
|
280
579
|
httpClient;
|
|
580
|
+
retryPolicy;
|
|
281
581
|
async get() {
|
|
282
582
|
const headers = {
|
|
283
583
|
"Content-Type": "application/json",
|
|
@@ -287,25 +587,33 @@ var CompanyEndpoint = class {
|
|
|
287
587
|
if (token) {
|
|
288
588
|
headers["Authorization"] = `Bearer ${token.accessToken}`;
|
|
289
589
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
590
|
+
return withRetries(this.retryPolicy, "company request", async () => {
|
|
591
|
+
const response = await this.httpClient.request("GET", `${this.baseUrl}/company`, { headers });
|
|
592
|
+
const responseText = await response.text();
|
|
593
|
+
if (!response.ok) {
|
|
594
|
+
throw new HttpException(
|
|
595
|
+
describeApiError(responseText, response.status),
|
|
596
|
+
response.status,
|
|
597
|
+
responseText,
|
|
598
|
+
{},
|
|
599
|
+
response.headers.get("retry-after")
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
let parsed;
|
|
603
|
+
try {
|
|
604
|
+
parsed = JSON.parse(responseText);
|
|
605
|
+
} catch {
|
|
606
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
607
|
+
}
|
|
608
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
609
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
610
|
+
}
|
|
611
|
+
const obj = parsed;
|
|
612
|
+
if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
|
|
613
|
+
return normalizeCompanyResource(obj["data"]);
|
|
614
|
+
}
|
|
615
|
+
return normalizeCompanyResource(obj);
|
|
616
|
+
});
|
|
309
617
|
}
|
|
310
618
|
};
|
|
311
619
|
|
|
@@ -344,6 +652,31 @@ function describeComplianceEnrollmentStatus(status) {
|
|
|
344
652
|
return DESCRIPTIONS[status];
|
|
345
653
|
}
|
|
346
654
|
|
|
655
|
+
// src/enums/ComplianceService.ts
|
|
656
|
+
var ComplianceService = {
|
|
657
|
+
E_REPORTING: "e_reporting",
|
|
658
|
+
E_INVOICING: "e_invoicing",
|
|
659
|
+
UNKNOWN: "unknown"
|
|
660
|
+
};
|
|
661
|
+
var VALUES2 = Object.values(ComplianceService);
|
|
662
|
+
function toComplianceService(value) {
|
|
663
|
+
if (typeof value === "string") {
|
|
664
|
+
const normalized = value.trim().toLowerCase();
|
|
665
|
+
if (VALUES2.includes(normalized)) {
|
|
666
|
+
return normalized;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return ComplianceService.UNKNOWN;
|
|
670
|
+
}
|
|
671
|
+
var DESCRIPTIONS2 = {
|
|
672
|
+
[ComplianceService.E_REPORTING]: "E-reporting of transaction data to the tax authority (France: DGFiP Flux 10).",
|
|
673
|
+
[ComplianceService.E_INVOICING]: "Structured e-invoices delivered to the buyer (Norway: EHF / Peppol BIS 3.0).",
|
|
674
|
+
[ComplianceService.UNKNOWN]: "Unknown service."
|
|
675
|
+
};
|
|
676
|
+
function describeComplianceService(service) {
|
|
677
|
+
return DESCRIPTIONS2[service];
|
|
678
|
+
}
|
|
679
|
+
|
|
347
680
|
// src/dto/ComplianceEnrollment.ts
|
|
348
681
|
function asString(value, fallback = "") {
|
|
349
682
|
return typeof value === "string" ? value : fallback;
|
|
@@ -355,7 +688,7 @@ function asNumber(value) {
|
|
|
355
688
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
356
689
|
}
|
|
357
690
|
var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
358
|
-
constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt) {
|
|
691
|
+
constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt, service = ComplianceService.UNKNOWN) {
|
|
359
692
|
this.id = id;
|
|
360
693
|
this.companyId = companyId;
|
|
361
694
|
this.country = country;
|
|
@@ -374,6 +707,7 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
374
707
|
this.autoSend = autoSend;
|
|
375
708
|
this.createdAt = createdAt;
|
|
376
709
|
this.updatedAt = updatedAt;
|
|
710
|
+
this.service = service;
|
|
377
711
|
}
|
|
378
712
|
id;
|
|
379
713
|
companyId;
|
|
@@ -393,6 +727,7 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
393
727
|
autoSend;
|
|
394
728
|
createdAt;
|
|
395
729
|
updatedAt;
|
|
730
|
+
service;
|
|
396
731
|
static fromArray(data) {
|
|
397
732
|
const regimeConfig = typeof data["regime_config"] === "object" && data["regime_config"] !== null && !Array.isArray(data["regime_config"]) ? data["regime_config"] : null;
|
|
398
733
|
return new _ComplianceEnrollment(
|
|
@@ -413,7 +748,8 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
413
748
|
asNullableString(data["notification_email"]),
|
|
414
749
|
data["auto_send"] === true,
|
|
415
750
|
asNullableString(data["created_at"]),
|
|
416
|
-
asNullableString(data["updated_at"])
|
|
751
|
+
asNullableString(data["updated_at"]),
|
|
752
|
+
toComplianceService(data["service"])
|
|
417
753
|
);
|
|
418
754
|
}
|
|
419
755
|
};
|
|
@@ -463,17 +799,17 @@ var ComplianceTaxReportState = {
|
|
|
463
799
|
ERROR: "error",
|
|
464
800
|
UNKNOWN: "unknown"
|
|
465
801
|
};
|
|
466
|
-
var
|
|
802
|
+
var VALUES3 = Object.values(ComplianceTaxReportState);
|
|
467
803
|
function toComplianceTaxReportState(value) {
|
|
468
804
|
if (typeof value === "string") {
|
|
469
805
|
const normalized = value.trim().toLowerCase();
|
|
470
|
-
if (
|
|
806
|
+
if (VALUES3.includes(normalized)) {
|
|
471
807
|
return normalized;
|
|
472
808
|
}
|
|
473
809
|
}
|
|
474
810
|
return ComplianceTaxReportState.UNKNOWN;
|
|
475
811
|
}
|
|
476
|
-
var
|
|
812
|
+
var DESCRIPTIONS3 = {
|
|
477
813
|
[ComplianceTaxReportState.NEW]: "Created at provider, waiting for the daily ledger batch.",
|
|
478
814
|
[ComplianceTaxReportState.SENT]: "Ledger transmitted to PPF.",
|
|
479
815
|
[ComplianceTaxReportState.ACKNOWLEDGED]: "PPF acknowledged receipt of the ledger.",
|
|
@@ -483,7 +819,7 @@ var DESCRIPTIONS2 = {
|
|
|
483
819
|
[ComplianceTaxReportState.UNKNOWN]: "Unknown state."
|
|
484
820
|
};
|
|
485
821
|
function describeComplianceTaxReportState(state) {
|
|
486
|
-
return
|
|
822
|
+
return DESCRIPTIONS3[state];
|
|
487
823
|
}
|
|
488
824
|
|
|
489
825
|
// src/dto/ComplianceTaxReport.ts
|
|
@@ -605,6 +941,31 @@ var ComplianceTaxReportPage = class _ComplianceTaxReportPage {
|
|
|
605
941
|
}
|
|
606
942
|
};
|
|
607
943
|
|
|
944
|
+
// src/enums/ComplianceDocumentType.ts
|
|
945
|
+
var ComplianceDocumentType = {
|
|
946
|
+
INVOICE: "invoice",
|
|
947
|
+
CREDIT_NOTE: "credit_note",
|
|
948
|
+
UNKNOWN: "unknown"
|
|
949
|
+
};
|
|
950
|
+
var VALUES4 = Object.values(ComplianceDocumentType);
|
|
951
|
+
function toComplianceDocumentType(value) {
|
|
952
|
+
if (typeof value === "string") {
|
|
953
|
+
const normalized = value.trim().toLowerCase();
|
|
954
|
+
if (VALUES4.includes(normalized)) {
|
|
955
|
+
return normalized;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return ComplianceDocumentType.UNKNOWN;
|
|
959
|
+
}
|
|
960
|
+
var DESCRIPTIONS4 = {
|
|
961
|
+
[ComplianceDocumentType.INVOICE]: "Commercial invoice.",
|
|
962
|
+
[ComplianceDocumentType.CREDIT_NOTE]: "Credit note correcting a previously issued invoice.",
|
|
963
|
+
[ComplianceDocumentType.UNKNOWN]: "Unknown document type."
|
|
964
|
+
};
|
|
965
|
+
function describeComplianceDocumentType(type) {
|
|
966
|
+
return DESCRIPTIONS4[type];
|
|
967
|
+
}
|
|
968
|
+
|
|
608
969
|
// src/enums/ComplianceTransactionState.ts
|
|
609
970
|
var ComplianceTransactionState = {
|
|
610
971
|
PENDING: "pending",
|
|
@@ -613,17 +974,17 @@ var ComplianceTransactionState = {
|
|
|
613
974
|
ERROR: "error",
|
|
614
975
|
UNKNOWN: "unknown"
|
|
615
976
|
};
|
|
616
|
-
var
|
|
977
|
+
var VALUES5 = Object.values(ComplianceTransactionState);
|
|
617
978
|
function toComplianceTransactionState(value) {
|
|
618
979
|
if (typeof value === "string") {
|
|
619
980
|
const normalized = value.trim().toLowerCase();
|
|
620
|
-
if (
|
|
981
|
+
if (VALUES5.includes(normalized)) {
|
|
621
982
|
return normalized;
|
|
622
983
|
}
|
|
623
984
|
}
|
|
624
985
|
return ComplianceTransactionState.UNKNOWN;
|
|
625
986
|
}
|
|
626
|
-
var
|
|
987
|
+
var DESCRIPTIONS5 = {
|
|
627
988
|
[ComplianceTransactionState.PENDING]: "Recorded locally, not yet submitted to the provider.",
|
|
628
989
|
[ComplianceTransactionState.SENDING]: "Submission to the provider in progress.",
|
|
629
990
|
[ComplianceTransactionState.SUBMITTED]: "Successfully submitted to the provider.",
|
|
@@ -631,7 +992,7 @@ var DESCRIPTIONS3 = {
|
|
|
631
992
|
[ComplianceTransactionState.UNKNOWN]: "Unknown state."
|
|
632
993
|
};
|
|
633
994
|
function describeComplianceTransactionState(state) {
|
|
634
|
-
return
|
|
995
|
+
return DESCRIPTIONS5[state];
|
|
635
996
|
}
|
|
636
997
|
|
|
637
998
|
// src/enums/ComplianceTransactionType.ts
|
|
@@ -643,17 +1004,17 @@ var ComplianceTransactionType = {
|
|
|
643
1004
|
CROSSBORDER_INBOUND: "crossborder_inbound",
|
|
644
1005
|
UNKNOWN: "unknown"
|
|
645
1006
|
};
|
|
646
|
-
var
|
|
1007
|
+
var VALUES6 = Object.values(ComplianceTransactionType);
|
|
647
1008
|
function toComplianceTransactionType(value) {
|
|
648
1009
|
if (typeof value === "string") {
|
|
649
1010
|
const normalized = value.trim().toLowerCase();
|
|
650
|
-
if (
|
|
1011
|
+
if (VALUES6.includes(normalized)) {
|
|
651
1012
|
return normalized;
|
|
652
1013
|
}
|
|
653
1014
|
}
|
|
654
1015
|
return ComplianceTransactionType.UNKNOWN;
|
|
655
1016
|
}
|
|
656
|
-
var
|
|
1017
|
+
var DESCRIPTIONS6 = {
|
|
657
1018
|
[ComplianceTransactionType.B2C_OUTBOUND]: "Sale to a private person in the merchant country (DGFiP Flux 10.3).",
|
|
658
1019
|
[ComplianceTransactionType.B2B_DOMESTIC_OUTBOUND]: "Sale to a domestic business (DGFiP Flux 1, Phase 2).",
|
|
659
1020
|
[ComplianceTransactionType.B2B_DOMESTIC_INBOUND]: "Purchase from a domestic business (DGFiP Flux 1, Phase 2).",
|
|
@@ -662,7 +1023,7 @@ var DESCRIPTIONS4 = {
|
|
|
662
1023
|
[ComplianceTransactionType.UNKNOWN]: "Unknown transaction type."
|
|
663
1024
|
};
|
|
664
1025
|
function describeComplianceTransactionType(type) {
|
|
665
|
-
return
|
|
1026
|
+
return DESCRIPTIONS6[type];
|
|
666
1027
|
}
|
|
667
1028
|
|
|
668
1029
|
// src/dto/ComplianceTransaction.ts
|
|
@@ -680,8 +1041,34 @@ function asMoney2(value) {
|
|
|
680
1041
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
681
1042
|
return "0.00";
|
|
682
1043
|
}
|
|
1044
|
+
function asNullableNumber2(value) {
|
|
1045
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1046
|
+
if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) return Number(value);
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
function asRecord(value) {
|
|
1050
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1051
|
+
}
|
|
1052
|
+
function asRecordList(value) {
|
|
1053
|
+
return Array.isArray(value) ? value.filter((row) => asRecord(row) !== null) : [];
|
|
1054
|
+
}
|
|
1055
|
+
function toInvoiceLines(providerPayload) {
|
|
1056
|
+
const invoice = asRecord(asRecord(providerPayload)?.["invoice"]);
|
|
1057
|
+
return asRecordList(invoice?.["invoice_lines_attributes"]).map((line) => ({
|
|
1058
|
+
description: asString3(line["description"]),
|
|
1059
|
+
quantity: asNullableNumber2(line["quantity"]),
|
|
1060
|
+
price: asMoney2(line["price"]),
|
|
1061
|
+
unit: asNullableNumber2(line["unit"]),
|
|
1062
|
+
taxes: asRecordList(line["taxes_attributes"]).map((tax) => ({
|
|
1063
|
+
name: asString3(tax["name"]),
|
|
1064
|
+
percent: asNullableNumber2(tax["percent"]),
|
|
1065
|
+
category: asNullableString3(tax["category"]),
|
|
1066
|
+
comment: asNullableString3(tax["comment"])
|
|
1067
|
+
}))
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
683
1070
|
var ComplianceTransaction = class _ComplianceTransaction {
|
|
684
|
-
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) {
|
|
1071
|
+
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 = []) {
|
|
685
1072
|
this.id = id;
|
|
686
1073
|
this.companyId = companyId;
|
|
687
1074
|
this.complianceEnrollmentId = complianceEnrollmentId;
|
|
@@ -709,6 +1096,17 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
709
1096
|
this.taxReport = taxReport;
|
|
710
1097
|
this.createdAt = createdAt;
|
|
711
1098
|
this.updatedAt = updatedAt;
|
|
1099
|
+
this.counterpartyRegisterId = counterpartyRegisterId;
|
|
1100
|
+
this.counterpartyAddress = counterpartyAddress;
|
|
1101
|
+
this.counterpartyCity = counterpartyCity;
|
|
1102
|
+
this.counterpartyPostalcode = counterpartyPostalcode;
|
|
1103
|
+
this.counterpartyEmail = counterpartyEmail;
|
|
1104
|
+
this.buyerReference = buyerReference;
|
|
1105
|
+
this.documentType = documentType;
|
|
1106
|
+
this.amendedNumber = amendedNumber;
|
|
1107
|
+
this.amendedDate = amendedDate;
|
|
1108
|
+
this.providerState = providerState;
|
|
1109
|
+
this.invoiceLines = invoiceLines;
|
|
712
1110
|
}
|
|
713
1111
|
id;
|
|
714
1112
|
companyId;
|
|
@@ -737,6 +1135,20 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
737
1135
|
taxReport;
|
|
738
1136
|
createdAt;
|
|
739
1137
|
updatedAt;
|
|
1138
|
+
counterpartyRegisterId;
|
|
1139
|
+
counterpartyAddress;
|
|
1140
|
+
counterpartyCity;
|
|
1141
|
+
counterpartyPostalcode;
|
|
1142
|
+
counterpartyEmail;
|
|
1143
|
+
buyerReference;
|
|
1144
|
+
documentType;
|
|
1145
|
+
amendedNumber;
|
|
1146
|
+
amendedDate;
|
|
1147
|
+
providerState;
|
|
1148
|
+
invoiceLines;
|
|
1149
|
+
get isCreditNote() {
|
|
1150
|
+
return this.documentType === ComplianceDocumentType.CREDIT_NOTE;
|
|
1151
|
+
}
|
|
740
1152
|
static fromArray(data) {
|
|
741
1153
|
const taxReport = typeof data["tax_report"] === "object" && data["tax_report"] !== null && !Array.isArray(data["tax_report"]) ? ComplianceTaxReport.fromArray(data["tax_report"]) : void 0;
|
|
742
1154
|
return new _ComplianceTransaction(
|
|
@@ -766,7 +1178,18 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
766
1178
|
asNullableString3(data["reported_at"]),
|
|
767
1179
|
taxReport,
|
|
768
1180
|
asNullableString3(data["created_at"]),
|
|
769
|
-
asNullableString3(data["updated_at"])
|
|
1181
|
+
asNullableString3(data["updated_at"]),
|
|
1182
|
+
asNullableString3(data["counterparty_register_id"]),
|
|
1183
|
+
asNullableString3(data["counterparty_address"]),
|
|
1184
|
+
asNullableString3(data["counterparty_city"]),
|
|
1185
|
+
asNullableString3(data["counterparty_postalcode"]),
|
|
1186
|
+
asNullableString3(data["counterparty_email"]),
|
|
1187
|
+
asNullableString3(data["buyer_reference"]),
|
|
1188
|
+
data["document_type"] === void 0 || data["document_type"] === null ? ComplianceDocumentType.INVOICE : toComplianceDocumentType(data["document_type"]),
|
|
1189
|
+
asNullableString3(data["amended_number"]),
|
|
1190
|
+
asNullableString3(data["amended_date"]),
|
|
1191
|
+
asNullableString3(data["provider_state"]),
|
|
1192
|
+
toInvoiceLines(data["provider_payload"])
|
|
770
1193
|
);
|
|
771
1194
|
}
|
|
772
1195
|
};
|
|
@@ -825,10 +1248,134 @@ var ImportResult = class _ImportResult {
|
|
|
825
1248
|
}
|
|
826
1249
|
};
|
|
827
1250
|
|
|
828
|
-
// src/
|
|
1251
|
+
// src/enums/PeppolLookupStatus.ts
|
|
1252
|
+
var PeppolLookupStatus = {
|
|
1253
|
+
REACHABLE: "reachable",
|
|
1254
|
+
NOT_REACHABLE: "not_reachable",
|
|
1255
|
+
PENDING: "pending",
|
|
1256
|
+
UNKNOWN: "unknown"
|
|
1257
|
+
};
|
|
1258
|
+
var VALUES7 = Object.values(PeppolLookupStatus);
|
|
1259
|
+
function toPeppolLookupStatus(value) {
|
|
1260
|
+
if (typeof value === "string") {
|
|
1261
|
+
const normalized = value.trim().toLowerCase();
|
|
1262
|
+
if (VALUES7.includes(normalized)) {
|
|
1263
|
+
return normalized;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return PeppolLookupStatus.UNKNOWN;
|
|
1267
|
+
}
|
|
1268
|
+
var DESCRIPTIONS7 = {
|
|
1269
|
+
[PeppolLookupStatus.REACHABLE]: "The participant is registered on Peppol and can receive e-invoices.",
|
|
1270
|
+
[PeppolLookupStatus.NOT_REACHABLE]: "The participant is not registered on Peppol.",
|
|
1271
|
+
[PeppolLookupStatus.PENDING]: "The directory lookup is still resolving \u2014 ask again in a few seconds.",
|
|
1272
|
+
[PeppolLookupStatus.UNKNOWN]: "Unknown lookup status."
|
|
1273
|
+
};
|
|
1274
|
+
function describePeppolLookupStatus(status) {
|
|
1275
|
+
return DESCRIPTIONS7[status];
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/dto/PeppolLookupResult.ts
|
|
829
1279
|
function asString4(value, fallback = "") {
|
|
830
1280
|
return typeof value === "string" ? value : fallback;
|
|
831
1281
|
}
|
|
1282
|
+
function asNullableString4(value) {
|
|
1283
|
+
return typeof value === "string" ? value : null;
|
|
1284
|
+
}
|
|
1285
|
+
var PeppolLookupResult = class _PeppolLookupResult {
|
|
1286
|
+
constructor(status, reachable, country, scheme, id, documentTypes, transportTypeCode, checkedAt) {
|
|
1287
|
+
this.status = status;
|
|
1288
|
+
this.reachable = reachable;
|
|
1289
|
+
this.country = country;
|
|
1290
|
+
this.scheme = scheme;
|
|
1291
|
+
this.id = id;
|
|
1292
|
+
this.documentTypes = documentTypes;
|
|
1293
|
+
this.transportTypeCode = transportTypeCode;
|
|
1294
|
+
this.checkedAt = checkedAt;
|
|
1295
|
+
}
|
|
1296
|
+
status;
|
|
1297
|
+
reachable;
|
|
1298
|
+
country;
|
|
1299
|
+
scheme;
|
|
1300
|
+
id;
|
|
1301
|
+
documentTypes;
|
|
1302
|
+
transportTypeCode;
|
|
1303
|
+
checkedAt;
|
|
1304
|
+
/** True while the directory is still resolving — ask again in a few seconds. */
|
|
1305
|
+
get isPending() {
|
|
1306
|
+
return this.status === PeppolLookupStatus.PENDING;
|
|
1307
|
+
}
|
|
1308
|
+
static fromArray(data) {
|
|
1309
|
+
const documentTypes = Array.isArray(data["document_types"]) ? data["document_types"].filter((t) => typeof t === "string") : [];
|
|
1310
|
+
return new _PeppolLookupResult(
|
|
1311
|
+
toPeppolLookupStatus(data["status"]),
|
|
1312
|
+
typeof data["reachable"] === "boolean" ? data["reachable"] : null,
|
|
1313
|
+
asString4(data["country"]),
|
|
1314
|
+
asString4(data["scheme"]),
|
|
1315
|
+
asString4(data["id"]),
|
|
1316
|
+
documentTypes,
|
|
1317
|
+
asNullableString4(data["transport_type_code"]),
|
|
1318
|
+
asNullableString4(data["checked_at"])
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
// src/dto/RegistryCompany.ts
|
|
1324
|
+
function asString5(value, fallback = "") {
|
|
1325
|
+
return typeof value === "string" ? value : fallback;
|
|
1326
|
+
}
|
|
1327
|
+
function asNullableString5(value) {
|
|
1328
|
+
return typeof value === "string" ? value : null;
|
|
1329
|
+
}
|
|
1330
|
+
var RegistryCompany = class _RegistryCompany {
|
|
1331
|
+
constructor(orgNumber, companyName, organisationForm, vatRegistered, vatNumber, enterpriseRegister, bankrupt, underLiquidation, address, postalcode, city, country) {
|
|
1332
|
+
this.orgNumber = orgNumber;
|
|
1333
|
+
this.companyName = companyName;
|
|
1334
|
+
this.organisationForm = organisationForm;
|
|
1335
|
+
this.vatRegistered = vatRegistered;
|
|
1336
|
+
this.vatNumber = vatNumber;
|
|
1337
|
+
this.enterpriseRegister = enterpriseRegister;
|
|
1338
|
+
this.bankrupt = bankrupt;
|
|
1339
|
+
this.underLiquidation = underLiquidation;
|
|
1340
|
+
this.address = address;
|
|
1341
|
+
this.postalcode = postalcode;
|
|
1342
|
+
this.city = city;
|
|
1343
|
+
this.country = country;
|
|
1344
|
+
}
|
|
1345
|
+
orgNumber;
|
|
1346
|
+
companyName;
|
|
1347
|
+
organisationForm;
|
|
1348
|
+
vatRegistered;
|
|
1349
|
+
vatNumber;
|
|
1350
|
+
enterpriseRegister;
|
|
1351
|
+
bankrupt;
|
|
1352
|
+
underLiquidation;
|
|
1353
|
+
address;
|
|
1354
|
+
postalcode;
|
|
1355
|
+
city;
|
|
1356
|
+
country;
|
|
1357
|
+
static fromArray(data) {
|
|
1358
|
+
return new _RegistryCompany(
|
|
1359
|
+
asString5(data["org_number"]),
|
|
1360
|
+
asString5(data["company_name"]),
|
|
1361
|
+
asNullableString5(data["organisation_form"]),
|
|
1362
|
+
data["vat_registered"] === true,
|
|
1363
|
+
asNullableString5(data["vat_number"]),
|
|
1364
|
+
data["enterprise_register"] === true,
|
|
1365
|
+
data["bankrupt"] === true,
|
|
1366
|
+
data["under_liquidation"] === true,
|
|
1367
|
+
asNullableString5(data["address"]),
|
|
1368
|
+
asNullableString5(data["postalcode"]),
|
|
1369
|
+
asNullableString5(data["city"]),
|
|
1370
|
+
asString5(data["country"])
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
// src/dto/RevenueStatistics.ts
|
|
1376
|
+
function asString6(value, fallback = "") {
|
|
1377
|
+
return typeof value === "string" ? value : fallback;
|
|
1378
|
+
}
|
|
832
1379
|
function asNumber4(value) {
|
|
833
1380
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
834
1381
|
}
|
|
@@ -878,35 +1425,35 @@ var RevenueStatistics = class _RevenueStatistics {
|
|
|
878
1425
|
typeof data["totals"] === "object" && data["totals"] !== null ? data["totals"] : {}
|
|
879
1426
|
);
|
|
880
1427
|
const timeSeries = asRecords(data["time_series"]).map((row) => ({
|
|
881
|
-
bucket:
|
|
1428
|
+
bucket: asString6(row["bucket"]),
|
|
882
1429
|
...toTotals(row)
|
|
883
1430
|
}));
|
|
884
1431
|
const byType = asRecords(data["by_type"]).map((row) => ({
|
|
885
|
-
type:
|
|
886
|
-
typeLabel:
|
|
1432
|
+
type: asString6(row["type"]),
|
|
1433
|
+
typeLabel: asString6(row["type_label"]),
|
|
887
1434
|
count: asNumber4(row["count"]),
|
|
888
1435
|
total: asMoney3(row["total"])
|
|
889
1436
|
}));
|
|
890
1437
|
const byCountry = asRecords(data["by_country"]).map((row) => ({
|
|
891
|
-
country:
|
|
1438
|
+
country: asString6(row["country"], "unknown"),
|
|
892
1439
|
count: asNumber4(row["count"]),
|
|
893
1440
|
total: asMoney3(row["total"])
|
|
894
1441
|
}));
|
|
895
1442
|
const byState = asRecords(data["by_state"]).map((row) => ({
|
|
896
|
-
state:
|
|
897
|
-
stateLabel:
|
|
1443
|
+
state: asString6(row["state"]),
|
|
1444
|
+
stateLabel: asString6(row["state_label"]),
|
|
898
1445
|
count: asNumber4(row["count"]),
|
|
899
1446
|
total: asMoney3(row["total"])
|
|
900
1447
|
}));
|
|
901
1448
|
const byCurrency = asRecords(data["by_currency"]).map((row) => ({
|
|
902
|
-
currency:
|
|
1449
|
+
currency: asString6(row["currency"]),
|
|
903
1450
|
...toTotals(row)
|
|
904
1451
|
}));
|
|
905
1452
|
return new _RevenueStatistics(
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1453
|
+
asString6(range["date_from"]) || void 0,
|
|
1454
|
+
asString6(range["date_to"]) || void 0,
|
|
1455
|
+
asString6(range["interval"]) || void 0,
|
|
1456
|
+
asString6(data["primary_currency"], "EUR"),
|
|
910
1457
|
data["is_multi_currency"] === true,
|
|
911
1458
|
totals,
|
|
912
1459
|
timeSeries,
|
|
@@ -919,10 +1466,10 @@ var RevenueStatistics = class _RevenueStatistics {
|
|
|
919
1466
|
};
|
|
920
1467
|
|
|
921
1468
|
// src/dto/SireneLookupResult.ts
|
|
922
|
-
function
|
|
1469
|
+
function asString7(value, fallback = "") {
|
|
923
1470
|
return typeof value === "string" ? value : fallback;
|
|
924
1471
|
}
|
|
925
|
-
function
|
|
1472
|
+
function asNullableString6(value) {
|
|
926
1473
|
return typeof value === "string" ? value : null;
|
|
927
1474
|
}
|
|
928
1475
|
var SireneLookupResult = class _SireneLookupResult {
|
|
@@ -948,21 +1495,21 @@ var SireneLookupResult = class _SireneLookupResult {
|
|
|
948
1495
|
postalcode;
|
|
949
1496
|
static fromArray(data) {
|
|
950
1497
|
return new _SireneLookupResult(
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1498
|
+
asString7(data["company_name"]),
|
|
1499
|
+
asString7(data["siren"]),
|
|
1500
|
+
asNullableString6(data["siret"]),
|
|
1501
|
+
asNullableString6(data["naf_code"]),
|
|
1502
|
+
asNullableString6(data["naf_full"]),
|
|
1503
|
+
asNullableString6(data["enterprise_size"]),
|
|
1504
|
+
asNullableString6(data["address"]),
|
|
1505
|
+
asNullableString6(data["city"]),
|
|
1506
|
+
asNullableString6(data["postalcode"])
|
|
960
1507
|
);
|
|
961
1508
|
}
|
|
962
1509
|
};
|
|
963
1510
|
|
|
964
1511
|
// src/dto/VatRates.ts
|
|
965
|
-
function
|
|
1512
|
+
function asString8(value, fallback = "") {
|
|
966
1513
|
return typeof value === "string" ? value : fallback;
|
|
967
1514
|
}
|
|
968
1515
|
function asNumber5(value) {
|
|
@@ -980,12 +1527,12 @@ var VatRates = class _VatRates {
|
|
|
980
1527
|
static fromArray(data) {
|
|
981
1528
|
const rates = (Array.isArray(data["rates"]) ? data["rates"] : []).filter((row) => typeof row === "object" && row !== null).map((row) => ({
|
|
982
1529
|
percent: asNumber5(row["percent"]),
|
|
983
|
-
category:
|
|
984
|
-
labelKey:
|
|
985
|
-
taxName:
|
|
1530
|
+
category: asString8(row["category"]),
|
|
1531
|
+
labelKey: asString8(row["label_key"]),
|
|
1532
|
+
taxName: asString8(row["tax_name"]),
|
|
986
1533
|
requiresVatex: row["requires_vatex"] === true
|
|
987
1534
|
}));
|
|
988
|
-
return new _VatRates(
|
|
1535
|
+
return new _VatRates(asString8(data["country"]), asString8(data["currency"], "EUR"), rates);
|
|
989
1536
|
}
|
|
990
1537
|
};
|
|
991
1538
|
|
|
@@ -1004,10 +1551,48 @@ var ValidationException = class extends TaxoraException {
|
|
|
1004
1551
|
}
|
|
1005
1552
|
};
|
|
1006
1553
|
|
|
1554
|
+
// src/support/NorwegianOrgNumber.ts
|
|
1555
|
+
var WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2];
|
|
1556
|
+
var NorwegianOrgNumber = {
|
|
1557
|
+
/** Peppol identifier scheme for Norwegian organisation numbers. */
|
|
1558
|
+
PEPPOL_SCHEME: "0192",
|
|
1559
|
+
/**
|
|
1560
|
+
* Strips whitespace, dots, dashes, a "0192:" Peppol scheme prefix, the "NO"
|
|
1561
|
+
* prefix and the "MVA" suffix: "NO 923 609 016 MVA", "923609016MVA" and
|
|
1562
|
+
* "923 609 016" all become "923609016". Does not validate.
|
|
1563
|
+
*/
|
|
1564
|
+
normalize(value) {
|
|
1565
|
+
let v = (value ?? "").trim().toUpperCase().replace(/[\s.-]/g, "");
|
|
1566
|
+
if (v.startsWith("0192:")) v = v.slice(5);
|
|
1567
|
+
if (v.startsWith("NO")) v = v.slice(2);
|
|
1568
|
+
if (v.endsWith("MVA")) v = v.slice(0, -3);
|
|
1569
|
+
return v;
|
|
1570
|
+
},
|
|
1571
|
+
/** True when the (normalized) value is 9 digits with a valid mod-11 check digit. */
|
|
1572
|
+
isValid(value) {
|
|
1573
|
+
const digits = NorwegianOrgNumber.normalize(value);
|
|
1574
|
+
if (!/^\d{9}$/.test(digits)) return false;
|
|
1575
|
+
let sum = 0;
|
|
1576
|
+
for (let i = 0; i < WEIGHTS.length; i++) {
|
|
1577
|
+
sum += Number(digits[i]) * WEIGHTS[i];
|
|
1578
|
+
}
|
|
1579
|
+
let check = 11 - sum % 11;
|
|
1580
|
+
if (check === 11) check = 0;
|
|
1581
|
+
return check !== 10 && check === Number(digits[8]);
|
|
1582
|
+
},
|
|
1583
|
+
/** "NO923609016MVA" — the Norwegian VAT number of an MVA-registered entity. Does not validate. */
|
|
1584
|
+
toVatNumber(value) {
|
|
1585
|
+
return `NO${NorwegianOrgNumber.normalize(value)}MVA`;
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1007
1589
|
// src/endpoints/EReportingEndpoint.ts
|
|
1008
1590
|
var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
1009
1591
|
var NAF_CODE_REGEX = /^\d{2}$/;
|
|
1592
|
+
var COUNTRY_REGEX = /^[A-Za-z]{2}$/;
|
|
1593
|
+
var PEPPOL_SCHEME_REGEX = /^\d{4}$/;
|
|
1010
1594
|
var INTERVALS = ["day", "week", "month"];
|
|
1595
|
+
var SERVICES = ["e_reporting", "e_invoicing"];
|
|
1011
1596
|
var ENTERPRISE_SIZES = ["micro", "pme", "eti", "ge"];
|
|
1012
1597
|
var TYPE_OPERATIONS = ["services", "goods", "mixed"];
|
|
1013
1598
|
var TRANSACTION_TYPES = [
|
|
@@ -1018,29 +1603,40 @@ var TRANSACTION_TYPES = [
|
|
|
1018
1603
|
"crossborder_inbound"
|
|
1019
1604
|
];
|
|
1020
1605
|
var EReportingEndpoint = class {
|
|
1021
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
1606
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
1022
1607
|
this.baseUrl = baseUrl;
|
|
1023
1608
|
this.apiKey = apiKey;
|
|
1024
1609
|
this.tokenStorage = tokenStorage;
|
|
1025
1610
|
this.httpClient = httpClient;
|
|
1611
|
+
this.retryPolicy = retryPolicy;
|
|
1026
1612
|
}
|
|
1027
1613
|
baseUrl;
|
|
1028
1614
|
apiKey;
|
|
1029
1615
|
tokenStorage;
|
|
1030
1616
|
httpClient;
|
|
1617
|
+
retryPolicy;
|
|
1031
1618
|
// ---------------------------------------------------------------------
|
|
1032
1619
|
// Enrollments
|
|
1033
1620
|
// ---------------------------------------------------------------------
|
|
1034
1621
|
/**
|
|
1035
1622
|
* Create a compliance enrollment and provision the provider account. By
|
|
1036
|
-
* default the
|
|
1623
|
+
* default the country regime is activated right away (France: DGFiP tax
|
|
1624
|
+
* report setting; Norway: Peppol registration); pass `autoActivate: false`
|
|
1037
1625
|
* to only create the account.
|
|
1038
1626
|
*
|
|
1039
|
-
*
|
|
1040
|
-
* the
|
|
1627
|
+
* The input shape depends on `country`: omitted/"FR" takes the France input
|
|
1628
|
+
* (unchanged, backwards compatible), "NO" the Norway input — see also the
|
|
1629
|
+
* createNorwayEnrollment() shortcut.
|
|
1630
|
+
*
|
|
1631
|
+
* Errors: 403 (HttpException, body code `service_not_active`) when the company
|
|
1632
|
+
* lacks the requested service; on provider failure 502 (HttpException) — the
|
|
1633
|
+
* enrollment is then persisted in `error_account`/`error_activation` state so
|
|
1041
1634
|
* provisioning can be retried.
|
|
1042
1635
|
*/
|
|
1043
1636
|
async createEnrollment(input) {
|
|
1637
|
+
if (this.isNorwayEnrollmentInput(input)) {
|
|
1638
|
+
return this.createEnrollmentFromBody(this.buildNorwayEnrollmentBody(input));
|
|
1639
|
+
}
|
|
1044
1640
|
this.validateEnrollmentInput(input);
|
|
1045
1641
|
const body = {
|
|
1046
1642
|
email: input.email,
|
|
@@ -1050,6 +1646,7 @@ var EReportingEndpoint = class {
|
|
|
1050
1646
|
reporting_start_date: this.formatDate(input.reportingStartDate)
|
|
1051
1647
|
};
|
|
1052
1648
|
if (input.country !== void 0) body["country"] = input.country;
|
|
1649
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1053
1650
|
if (input.regime !== void 0) body["regime"] = input.regime;
|
|
1054
1651
|
if (input.siret !== void 0) body["siret"] = input.siret;
|
|
1055
1652
|
if (input.siren !== void 0) body["siren"] = input.siren;
|
|
@@ -1060,9 +1657,15 @@ var EReportingEndpoint = class {
|
|
|
1060
1657
|
if (input.postalcode !== void 0) body["postalcode"] = input.postalcode;
|
|
1061
1658
|
if (input.province !== void 0) body["province"] = input.province;
|
|
1062
1659
|
if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1660
|
+
return this.createEnrollmentFromBody(body);
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Enroll the company for Norway e-invoicing (EHF / Peppol BIS 3.0) — shortcut
|
|
1664
|
+
* for createEnrollment({ country: 'NO', ... }). The organisation number is
|
|
1665
|
+
* checked (mod-11) and normalized to 9 digits before sending.
|
|
1666
|
+
*/
|
|
1667
|
+
async createNorwayEnrollment(input) {
|
|
1668
|
+
return this.createEnrollment({ ...input, country: "NO" });
|
|
1066
1669
|
}
|
|
1067
1670
|
/** Paginated list of the company's enrollments (newest first). `perPage` is capped at 100 server-side. */
|
|
1068
1671
|
async listEnrollments(page = 1, perPage = 25) {
|
|
@@ -1096,6 +1699,72 @@ var EReportingEndpoint = class {
|
|
|
1096
1699
|
const data = await this.parseJsonResponse(response);
|
|
1097
1700
|
return SireneLookupResult.fromArray(data);
|
|
1098
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Look up companies in a national company register — currently Norway
|
|
1704
|
+
* (Brønnøysund Enhetsregisteret). `q` is an organisation number, a "NO…MVA"
|
|
1705
|
+
* VAT number or a company name; an exact number match yields one result.
|
|
1706
|
+
* Ideal for prefilling createNorwayEnrollment() and the buyer fields of an
|
|
1707
|
+
* e-invoice. For France use sireneLookup().
|
|
1708
|
+
*
|
|
1709
|
+
* Errors: 404 (HttpException) when nothing is found, 422 (ValidationException)
|
|
1710
|
+
* on invalid input / unsupported country, 429 (HttpException) when the
|
|
1711
|
+
* per-user rate limit (30/min) is exceeded.
|
|
1712
|
+
*/
|
|
1713
|
+
async registryLookup(country, q) {
|
|
1714
|
+
const normalizedCountry = this.normalizeLookupCountry(country);
|
|
1715
|
+
const query = q.trim();
|
|
1716
|
+
if (query === "") {
|
|
1717
|
+
throw new TaxoraException("q must not be empty.");
|
|
1718
|
+
}
|
|
1719
|
+
if (normalizedCountry === "NO") {
|
|
1720
|
+
const digits = NorwegianOrgNumber.normalize(query);
|
|
1721
|
+
if (/^\d{9}$/.test(digits) && !NorwegianOrgNumber.isValid(digits)) {
|
|
1722
|
+
throw new TaxoraException(`Invalid Norwegian organisation number "${query}" (check digit).`);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
const params = new URLSearchParams();
|
|
1726
|
+
params.set("country", normalizedCountry);
|
|
1727
|
+
params.set("q", query);
|
|
1728
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/registry-lookup?${params.toString()}`);
|
|
1729
|
+
const data = await this.parseJsonResponse(response);
|
|
1730
|
+
const results = Array.isArray(data["results"]) ? data["results"] : [];
|
|
1731
|
+
return results.filter((row) => typeof row === "object" && row !== null && !Array.isArray(row)).map((row) => RegistryCompany.fromArray(row));
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Check whether a participant (typically the buyer) can receive e-invoices
|
|
1735
|
+
* over Peppol. For Norway pass the organisation number (scheme defaults to
|
|
1736
|
+
* "0192"): `reachable === true` means the buyer is registered in ELMA and must
|
|
1737
|
+
* receive an EHF e-invoice. The directory resolves asynchronously —
|
|
1738
|
+
* `status === 'pending'` (`reachable === null`) means ask again in a few
|
|
1739
|
+
* seconds. Free of charge; definite answers are cached for 24h server-side.
|
|
1740
|
+
*
|
|
1741
|
+
* A Norwegian organisation number is checked (mod-11) and normalized before
|
|
1742
|
+
* sending. Errors: 422 (ValidationException) on invalid input, 502
|
|
1743
|
+
* (HttpException) on provider failure.
|
|
1744
|
+
*/
|
|
1745
|
+
async peppolLookup(country, id, scheme) {
|
|
1746
|
+
const normalizedCountry = this.normalizeLookupCountry(country);
|
|
1747
|
+
let participantId = id.trim();
|
|
1748
|
+
if (participantId === "") {
|
|
1749
|
+
throw new TaxoraException("id must not be empty.");
|
|
1750
|
+
}
|
|
1751
|
+
if (scheme !== void 0 && !PEPPOL_SCHEME_REGEX.test(scheme)) {
|
|
1752
|
+
throw new TaxoraException('scheme must be a 4-digit Peppol identifier scheme, e.g. "0192".');
|
|
1753
|
+
}
|
|
1754
|
+
if (normalizedCountry === "NO" && (scheme === void 0 || scheme === NorwegianOrgNumber.PEPPOL_SCHEME)) {
|
|
1755
|
+
if (!NorwegianOrgNumber.isValid(participantId)) {
|
|
1756
|
+
throw new TaxoraException(`Invalid Norwegian organisation number "${participantId}".`);
|
|
1757
|
+
}
|
|
1758
|
+
participantId = NorwegianOrgNumber.normalize(participantId);
|
|
1759
|
+
}
|
|
1760
|
+
const params = new URLSearchParams();
|
|
1761
|
+
params.set("country", normalizedCountry);
|
|
1762
|
+
params.set("id", participantId);
|
|
1763
|
+
if (scheme !== void 0) params.set("scheme", scheme);
|
|
1764
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/peppol-lookup?${params.toString()}`);
|
|
1765
|
+
const data = await this.parseJsonResponse(response);
|
|
1766
|
+
return PeppolLookupResult.fromArray(data);
|
|
1767
|
+
}
|
|
1099
1768
|
// ---------------------------------------------------------------------
|
|
1100
1769
|
// Transactions
|
|
1101
1770
|
// ---------------------------------------------------------------------
|
|
@@ -1119,6 +1788,7 @@ var EReportingEndpoint = class {
|
|
|
1119
1788
|
this.validateInvoiceNumber(input.invoiceNumber);
|
|
1120
1789
|
this.validateInvoiceLines(input.invoiceLines);
|
|
1121
1790
|
this.validateOptionalCodes(input);
|
|
1791
|
+
this.validateBuyerFields(input);
|
|
1122
1792
|
const body = {
|
|
1123
1793
|
compliance_enrollment_id: input.complianceEnrollmentId,
|
|
1124
1794
|
transaction_type: input.transactionType,
|
|
@@ -1134,13 +1804,23 @@ var EReportingEndpoint = class {
|
|
|
1134
1804
|
const data = await this.parseJsonResponse(response);
|
|
1135
1805
|
return ComplianceTransaction.fromArray(data);
|
|
1136
1806
|
}
|
|
1137
|
-
/** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type. */
|
|
1807
|
+
/** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type / enrollment / country. */
|
|
1138
1808
|
async listTransactions(filters = {}) {
|
|
1809
|
+
if (filters.complianceEnrollmentId !== void 0) {
|
|
1810
|
+
this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
|
|
1811
|
+
}
|
|
1812
|
+
if (filters.country !== void 0 && !COUNTRY_REGEX.test(filters.country)) {
|
|
1813
|
+
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1814
|
+
}
|
|
1139
1815
|
const params = new URLSearchParams();
|
|
1140
1816
|
if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
|
|
1141
1817
|
if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
|
|
1142
1818
|
if (filters.state !== void 0) params.set("state", filters.state);
|
|
1143
1819
|
if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
|
|
1820
|
+
if (filters.complianceEnrollmentId !== void 0) {
|
|
1821
|
+
params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
|
|
1822
|
+
}
|
|
1823
|
+
if (filters.country !== void 0) params.set("country", filters.country.toUpperCase());
|
|
1144
1824
|
if (filters.page !== void 0) params.set("page", String(filters.page));
|
|
1145
1825
|
if (filters.perPage !== void 0) params.set("per_page", String(filters.perPage));
|
|
1146
1826
|
const queryString = params.toString();
|
|
@@ -1162,6 +1842,7 @@ var EReportingEndpoint = class {
|
|
|
1162
1842
|
if (input.invoiceNumber !== void 0) this.validateInvoiceNumber(input.invoiceNumber);
|
|
1163
1843
|
if (input.invoiceLines !== void 0) this.validateInvoiceLines(input.invoiceLines);
|
|
1164
1844
|
this.validateOptionalCodes(input);
|
|
1845
|
+
this.validateBuyerFields(input);
|
|
1165
1846
|
const body = {};
|
|
1166
1847
|
if (input.transactionType !== void 0) body["transaction_type"] = input.transactionType;
|
|
1167
1848
|
if (input.invoiceNumber !== void 0) body["invoice_number"] = input.invoiceNumber;
|
|
@@ -1284,6 +1965,14 @@ var EReportingEndpoint = class {
|
|
|
1284
1965
|
* returned as HTTP 400 (surfaced as HttpException), not 422.
|
|
1285
1966
|
*/
|
|
1286
1967
|
async requestEReportingAccess(input = {}) {
|
|
1968
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1969
|
+
if (input.countries !== void 0) {
|
|
1970
|
+
input.countries.forEach((country, index) => {
|
|
1971
|
+
if (!COUNTRY_REGEX.test(country)) {
|
|
1972
|
+
throw new TaxoraException(`countries[${index}] must be a 2-character ISO 3166-1 alpha-2 code.`);
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1287
1976
|
const body = {};
|
|
1288
1977
|
if (input.name !== void 0) body["name"] = input.name;
|
|
1289
1978
|
if (input.email !== void 0) body["email"] = input.email;
|
|
@@ -1291,6 +1980,8 @@ var EReportingEndpoint = class {
|
|
|
1291
1980
|
if (input.phone !== void 0) body["phone"] = input.phone;
|
|
1292
1981
|
if (input.message !== void 0) body["message"] = input.message;
|
|
1293
1982
|
if (input.language !== void 0) body["language"] = input.language;
|
|
1983
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1984
|
+
if (input.countries !== void 0) body["countries"] = input.countries.map((c) => c.toUpperCase());
|
|
1294
1985
|
const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/e-reporting-request`, body);
|
|
1295
1986
|
await this.parseJsonResponse(response);
|
|
1296
1987
|
}
|
|
@@ -1326,6 +2017,73 @@ var EReportingEndpoint = class {
|
|
|
1326
2017
|
if (input.country !== void 0 && input.country.length !== 2) {
|
|
1327
2018
|
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1328
2019
|
}
|
|
2020
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
2021
|
+
}
|
|
2022
|
+
isNorwayEnrollmentInput(input) {
|
|
2023
|
+
return typeof input.country === "string" && input.country.trim().toUpperCase() === "NO";
|
|
2024
|
+
}
|
|
2025
|
+
/** Validates the Norway input and builds its wire body (org number normalized to 9 digits). */
|
|
2026
|
+
buildNorwayEnrollmentBody(input) {
|
|
2027
|
+
if (typeof input.orgNumber !== "string" || !NorwegianOrgNumber.isValid(input.orgNumber)) {
|
|
2028
|
+
throw new TaxoraException(
|
|
2029
|
+
"orgNumber must be a valid 9-digit Norwegian organisation number (mod-11 check digit)."
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
for (const field of ["companyName", "address", "city", "postalcode"]) {
|
|
2033
|
+
if (typeof input[field] !== "string" || input[field].trim() === "") {
|
|
2034
|
+
throw new TaxoraException(`${field} is required for a Norway enrollment.`);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
this.validateEmail(input.email, "email");
|
|
2038
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
2039
|
+
if (input.legalCountry !== void 0 && !COUNTRY_REGEX.test(input.legalCountry)) {
|
|
2040
|
+
throw new TaxoraException("legalCountry must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
2041
|
+
}
|
|
2042
|
+
const body = {
|
|
2043
|
+
country: "NO",
|
|
2044
|
+
org_number: NorwegianOrgNumber.normalize(input.orgNumber),
|
|
2045
|
+
company_name: input.companyName,
|
|
2046
|
+
address: input.address,
|
|
2047
|
+
city: input.city,
|
|
2048
|
+
postalcode: input.postalcode,
|
|
2049
|
+
email: input.email
|
|
2050
|
+
};
|
|
2051
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
2052
|
+
if (input.regime !== void 0) body["regime"] = input.regime;
|
|
2053
|
+
if (input.vatNumber !== void 0) body["vat_number"] = input.vatNumber;
|
|
2054
|
+
if (input.province !== void 0) body["province"] = input.province;
|
|
2055
|
+
if (input.vatRegistered !== void 0) body["vat_registered"] = input.vatRegistered;
|
|
2056
|
+
if (input.enterpriseRegister !== void 0) body["enterprise_register"] = input.enterpriseRegister;
|
|
2057
|
+
if (input.reception !== void 0) body["reception"] = input.reception;
|
|
2058
|
+
if (input.legalCountry !== void 0) body["legal_country"] = input.legalCountry.toUpperCase();
|
|
2059
|
+
if (input.reportingStartDate !== void 0) {
|
|
2060
|
+
body["reporting_start_date"] = this.formatDate(input.reportingStartDate);
|
|
2061
|
+
}
|
|
2062
|
+
if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
|
|
2063
|
+
return body;
|
|
2064
|
+
}
|
|
2065
|
+
async createEnrollmentFromBody(body) {
|
|
2066
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/enrollments`, body);
|
|
2067
|
+
const data = await this.parseJsonResponse(response);
|
|
2068
|
+
return ComplianceEnrollment.fromArray(data);
|
|
2069
|
+
}
|
|
2070
|
+
validateService(service) {
|
|
2071
|
+
if (!SERVICES.includes(service)) {
|
|
2072
|
+
throw new TaxoraException(`Invalid service "${String(service)}". Expected one of: ${SERVICES.join(", ")}.`);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
validateEmail(email, name) {
|
|
2076
|
+
if (typeof email !== "string" || email.trim() === "" || !email.includes("@")) {
|
|
2077
|
+
throw new TaxoraException(`${name} must be a valid email address.`);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
/** Lookup routes take a 2-letter country; the API expects it upper-case. */
|
|
2081
|
+
normalizeLookupCountry(country) {
|
|
2082
|
+
const normalized = country.trim().toUpperCase();
|
|
2083
|
+
if (!COUNTRY_REGEX.test(normalized)) {
|
|
2084
|
+
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
2085
|
+
}
|
|
2086
|
+
return normalized;
|
|
1329
2087
|
}
|
|
1330
2088
|
validateId(id, name) {
|
|
1331
2089
|
if (!Number.isInteger(id) || id <= 0) {
|
|
@@ -1365,6 +2123,33 @@ var EReportingEndpoint = class {
|
|
|
1365
2123
|
throw new TaxoraException("counterpartyCountry must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1366
2124
|
}
|
|
1367
2125
|
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Format checks for the buyer/document fields. Country rules (e.g. the Norway
|
|
2128
|
+
* org number being required) depend on the enrollment and stay server-side;
|
|
2129
|
+
* a register id is only check-digit validated when the buyer is Norwegian.
|
|
2130
|
+
*/
|
|
2131
|
+
validateBuyerFields(input) {
|
|
2132
|
+
if (input.counterpartyRegisterId !== void 0) {
|
|
2133
|
+
if (input.counterpartyRegisterId.length > 30) {
|
|
2134
|
+
throw new TaxoraException("counterpartyRegisterId must not exceed 30 characters.");
|
|
2135
|
+
}
|
|
2136
|
+
if (input.counterpartyCountry?.toUpperCase() === "NO" && !NorwegianOrgNumber.isValid(input.counterpartyRegisterId)) {
|
|
2137
|
+
throw new TaxoraException(
|
|
2138
|
+
"counterpartyRegisterId must be a valid 9-digit Norwegian organisation number (mod-11 check digit)."
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
if (input.counterpartyEmail !== void 0) this.validateEmail(input.counterpartyEmail, "counterpartyEmail");
|
|
2143
|
+
if (input.buyerReference !== void 0 && input.buyerReference.length > 100) {
|
|
2144
|
+
throw new TaxoraException("buyerReference must not exceed 100 characters.");
|
|
2145
|
+
}
|
|
2146
|
+
if (input.amendedNumber !== void 0 && input.amendedNumber.length > 50) {
|
|
2147
|
+
throw new TaxoraException("amendedNumber must not exceed 50 characters.");
|
|
2148
|
+
}
|
|
2149
|
+
if (input.extraInfo !== void 0 && input.extraInfo.length > 1e3) {
|
|
2150
|
+
throw new TaxoraException("extraInfo must not exceed 1000 characters.");
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
1368
2153
|
// ---------------------------------------------------------------------
|
|
1369
2154
|
// Body building (camelCase input → snake_case wire format)
|
|
1370
2155
|
// ---------------------------------------------------------------------
|
|
@@ -1401,6 +2186,16 @@ var EReportingEndpoint = class {
|
|
|
1401
2186
|
if (input.remittanceInformation !== void 0) body["remittance_information"] = input.remittanceInformation;
|
|
1402
2187
|
if (input.paymentMethodText !== void 0) body["payment_method_text"] = input.paymentMethodText;
|
|
1403
2188
|
if (input.paymentTerms !== void 0) body["payment_terms"] = input.paymentTerms;
|
|
2189
|
+
if (input.counterpartyRegisterId !== void 0) body["counterparty_register_id"] = input.counterpartyRegisterId;
|
|
2190
|
+
if (input.counterpartyAddress !== void 0) body["counterparty_address"] = input.counterpartyAddress;
|
|
2191
|
+
if (input.counterpartyCity !== void 0) body["counterparty_city"] = input.counterpartyCity;
|
|
2192
|
+
if (input.counterpartyPostalcode !== void 0) body["counterparty_postalcode"] = input.counterpartyPostalcode;
|
|
2193
|
+
if (input.counterpartyEmail !== void 0) body["counterparty_email"] = input.counterpartyEmail;
|
|
2194
|
+
if (input.buyerReference !== void 0) body["buyer_reference"] = input.buyerReference;
|
|
2195
|
+
if (input.isCreditNote !== void 0) body["is_credit_note"] = input.isCreditNote;
|
|
2196
|
+
if (input.amendedNumber !== void 0) body["amended_number"] = input.amendedNumber;
|
|
2197
|
+
if (input.amendedDate !== void 0) body["amended_date"] = this.formatDate(input.amendedDate);
|
|
2198
|
+
if (input.extraInfo !== void 0) body["extra_info"] = input.extraInfo;
|
|
1404
2199
|
}
|
|
1405
2200
|
// ---------------------------------------------------------------------
|
|
1406
2201
|
// HTTP plumbing (kept in sync with SmartEnrichmentEndpoint)
|
|
@@ -1421,7 +2216,8 @@ var EReportingEndpoint = class {
|
|
|
1421
2216
|
async sendRequest(method, url, body) {
|
|
1422
2217
|
const options = { headers: this.buildHeaders() };
|
|
1423
2218
|
if (body) options.body = JSON.stringify(body);
|
|
1424
|
-
|
|
2219
|
+
const send = () => this.httpClient.request(method, url, options);
|
|
2220
|
+
return method === "GET" ? withResponseRetries(this.retryPolicy, "e-reporting request", send) : send();
|
|
1425
2221
|
}
|
|
1426
2222
|
/** Multipart upload: Content-Type is left to fetch so the boundary is set correctly. */
|
|
1427
2223
|
async sendMultipartRequest(method, url, form) {
|
|
@@ -1434,16 +2230,22 @@ var EReportingEndpoint = class {
|
|
|
1434
2230
|
throw this.buildValidationException(responseText);
|
|
1435
2231
|
}
|
|
1436
2232
|
if (!response.ok) {
|
|
1437
|
-
throw new HttpException(
|
|
2233
|
+
throw new HttpException(
|
|
2234
|
+
describeApiError(responseText, response.status),
|
|
2235
|
+
response.status,
|
|
2236
|
+
responseText,
|
|
2237
|
+
{},
|
|
2238
|
+
response.headers.get("retry-after")
|
|
2239
|
+
);
|
|
1438
2240
|
}
|
|
1439
2241
|
let parsed;
|
|
1440
2242
|
try {
|
|
1441
2243
|
parsed = JSON.parse(responseText);
|
|
1442
2244
|
} catch {
|
|
1443
|
-
throw new HttpException("Invalid JSON response",
|
|
2245
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
1444
2246
|
}
|
|
1445
2247
|
if (typeof parsed !== "object" || parsed === null) {
|
|
1446
|
-
throw new HttpException("Unexpected response format",
|
|
2248
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
1447
2249
|
}
|
|
1448
2250
|
const obj = parsed;
|
|
1449
2251
|
if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
|
|
@@ -1461,7 +2263,13 @@ var EReportingEndpoint = class {
|
|
|
1461
2263
|
throw this.buildValidationException(responseText);
|
|
1462
2264
|
}
|
|
1463
2265
|
if (!response.ok) {
|
|
1464
|
-
throw new HttpException(
|
|
2266
|
+
throw new HttpException(
|
|
2267
|
+
describeApiError(responseText, response.status),
|
|
2268
|
+
response.status,
|
|
2269
|
+
responseText,
|
|
2270
|
+
{},
|
|
2271
|
+
response.headers.get("retry-after")
|
|
2272
|
+
);
|
|
1465
2273
|
}
|
|
1466
2274
|
}
|
|
1467
2275
|
/**
|
|
@@ -1505,6 +2313,29 @@ var EReportingEndpoint = class {
|
|
|
1505
2313
|
}
|
|
1506
2314
|
};
|
|
1507
2315
|
|
|
2316
|
+
// src/enums/SmartEnrichmentMode.ts
|
|
2317
|
+
var SmartEnrichmentMode = {
|
|
2318
|
+
DEFAULT: "default",
|
|
2319
|
+
COMPLEX: "complex"
|
|
2320
|
+
};
|
|
2321
|
+
var VALUES8 = Object.values(SmartEnrichmentMode);
|
|
2322
|
+
function toSmartEnrichmentMode(value) {
|
|
2323
|
+
if (typeof value === "string") {
|
|
2324
|
+
const normalized = value.trim().toLowerCase();
|
|
2325
|
+
if (VALUES8.includes(normalized)) {
|
|
2326
|
+
return normalized;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
return void 0;
|
|
2330
|
+
}
|
|
2331
|
+
var DESCRIPTIONS8 = {
|
|
2332
|
+
[SmartEnrichmentMode.DEFAULT]: "One provider searches; a second is consulted only if it finds nothing.",
|
|
2333
|
+
[SmartEnrichmentMode.COMPLEX]: "Two providers search independently and are cross-checked against the tax authority. Costs more."
|
|
2334
|
+
};
|
|
2335
|
+
function describeSmartEnrichmentMode(mode) {
|
|
2336
|
+
return DESCRIPTIONS8[mode];
|
|
2337
|
+
}
|
|
2338
|
+
|
|
1508
2339
|
// src/enums/SmartEnrichmentStatus.ts
|
|
1509
2340
|
var SmartEnrichmentStatus = {
|
|
1510
2341
|
FOUND: "found",
|
|
@@ -1516,17 +2347,17 @@ var SmartEnrichmentStatus = {
|
|
|
1516
2347
|
FAILED: "failed",
|
|
1517
2348
|
UNKNOWN: "unknown"
|
|
1518
2349
|
};
|
|
1519
|
-
var
|
|
2350
|
+
var VALUES9 = Object.values(SmartEnrichmentStatus);
|
|
1520
2351
|
function toSmartEnrichmentStatus(value) {
|
|
1521
2352
|
if (typeof value === "string") {
|
|
1522
2353
|
const normalized = value.trim().toLowerCase();
|
|
1523
|
-
if (
|
|
2354
|
+
if (VALUES9.includes(normalized)) {
|
|
1524
2355
|
return normalized;
|
|
1525
2356
|
}
|
|
1526
2357
|
}
|
|
1527
2358
|
return SmartEnrichmentStatus.UNKNOWN;
|
|
1528
2359
|
}
|
|
1529
|
-
var
|
|
2360
|
+
var DESCRIPTIONS9 = {
|
|
1530
2361
|
[SmartEnrichmentStatus.FOUND]: "A VAT number was resolved and confirmed.",
|
|
1531
2362
|
[SmartEnrichmentStatus.NO_VAT_EXISTS]: "The company was identified but has no VAT number.",
|
|
1532
2363
|
[SmartEnrichmentStatus.NOT_FOUND]: "No sufficiently-confident match could be resolved.",
|
|
@@ -1537,12 +2368,12 @@ var DESCRIPTIONS5 = {
|
|
|
1537
2368
|
[SmartEnrichmentStatus.UNKNOWN]: "Unknown status."
|
|
1538
2369
|
};
|
|
1539
2370
|
function describeSmartEnrichmentStatus(status) {
|
|
1540
|
-
return
|
|
2371
|
+
return DESCRIPTIONS9[status];
|
|
1541
2372
|
}
|
|
1542
2373
|
|
|
1543
2374
|
// src/dto/SmartEnrichmentResource.ts
|
|
1544
2375
|
var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
1545
|
-
constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source) {
|
|
2376
|
+
constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source, mode = void 0, providerVerdicts = [], addressQuality = void 0) {
|
|
1546
2377
|
this.status = status;
|
|
1547
2378
|
this.vatNumber = vatNumber;
|
|
1548
2379
|
this.vatType = vatType;
|
|
@@ -1551,6 +2382,9 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1551
2382
|
this.matchedAddress = matchedAddress;
|
|
1552
2383
|
this.country = country;
|
|
1553
2384
|
this.source = source;
|
|
2385
|
+
this.mode = mode;
|
|
2386
|
+
this.providerVerdicts = providerVerdicts;
|
|
2387
|
+
this.addressQuality = addressQuality;
|
|
1554
2388
|
}
|
|
1555
2389
|
status;
|
|
1556
2390
|
vatNumber;
|
|
@@ -1560,6 +2394,9 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1560
2394
|
matchedAddress;
|
|
1561
2395
|
country;
|
|
1562
2396
|
source;
|
|
2397
|
+
mode;
|
|
2398
|
+
providerVerdicts;
|
|
2399
|
+
addressQuality;
|
|
1563
2400
|
static fromArray(data) {
|
|
1564
2401
|
const str = (key) => {
|
|
1565
2402
|
const v = data[key];
|
|
@@ -1569,6 +2406,8 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1569
2406
|
const v = data[key];
|
|
1570
2407
|
return typeof v === "number" ? v : void 0;
|
|
1571
2408
|
};
|
|
2409
|
+
const verdicts = data["providerVerdicts"];
|
|
2410
|
+
const quality = data["addressQuality"];
|
|
1572
2411
|
return new _SmartEnrichmentResource(
|
|
1573
2412
|
toSmartEnrichmentStatus(data["status"]),
|
|
1574
2413
|
str("vatNumber"),
|
|
@@ -1577,7 +2416,10 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1577
2416
|
str("matchedCompanyName"),
|
|
1578
2417
|
str("matchedAddress"),
|
|
1579
2418
|
str("country"),
|
|
1580
|
-
str("source")
|
|
2419
|
+
str("source"),
|
|
2420
|
+
toSmartEnrichmentMode(data["mode"]),
|
|
2421
|
+
Array.isArray(verdicts) ? verdicts.filter((v) => typeof v === "object" && v !== null) : [],
|
|
2422
|
+
typeof quality === "object" && quality !== null ? quality : void 0
|
|
1581
2423
|
);
|
|
1582
2424
|
}
|
|
1583
2425
|
toArray() {
|
|
@@ -1589,7 +2431,10 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
|
|
|
1589
2431
|
matchedCompanyName: this.matchedCompanyName ?? null,
|
|
1590
2432
|
matchedAddress: this.matchedAddress ?? null,
|
|
1591
2433
|
country: this.country ?? null,
|
|
1592
|
-
source: this.source ?? null
|
|
2434
|
+
source: this.source ?? null,
|
|
2435
|
+
mode: this.mode ?? null,
|
|
2436
|
+
providerVerdicts: this.providerVerdicts,
|
|
2437
|
+
addressQuality: this.addressQuality ?? null
|
|
1593
2438
|
};
|
|
1594
2439
|
}
|
|
1595
2440
|
};
|
|
@@ -1741,13 +2586,13 @@ var SmartEnrichmentHistoryPage = class _SmartEnrichmentHistoryPage {
|
|
|
1741
2586
|
};
|
|
1742
2587
|
|
|
1743
2588
|
// src/dto/SmartEnrichmentStatistics.ts
|
|
1744
|
-
function
|
|
2589
|
+
function asString9(value, fallback = "") {
|
|
1745
2590
|
return typeof value === "string" ? value : fallback;
|
|
1746
2591
|
}
|
|
1747
2592
|
function asNumber6(value) {
|
|
1748
2593
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
1749
2594
|
}
|
|
1750
|
-
function
|
|
2595
|
+
function asRecord2(value) {
|
|
1751
2596
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1752
2597
|
}
|
|
1753
2598
|
function asRecords2(value) {
|
|
@@ -1770,32 +2615,32 @@ var SmartEnrichmentStatistics = class _SmartEnrichmentStatistics {
|
|
|
1770
2615
|
byOutcome;
|
|
1771
2616
|
confidenceBuckets;
|
|
1772
2617
|
static fromArray(data) {
|
|
1773
|
-
const range =
|
|
1774
|
-
const totals =
|
|
2618
|
+
const range = asRecord2(data["range"]);
|
|
2619
|
+
const totals = asRecord2(data["totals"]);
|
|
1775
2620
|
const timeSeries = asRecords2(data["time_series"]).map((row) => ({
|
|
1776
|
-
bucket:
|
|
2621
|
+
bucket: asString9(row["bucket"]),
|
|
1777
2622
|
jobs: asNumber6(row["jobs"]),
|
|
1778
2623
|
items: asNumber6(row["items"]),
|
|
1779
2624
|
found: asNumber6(row["found"])
|
|
1780
2625
|
}));
|
|
1781
2626
|
const bySource = asRecords2(data["by_source"]).map((row) => ({
|
|
1782
|
-
source:
|
|
2627
|
+
source: asString9(row["source"]),
|
|
1783
2628
|
jobs: asNumber6(row["jobs"]),
|
|
1784
2629
|
found: asNumber6(row["found"])
|
|
1785
2630
|
}));
|
|
1786
2631
|
const byOutcome = asRecords2(data["by_outcome"]).map((row) => ({
|
|
1787
|
-
outcome:
|
|
2632
|
+
outcome: asString9(row["outcome"]),
|
|
1788
2633
|
count: asNumber6(row["count"])
|
|
1789
2634
|
}));
|
|
1790
2635
|
const confidenceBuckets = asRecords2(data["confidence_buckets"]).map((row) => ({
|
|
1791
|
-
bucket:
|
|
2636
|
+
bucket: asString9(row["bucket"]),
|
|
1792
2637
|
count: asNumber6(row["count"])
|
|
1793
2638
|
}));
|
|
1794
2639
|
return new _SmartEnrichmentStatistics(
|
|
1795
2640
|
{
|
|
1796
|
-
dateFrom:
|
|
1797
|
-
dateTo:
|
|
1798
|
-
interval:
|
|
2641
|
+
dateFrom: asString9(range["date_from"]),
|
|
2642
|
+
dateTo: asString9(range["date_to"]),
|
|
2643
|
+
interval: asString9(range["interval"])
|
|
1799
2644
|
},
|
|
1800
2645
|
{
|
|
1801
2646
|
jobs: asNumber6(totals["jobs"]),
|
|
@@ -1817,16 +2662,18 @@ var STATISTICS_INTERVALS = ["day", "week", "month"];
|
|
|
1817
2662
|
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
1818
2663
|
var DEFAULT_POLL_TIMEOUT_MS = 12e4;
|
|
1819
2664
|
var SmartEnrichmentEndpoint = class {
|
|
1820
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
2665
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
1821
2666
|
this.baseUrl = baseUrl;
|
|
1822
2667
|
this.apiKey = apiKey;
|
|
1823
2668
|
this.tokenStorage = tokenStorage;
|
|
1824
2669
|
this.httpClient = httpClient;
|
|
2670
|
+
this.retryPolicy = retryPolicy;
|
|
1825
2671
|
}
|
|
1826
2672
|
baseUrl;
|
|
1827
2673
|
apiKey;
|
|
1828
2674
|
tokenStorage;
|
|
1829
2675
|
httpClient;
|
|
2676
|
+
retryPolicy;
|
|
1830
2677
|
/**
|
|
1831
2678
|
* Single reverse lookup. Returns a confident result synchronously, or a `processing`
|
|
1832
2679
|
* job (poll with get(job.jobId) or waitForCompletion(job.jobId)) when deeper resolution
|
|
@@ -1841,6 +2688,7 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1841
2688
|
if (input.street !== void 0) body["street"] = input.street;
|
|
1842
2689
|
if (input.postalCode !== void 0) body["postalCode"] = input.postalCode;
|
|
1843
2690
|
if (input.city !== void 0) body["city"] = input.city;
|
|
2691
|
+
if (input.mode !== void 0) body["mode"] = input.mode;
|
|
1844
2692
|
const response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment`, body);
|
|
1845
2693
|
const data = await this.parseJsonResponse(response, [200, 202]);
|
|
1846
2694
|
return SmartEnrichmentJob.fromArray(data);
|
|
@@ -1848,13 +2696,18 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1848
2696
|
/**
|
|
1849
2697
|
* Bulk batch — always async. Returns a `processing` job; results arrive via the
|
|
1850
2698
|
* `enrichment.completed` webhook and are retrievable via get(job.jobId).
|
|
2699
|
+
*
|
|
2700
|
+
* @param mode Search depth for every row in the batch. A row carrying its own `mode` overrides
|
|
2701
|
+
* it, so a caller can pay for `complex` on just the rows that need it.
|
|
1851
2702
|
*/
|
|
1852
|
-
async bulkLookup(items) {
|
|
2703
|
+
async bulkLookup(items, mode) {
|
|
1853
2704
|
if (items.length === 0) {
|
|
1854
2705
|
throw new TaxoraException("bulkLookup requires at least one item.");
|
|
1855
2706
|
}
|
|
1856
2707
|
items.forEach((item, index) => this.validateInput(item, `items[${index}]`));
|
|
1857
|
-
const
|
|
2708
|
+
const body = { items };
|
|
2709
|
+
if (mode !== void 0) body["mode"] = mode;
|
|
2710
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment/bulk`, body);
|
|
1858
2711
|
const data = await this.parseJsonResponse(response, [200, 202]);
|
|
1859
2712
|
return SmartEnrichmentJob.fromArray(data);
|
|
1860
2713
|
}
|
|
@@ -1996,24 +2849,34 @@ var SmartEnrichmentEndpoint = class {
|
|
|
1996
2849
|
async sendRequest(method, url, body) {
|
|
1997
2850
|
const options = { headers: this.buildHeaders() };
|
|
1998
2851
|
if (body) options.body = JSON.stringify(body);
|
|
1999
|
-
|
|
2852
|
+
const send = () => this.httpClient.request(method, url, options);
|
|
2853
|
+
return method === "GET" ? withResponseRetries(this.retryPolicy, "Smart Enrichment request", send) : send();
|
|
2000
2854
|
}
|
|
2001
2855
|
async parseJsonResponse(response, acceptedStatuses = [200]) {
|
|
2002
2856
|
const responseText = await response.text();
|
|
2003
2857
|
if (response.status === 422) {
|
|
2004
|
-
throw new ValidationException(
|
|
2858
|
+
throw new ValidationException(
|
|
2859
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2860
|
+
this.parseValidationErrors(responseText)
|
|
2861
|
+
);
|
|
2005
2862
|
}
|
|
2006
2863
|
if (!response.ok && !acceptedStatuses.includes(response.status)) {
|
|
2007
|
-
throw new HttpException(
|
|
2864
|
+
throw new HttpException(
|
|
2865
|
+
describeApiError(responseText, response.status),
|
|
2866
|
+
response.status,
|
|
2867
|
+
responseText,
|
|
2868
|
+
{},
|
|
2869
|
+
response.headers.get("retry-after")
|
|
2870
|
+
);
|
|
2008
2871
|
}
|
|
2009
2872
|
let parsed;
|
|
2010
2873
|
try {
|
|
2011
2874
|
parsed = JSON.parse(responseText);
|
|
2012
2875
|
} catch {
|
|
2013
|
-
throw new HttpException("Invalid JSON response",
|
|
2876
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
2014
2877
|
}
|
|
2015
2878
|
if (typeof parsed !== "object" || parsed === null) {
|
|
2016
|
-
throw new HttpException("Unexpected response format",
|
|
2879
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
2017
2880
|
}
|
|
2018
2881
|
const obj = parsed;
|
|
2019
2882
|
if (obj["data"] !== void 0 && (typeof obj["data"] === "object" || Array.isArray(obj["data"]))) {
|
|
@@ -2029,19 +2892,28 @@ var SmartEnrichmentEndpoint = class {
|
|
|
2029
2892
|
async parseRawJson(response) {
|
|
2030
2893
|
const responseText = await response.text();
|
|
2031
2894
|
if (response.status === 422) {
|
|
2032
|
-
throw new ValidationException(
|
|
2895
|
+
throw new ValidationException(
|
|
2896
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2897
|
+
this.parseValidationErrors(responseText)
|
|
2898
|
+
);
|
|
2033
2899
|
}
|
|
2034
2900
|
if (!response.ok) {
|
|
2035
|
-
throw new HttpException(
|
|
2901
|
+
throw new HttpException(
|
|
2902
|
+
describeApiError(responseText, response.status),
|
|
2903
|
+
response.status,
|
|
2904
|
+
responseText,
|
|
2905
|
+
{},
|
|
2906
|
+
response.headers.get("retry-after")
|
|
2907
|
+
);
|
|
2036
2908
|
}
|
|
2037
2909
|
let parsed;
|
|
2038
2910
|
try {
|
|
2039
2911
|
parsed = JSON.parse(responseText);
|
|
2040
2912
|
} catch {
|
|
2041
|
-
throw new HttpException("Invalid JSON response",
|
|
2913
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
2042
2914
|
}
|
|
2043
2915
|
if (typeof parsed !== "object" || parsed === null) {
|
|
2044
|
-
throw new HttpException("Unexpected response format",
|
|
2916
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
2045
2917
|
}
|
|
2046
2918
|
return parsed;
|
|
2047
2919
|
}
|
|
@@ -2052,10 +2924,19 @@ var SmartEnrichmentEndpoint = class {
|
|
|
2052
2924
|
async parseCsvResponse(response) {
|
|
2053
2925
|
const responseText = await response.text();
|
|
2054
2926
|
if (response.status === 422) {
|
|
2055
|
-
throw new ValidationException(
|
|
2927
|
+
throw new ValidationException(
|
|
2928
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2929
|
+
this.parseValidationErrors(responseText)
|
|
2930
|
+
);
|
|
2056
2931
|
}
|
|
2057
2932
|
if (!response.ok) {
|
|
2058
|
-
throw new HttpException(
|
|
2933
|
+
throw new HttpException(
|
|
2934
|
+
describeApiError(responseText, response.status),
|
|
2935
|
+
response.status,
|
|
2936
|
+
responseText,
|
|
2937
|
+
{},
|
|
2938
|
+
response.headers.get("retry-after")
|
|
2939
|
+
);
|
|
2059
2940
|
}
|
|
2060
2941
|
return responseText;
|
|
2061
2942
|
}
|
|
@@ -2092,14 +2973,14 @@ var VatState = {
|
|
|
2092
2973
|
function getFailedVatStates() {
|
|
2093
2974
|
return [VatState.INVALID, VatState.FRAUD];
|
|
2094
2975
|
}
|
|
2095
|
-
var
|
|
2976
|
+
var DESCRIPTIONS10 = {
|
|
2096
2977
|
[VatState.VALID]: "The VAT number is valid and active.",
|
|
2097
2978
|
[VatState.INVALID]: "The VAT number is invalid or not registered.",
|
|
2098
2979
|
[VatState.FRAUD]: "The VAT number has been flagged as potentially fraudulent.",
|
|
2099
2980
|
[VatState.UNKNOWN]: "The VAT number state could not be determined."
|
|
2100
2981
|
};
|
|
2101
2982
|
function describeVatState(state) {
|
|
2102
|
-
return
|
|
2983
|
+
return DESCRIPTIONS10[state];
|
|
2103
2984
|
}
|
|
2104
2985
|
|
|
2105
2986
|
// src/dto/CompanyAddress.ts
|
|
@@ -2564,16 +3445,18 @@ var VatValidationAddressInput = class _VatValidationAddressInput {
|
|
|
2564
3445
|
// src/endpoints/VatEndpoint.ts
|
|
2565
3446
|
var DATE_REGEX2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
2566
3447
|
var VatEndpoint = class {
|
|
2567
|
-
constructor(baseUrl, apiKey, tokenStorage, httpClient) {
|
|
3448
|
+
constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
|
|
2568
3449
|
this.baseUrl = baseUrl;
|
|
2569
3450
|
this.apiKey = apiKey;
|
|
2570
3451
|
this.tokenStorage = tokenStorage;
|
|
2571
3452
|
this.httpClient = httpClient;
|
|
3453
|
+
this.retryPolicy = retryPolicy;
|
|
2572
3454
|
}
|
|
2573
3455
|
baseUrl;
|
|
2574
3456
|
apiKey;
|
|
2575
3457
|
tokenStorage;
|
|
2576
3458
|
httpClient;
|
|
3459
|
+
retryPolicy;
|
|
2577
3460
|
async validate(vatUid, companyName, provider, addressInput) {
|
|
2578
3461
|
const body = { vat_uid: vatUid };
|
|
2579
3462
|
if (companyName) body["company_name"] = companyName;
|
|
@@ -2581,44 +3464,44 @@ var VatEndpoint = class {
|
|
|
2581
3464
|
if (addressInput) {
|
|
2582
3465
|
body["address_input"] = addressInput instanceof VatValidationAddressInput ? addressInput.toArray() : addressInput;
|
|
2583
3466
|
}
|
|
2584
|
-
return this.
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
if (attemptsLeft <= 1) {
|
|
2590
|
-
const text = await response.text();
|
|
2591
|
-
throw new HttpException("VAT validation timed out after multiple attempts", 504, text);
|
|
2592
|
-
}
|
|
2593
|
-
return this.validateWithGatewayRetry(body, attemptsLeft - 1);
|
|
2594
|
-
}
|
|
2595
|
-
const data = await this.parseJsonResponse(response);
|
|
2596
|
-
return VatResource.fromArray(data);
|
|
3467
|
+
return withRetries(this.retryPolicy, "VAT validation", async () => {
|
|
3468
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate`, body);
|
|
3469
|
+
const data = await this.parseJsonResponse(response);
|
|
3470
|
+
return VatResource.fromArray(data);
|
|
3471
|
+
});
|
|
2597
3472
|
}
|
|
2598
3473
|
async validateMultiple(vatUids, companyNames, provider) {
|
|
2599
3474
|
const body = { vat_uids: vatUids };
|
|
2600
3475
|
if (companyNames) body["company_names"] = companyNames;
|
|
2601
3476
|
if (provider) body["source"] = provider;
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
3477
|
+
return withRetries(this.retryPolicy, "VAT validation", async () => {
|
|
3478
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/multiple`, body);
|
|
3479
|
+
const data = await this.parseJsonResponse(response);
|
|
3480
|
+
return VatCollection.fromResponse(data);
|
|
3481
|
+
});
|
|
2605
3482
|
}
|
|
2606
3483
|
async validateSchema(vatUid) {
|
|
2607
3484
|
const body = { vat_uid: vatUid };
|
|
2608
|
-
|
|
2609
|
-
|
|
3485
|
+
return withRetries(this.retryPolicy, "VAT schema check", async () => {
|
|
3486
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/schema`, body);
|
|
3487
|
+
return this.parseJsonResponse(response);
|
|
3488
|
+
});
|
|
2610
3489
|
}
|
|
2611
3490
|
async state(vatUid) {
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
3491
|
+
return withRetries(this.retryPolicy, "VAT state lookup", async () => {
|
|
3492
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/vat/state/${encodeURIComponent(vatUid)}`);
|
|
3493
|
+
const data = await this.parseJsonResponse(response);
|
|
3494
|
+
return VatResource.fromArray(data);
|
|
3495
|
+
});
|
|
2615
3496
|
}
|
|
2616
3497
|
async history(vatUid) {
|
|
2617
3498
|
let url = `${this.baseUrl}/vat/history`;
|
|
2618
3499
|
if (vatUid) url += `?vat_uid=${encodeURIComponent(vatUid)}`;
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
3500
|
+
return withRetries(this.retryPolicy, "VAT history request", async () => {
|
|
3501
|
+
const response = await this.sendRequest("GET", url);
|
|
3502
|
+
const data = await this.parseJsonResponse(response);
|
|
3503
|
+
return VatCollection.fromResponse(data);
|
|
3504
|
+
});
|
|
2622
3505
|
}
|
|
2623
3506
|
async search(term, perPage) {
|
|
2624
3507
|
const params = new URLSearchParams();
|
|
@@ -2626,16 +3509,20 @@ var VatEndpoint = class {
|
|
|
2626
3509
|
if (perPage !== void 0) params.set("per_page", String(perPage));
|
|
2627
3510
|
const queryString = params.toString();
|
|
2628
3511
|
const url = `${this.baseUrl}/vat/search${queryString ? `?${queryString}` : ""}`;
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
3512
|
+
return withRetries(this.retryPolicy, "VAT search", async () => {
|
|
3513
|
+
const response = await this.sendRequest("GET", url);
|
|
3514
|
+
const data = await this.parseJsonResponse(response);
|
|
3515
|
+
return VatCollection.fromResponse(data);
|
|
3516
|
+
});
|
|
2632
3517
|
}
|
|
2633
3518
|
async certificate(uuid, lang) {
|
|
2634
3519
|
let url = `${this.baseUrl}/vat/certificate/${encodeURIComponent(uuid)}`;
|
|
2635
3520
|
if (lang) url += `?lang=${lang}`;
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
3521
|
+
return withRetries(this.retryPolicy, "VAT certificate download", async () => {
|
|
3522
|
+
const response = await this.sendBinaryRequest("GET", url);
|
|
3523
|
+
const buffer = await response.arrayBuffer();
|
|
3524
|
+
return new Uint8Array(buffer);
|
|
3525
|
+
});
|
|
2639
3526
|
}
|
|
2640
3527
|
async certificatesBulkExport(fromDate, toDate, countries, lang) {
|
|
2641
3528
|
return this.exportRequest(`${this.baseUrl}/vat/certificates/bulk-export`, fromDate, toDate, countries, lang);
|
|
@@ -2659,9 +3546,11 @@ var VatEndpoint = class {
|
|
|
2659
3546
|
}
|
|
2660
3547
|
async downloadBulkExport(exportId) {
|
|
2661
3548
|
const url = `${this.baseUrl}/vat/certificates/bulk-export/${encodeURIComponent(exportId)}/download`;
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
3549
|
+
return withRetries(this.retryPolicy, "VAT export download", async () => {
|
|
3550
|
+
const response = await this.sendBinaryRequest("GET", url);
|
|
3551
|
+
const buffer = await response.arrayBuffer();
|
|
3552
|
+
return new Uint8Array(buffer);
|
|
3553
|
+
});
|
|
2665
3554
|
}
|
|
2666
3555
|
buildHeaders() {
|
|
2667
3556
|
const headers = {
|
|
@@ -2685,7 +3574,13 @@ var VatEndpoint = class {
|
|
|
2685
3574
|
const response = await this.httpClient.request(method, url, { headers });
|
|
2686
3575
|
if (!response.ok) {
|
|
2687
3576
|
const text = await response.text();
|
|
2688
|
-
throw new HttpException(
|
|
3577
|
+
throw new HttpException(
|
|
3578
|
+
describeApiError(text, response.status),
|
|
3579
|
+
response.status,
|
|
3580
|
+
text,
|
|
3581
|
+
{},
|
|
3582
|
+
response.headers.get("retry-after")
|
|
3583
|
+
);
|
|
2689
3584
|
}
|
|
2690
3585
|
return response;
|
|
2691
3586
|
}
|
|
@@ -2700,19 +3595,25 @@ var VatEndpoint = class {
|
|
|
2700
3595
|
}
|
|
2701
3596
|
} catch {
|
|
2702
3597
|
}
|
|
2703
|
-
throw new ValidationException("Validation failed", errors);
|
|
3598
|
+
throw new ValidationException(jsonErrorMessage(responseText) ?? "Validation failed", errors);
|
|
2704
3599
|
}
|
|
2705
3600
|
if (!response.ok && !acceptedStatuses.includes(response.status)) {
|
|
2706
|
-
throw new HttpException(
|
|
3601
|
+
throw new HttpException(
|
|
3602
|
+
describeApiError(responseText, response.status),
|
|
3603
|
+
response.status,
|
|
3604
|
+
responseText,
|
|
3605
|
+
{},
|
|
3606
|
+
response.headers.get("retry-after")
|
|
3607
|
+
);
|
|
2707
3608
|
}
|
|
2708
3609
|
let parsed;
|
|
2709
3610
|
try {
|
|
2710
3611
|
parsed = JSON.parse(responseText);
|
|
2711
3612
|
} catch {
|
|
2712
|
-
throw new HttpException("Invalid JSON response",
|
|
3613
|
+
throw new HttpException("Invalid JSON response", response.status, responseText);
|
|
2713
3614
|
}
|
|
2714
3615
|
if (typeof parsed !== "object" || parsed === null) {
|
|
2715
|
-
throw new HttpException("Unexpected response format",
|
|
3616
|
+
throw new HttpException("Unexpected response format", response.status, responseText);
|
|
2716
3617
|
}
|
|
2717
3618
|
const obj = parsed;
|
|
2718
3619
|
if (obj["data"] !== void 0 && (typeof obj["data"] === "object" || Array.isArray(obj["data"]))) {
|
|
@@ -2811,7 +3712,7 @@ var AuthRetryHttpClient = class {
|
|
|
2811
3712
|
};
|
|
2812
3713
|
|
|
2813
3714
|
// src/version.ts
|
|
2814
|
-
var SDK_VERSION = "1.
|
|
3715
|
+
var SDK_VERSION = "1.3.0";
|
|
2815
3716
|
|
|
2816
3717
|
// src/http/SdkVersionHttpClient.ts
|
|
2817
3718
|
var SDK_VERSION_HEADER = "X-Taxora-SDK-Version";
|
|
@@ -2843,14 +3744,21 @@ var TaxoraClient = class {
|
|
|
2843
3744
|
const environment = options.environment ?? Environment.SANDBOX;
|
|
2844
3745
|
const tokenStorage = options.tokenStorage ?? new InMemoryTokenStorage();
|
|
2845
3746
|
const httpClient = options.httpClient ?? new FetchHttpClient();
|
|
3747
|
+
const retryPolicy = options.retryPolicy ?? new RetryPolicy();
|
|
2846
3748
|
const versionedClient = new SdkVersionHttpClient(httpClient);
|
|
2847
3749
|
this.baseUrl = getBaseUrl(environment);
|
|
2848
|
-
this.auth = new AuthEndpoint(this.baseUrl, options.apiKey, tokenStorage, versionedClient);
|
|
3750
|
+
this.auth = new AuthEndpoint(this.baseUrl, options.apiKey, tokenStorage, versionedClient, retryPolicy);
|
|
2849
3751
|
const retryClient = new AuthRetryHttpClient(versionedClient, tokenStorage, () => this.auth.refresh());
|
|
2850
|
-
this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
|
|
2851
|
-
this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
|
|
2852
|
-
this.smartEnrichment = new SmartEnrichmentEndpoint(
|
|
2853
|
-
|
|
3752
|
+
this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3753
|
+
this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3754
|
+
this.smartEnrichment = new SmartEnrichmentEndpoint(
|
|
3755
|
+
this.baseUrl,
|
|
3756
|
+
options.apiKey,
|
|
3757
|
+
tokenStorage,
|
|
3758
|
+
retryClient,
|
|
3759
|
+
retryPolicy
|
|
3760
|
+
);
|
|
3761
|
+
this.eReporting = new EReportingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
2854
3762
|
}
|
|
2855
3763
|
};
|
|
2856
3764
|
|
|
@@ -2871,6 +3779,125 @@ var Language = {
|
|
|
2871
3779
|
ENGLISH: "en",
|
|
2872
3780
|
GERMAN: "de"
|
|
2873
3781
|
};
|
|
3782
|
+
|
|
3783
|
+
// src/webhooks/WebhookSignature.ts
|
|
3784
|
+
var import_node_crypto = require("crypto");
|
|
3785
|
+
var WEBHOOK_SIGNATURE_HEADER = "Taxora-Signature";
|
|
3786
|
+
var WEBHOOK_ID_HEADER = "Taxora-Webhook-Id";
|
|
3787
|
+
var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
3788
|
+
var SCHEME = "v1";
|
|
3789
|
+
var WebhookSignatureVerificationError = class extends TaxoraException {
|
|
3790
|
+
reason;
|
|
3791
|
+
constructor(message, reason, context = {}) {
|
|
3792
|
+
super(message, { ...context, reason });
|
|
3793
|
+
this.name = "WebhookSignatureVerificationError";
|
|
3794
|
+
this.reason = reason;
|
|
3795
|
+
}
|
|
3796
|
+
};
|
|
3797
|
+
function parseWebhookSignatureHeader(header) {
|
|
3798
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
3799
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
3800
|
+
throw new WebhookSignatureVerificationError(
|
|
3801
|
+
`Missing ${WEBHOOK_SIGNATURE_HEADER} header. Is signing enabled for this webhook endpoint?`,
|
|
3802
|
+
"missing_header"
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
let timestamp = null;
|
|
3806
|
+
const signatures = [];
|
|
3807
|
+
for (const part of value.split(",")) {
|
|
3808
|
+
const separator = part.indexOf("=");
|
|
3809
|
+
if (separator === -1) continue;
|
|
3810
|
+
const key = part.slice(0, separator).trim();
|
|
3811
|
+
const val = part.slice(separator + 1).trim();
|
|
3812
|
+
if (key === "t" && /^\d+$/.test(val)) {
|
|
3813
|
+
timestamp = Number(val);
|
|
3814
|
+
} else if (key === SCHEME && /^[0-9a-f]{64}$/i.test(val)) {
|
|
3815
|
+
signatures.push(val.toLowerCase());
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
if (timestamp === null || signatures.length === 0) {
|
|
3819
|
+
throw new WebhookSignatureVerificationError(
|
|
3820
|
+
`Malformed ${WEBHOOK_SIGNATURE_HEADER} header \u2014 expected "t=<unix>,v1=<hex>".`,
|
|
3821
|
+
"malformed_header"
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
return { timestamp, signatures };
|
|
3825
|
+
}
|
|
3826
|
+
function computeWebhookSignature(rawBody, timestamp, secret) {
|
|
3827
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(`${timestamp}.`).update(toBytes(rawBody)).digest("hex");
|
|
3828
|
+
}
|
|
3829
|
+
function generateWebhookSignatureHeader(rawBody, secret, timestamp = Math.floor(Date.now() / 1e3)) {
|
|
3830
|
+
const secrets = normalizeSecrets(secret);
|
|
3831
|
+
return [`t=${timestamp}`, ...secrets.map((s) => `${SCHEME}=${computeWebhookSignature(rawBody, timestamp, s)}`)].join(
|
|
3832
|
+
","
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
function verifyWebhookSignature(rawBody, header, secret, options = {}) {
|
|
3836
|
+
const secrets = normalizeSecrets(secret);
|
|
3837
|
+
const bytes = toBytes(rawBody);
|
|
3838
|
+
const { timestamp, signatures } = parseWebhookSignatureHeader(header);
|
|
3839
|
+
const tolerance = options.tolerance ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
3840
|
+
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
3841
|
+
if (tolerance > 0 && Math.abs(now - timestamp) > tolerance) {
|
|
3842
|
+
throw new WebhookSignatureVerificationError(
|
|
3843
|
+
`Timestamp outside the tolerance window (${tolerance}s). Possible replay \u2014 or check the server clock.`,
|
|
3844
|
+
"timestamp_outside_tolerance",
|
|
3845
|
+
{ timestamp, now, tolerance }
|
|
3846
|
+
);
|
|
3847
|
+
}
|
|
3848
|
+
const received = signatures.map((s) => Buffer.from(s, "hex"));
|
|
3849
|
+
for (const s of secrets) {
|
|
3850
|
+
const expected = Buffer.from(computeWebhookSignature(bytes, timestamp, s), "hex");
|
|
3851
|
+
let matched = false;
|
|
3852
|
+
for (const candidate of received) {
|
|
3853
|
+
if (candidate.length === expected.length && (0, import_node_crypto.timingSafeEqual)(candidate, expected)) {
|
|
3854
|
+
matched = true;
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
if (matched) return true;
|
|
3858
|
+
}
|
|
3859
|
+
throw new WebhookSignatureVerificationError(
|
|
3860
|
+
'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.',
|
|
3861
|
+
"no_matching_signature"
|
|
3862
|
+
);
|
|
3863
|
+
}
|
|
3864
|
+
function constructWebhookEvent(rawBody, header, secret, options = {}) {
|
|
3865
|
+
verifyWebhookSignature(rawBody, header, secret, options);
|
|
3866
|
+
let parsed;
|
|
3867
|
+
try {
|
|
3868
|
+
parsed = JSON.parse(Buffer.from(toBytes(rawBody)).toString("utf8"));
|
|
3869
|
+
} catch {
|
|
3870
|
+
throw new WebhookSignatureVerificationError("Webhook payload is not valid JSON.", "invalid_payload");
|
|
3871
|
+
}
|
|
3872
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.id !== "string" || typeof parsed.event !== "string" || typeof parsed.data !== "object") {
|
|
3873
|
+
throw new WebhookSignatureVerificationError(
|
|
3874
|
+
"Webhook payload does not have the expected { id, event, timestamp, data } shape.",
|
|
3875
|
+
"invalid_payload"
|
|
3876
|
+
);
|
|
3877
|
+
}
|
|
3878
|
+
return parsed;
|
|
3879
|
+
}
|
|
3880
|
+
function normalizeSecrets(secret) {
|
|
3881
|
+
const list = (typeof secret === "string" ? [secret] : [...secret]).filter(
|
|
3882
|
+
(s) => typeof s === "string" && s !== ""
|
|
3883
|
+
);
|
|
3884
|
+
if (list.length === 0) {
|
|
3885
|
+
throw new WebhookSignatureVerificationError(
|
|
3886
|
+
"No webhook signing secret given. Copy it from the Taxora dashboard (Developer \u2192 Webhooks).",
|
|
3887
|
+
"missing_secret"
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
return list;
|
|
3891
|
+
}
|
|
3892
|
+
function toBytes(rawBody) {
|
|
3893
|
+
if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
|
|
3894
|
+
if (rawBody instanceof Uint8Array) return rawBody;
|
|
3895
|
+
if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
|
|
3896
|
+
throw new WebhookSignatureVerificationError(
|
|
3897
|
+
"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.",
|
|
3898
|
+
"invalid_body"
|
|
3899
|
+
);
|
|
3900
|
+
}
|
|
2874
3901
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2875
3902
|
0 && (module.exports = {
|
|
2876
3903
|
ApiVersion,
|
|
@@ -2879,9 +3906,11 @@ var Language = {
|
|
|
2879
3906
|
AuthenticationException,
|
|
2880
3907
|
CompanyAddress,
|
|
2881
3908
|
CompanyEndpoint,
|
|
3909
|
+
ComplianceDocumentType,
|
|
2882
3910
|
ComplianceEnrollment,
|
|
2883
3911
|
ComplianceEnrollmentPage,
|
|
2884
3912
|
ComplianceEnrollmentStatus,
|
|
3913
|
+
ComplianceService,
|
|
2885
3914
|
ComplianceTaxReport,
|
|
2886
3915
|
ComplianceTaxReportPage,
|
|
2887
3916
|
ComplianceTaxReportState,
|
|
@@ -2889,6 +3918,7 @@ var Language = {
|
|
|
2889
3918
|
ComplianceTransactionPage,
|
|
2890
3919
|
ComplianceTransactionState,
|
|
2891
3920
|
ComplianceTransactionType,
|
|
3921
|
+
DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
|
|
2892
3922
|
EReportingEndpoint,
|
|
2893
3923
|
Environment,
|
|
2894
3924
|
FetchHttpClient,
|
|
@@ -2897,8 +3927,13 @@ var Language = {
|
|
|
2897
3927
|
InMemoryTokenStorage,
|
|
2898
3928
|
Language,
|
|
2899
3929
|
LoginIdentifier,
|
|
3930
|
+
NorwegianOrgNumber,
|
|
3931
|
+
PeppolLookupResult,
|
|
3932
|
+
PeppolLookupStatus,
|
|
2900
3933
|
ProviderDocument,
|
|
2901
3934
|
ProviderDocumentLine,
|
|
3935
|
+
RegistryCompany,
|
|
3936
|
+
RetryPolicy,
|
|
2902
3937
|
RevenueStatistics,
|
|
2903
3938
|
ScoreBreakdown,
|
|
2904
3939
|
SireneLookupResult,
|
|
@@ -2906,6 +3941,7 @@ var Language = {
|
|
|
2906
3941
|
SmartEnrichmentHistoryPage,
|
|
2907
3942
|
SmartEnrichmentHistoryRow,
|
|
2908
3943
|
SmartEnrichmentJob,
|
|
3944
|
+
SmartEnrichmentMode,
|
|
2909
3945
|
SmartEnrichmentResource,
|
|
2910
3946
|
SmartEnrichmentStatistics,
|
|
2911
3947
|
SmartEnrichmentStatus,
|
|
@@ -2921,18 +3957,35 @@ var Language = {
|
|
|
2921
3957
|
VatResource,
|
|
2922
3958
|
VatState,
|
|
2923
3959
|
VatValidationAddressInput,
|
|
3960
|
+
WEBHOOK_ID_HEADER,
|
|
3961
|
+
WEBHOOK_SIGNATURE_HEADER,
|
|
3962
|
+
WebhookSignatureVerificationError,
|
|
3963
|
+
computeWebhookSignature,
|
|
3964
|
+
constructWebhookEvent,
|
|
3965
|
+
describeApiError,
|
|
3966
|
+
describeComplianceDocumentType,
|
|
2924
3967
|
describeComplianceEnrollmentStatus,
|
|
3968
|
+
describeComplianceService,
|
|
2925
3969
|
describeComplianceTaxReportState,
|
|
2926
3970
|
describeComplianceTransactionState,
|
|
2927
3971
|
describeComplianceTransactionType,
|
|
3972
|
+
describePeppolLookupStatus,
|
|
3973
|
+
describeSmartEnrichmentMode,
|
|
2928
3974
|
describeSmartEnrichmentStatus,
|
|
2929
3975
|
describeVatState,
|
|
3976
|
+
generateWebhookSignatureHeader,
|
|
2930
3977
|
getBaseUrl,
|
|
2931
3978
|
getFailedVatStates,
|
|
3979
|
+
parseWebhookSignatureHeader,
|
|
3980
|
+
toComplianceDocumentType,
|
|
2932
3981
|
toComplianceEnrollmentStatus,
|
|
3982
|
+
toComplianceService,
|
|
2933
3983
|
toComplianceTaxReportState,
|
|
2934
3984
|
toComplianceTransactionState,
|
|
2935
3985
|
toComplianceTransactionType,
|
|
2936
|
-
|
|
3986
|
+
toPeppolLookupStatus,
|
|
3987
|
+
toSmartEnrichmentMode,
|
|
3988
|
+
toSmartEnrichmentStatus,
|
|
3989
|
+
verifyWebhookSignature
|
|
2937
3990
|
});
|
|
2938
3991
|
//# sourceMappingURL=index.cjs.map
|