@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/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
- toSmartEnrichmentStatus: () => toSmartEnrichmentStatus
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
- constructor(message, statusCode, responseBody, context = {}) {
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 response = await this.httpClient.request("POST", `${this.baseUrl}/auth/login`, {
193
- headers: {
194
- "Content-Type": "application/json",
195
- "x-api-key": this.apiKey
196
- },
197
- body: JSON.stringify(body)
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
- if (!response.ok) {
207
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
208
- }
209
- const data = this.parseResponse(responseText);
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 response = await this.httpClient.request("POST", `${this.baseUrl}/auth/refresh`, { headers });
227
- const responseText = await response.text();
228
- if (response.status === 401) {
229
- throw new AuthenticationException("Token refresh failed: unauthorized", { status: 401, body: responseText });
230
- }
231
- if (!response.ok) {
232
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
233
- }
234
- const data = this.parseResponse(responseText);
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", 0, responseText);
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", 0, responseText);
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
- const response = await this.httpClient.request("GET", `${this.baseUrl}/company`, { headers });
291
- const responseText = await response.text();
292
- if (!response.ok) {
293
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
294
- }
295
- let parsed;
296
- try {
297
- parsed = JSON.parse(responseText);
298
- } catch {
299
- throw new HttpException("Invalid JSON response", 0, responseText);
300
- }
301
- if (typeof parsed !== "object" || parsed === null) {
302
- throw new HttpException("Unexpected response format", 0, responseText);
303
- }
304
- const obj = parsed;
305
- if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
306
- return normalizeCompanyResource(obj["data"]);
307
- }
308
- return normalizeCompanyResource(obj);
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 VALUES2 = Object.values(ComplianceTaxReportState);
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 (VALUES2.includes(normalized)) {
806
+ if (VALUES3.includes(normalized)) {
471
807
  return normalized;
472
808
  }
473
809
  }
474
810
  return ComplianceTaxReportState.UNKNOWN;
475
811
  }
476
- var DESCRIPTIONS2 = {
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 DESCRIPTIONS2[state];
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 VALUES3 = Object.values(ComplianceTransactionState);
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 (VALUES3.includes(normalized)) {
981
+ if (VALUES5.includes(normalized)) {
621
982
  return normalized;
622
983
  }
623
984
  }
624
985
  return ComplianceTransactionState.UNKNOWN;
625
986
  }
626
- var DESCRIPTIONS3 = {
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 DESCRIPTIONS3[state];
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 VALUES4 = Object.values(ComplianceTransactionType);
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 (VALUES4.includes(normalized)) {
1011
+ if (VALUES6.includes(normalized)) {
651
1012
  return normalized;
652
1013
  }
653
1014
  }
654
1015
  return ComplianceTransactionType.UNKNOWN;
655
1016
  }
656
- var DESCRIPTIONS4 = {
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 DESCRIPTIONS4[type];
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/dto/RevenueStatistics.ts
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: asString4(row["bucket"]),
1428
+ bucket: asString6(row["bucket"]),
882
1429
  ...toTotals(row)
883
1430
  }));
884
1431
  const byType = asRecords(data["by_type"]).map((row) => ({
885
- type: asString4(row["type"]),
886
- typeLabel: asString4(row["type_label"]),
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: asString4(row["country"], "unknown"),
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: asString4(row["state"]),
897
- stateLabel: asString4(row["state_label"]),
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: asString4(row["currency"]),
1449
+ currency: asString6(row["currency"]),
903
1450
  ...toTotals(row)
904
1451
  }));
905
1452
  return new _RevenueStatistics(
906
- asString4(range["date_from"]) || void 0,
907
- asString4(range["date_to"]) || void 0,
908
- asString4(range["interval"]) || void 0,
909
- asString4(data["primary_currency"], "EUR"),
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 asString5(value, fallback = "") {
1469
+ function asString7(value, fallback = "") {
923
1470
  return typeof value === "string" ? value : fallback;
924
1471
  }
925
- function asNullableString4(value) {
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
- asString5(data["company_name"]),
952
- asString5(data["siren"]),
953
- asNullableString4(data["siret"]),
954
- asNullableString4(data["naf_code"]),
955
- asNullableString4(data["naf_full"]),
956
- asNullableString4(data["enterprise_size"]),
957
- asNullableString4(data["address"]),
958
- asNullableString4(data["city"]),
959
- asNullableString4(data["postalcode"])
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 asString6(value, fallback = "") {
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: asString6(row["category"]),
984
- labelKey: asString6(row["label_key"]),
985
- taxName: asString6(row["tax_name"]),
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(asString6(data["country"]), asString6(data["currency"], "EUR"), rates);
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 DGFiP regime is activated right away; pass `autoActivate: false`
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
- * On provider failure the API responds 502 (mapped to an HttpException) and
1040
- * the enrollment is persisted in `error_account`/`error_activation` state so
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
- const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/enrollments`, body);
1064
- const data = await this.parseJsonResponse(response);
1065
- return ComplianceEnrollment.fromArray(data);
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
- return this.httpClient.request(method, url, options);
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(`HTTP error ${response.status}`, response.status, responseText);
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", 0, responseText);
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", 0, responseText);
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(`HTTP error ${response.status}`, response.status, responseText);
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 VALUES5 = Object.values(SmartEnrichmentStatus);
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 (VALUES5.includes(normalized)) {
2354
+ if (VALUES9.includes(normalized)) {
1524
2355
  return normalized;
1525
2356
  }
1526
2357
  }
1527
2358
  return SmartEnrichmentStatus.UNKNOWN;
1528
2359
  }
1529
- var DESCRIPTIONS5 = {
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 DESCRIPTIONS5[status];
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 asString7(value, fallback = "") {
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 asRecord(value) {
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 = asRecord(data["range"]);
1774
- const totals = asRecord(data["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: asString7(row["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: asString7(row["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: asString7(row["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: asString7(row["bucket"]),
2636
+ bucket: asString9(row["bucket"]),
1792
2637
  count: asNumber6(row["count"])
1793
2638
  }));
1794
2639
  return new _SmartEnrichmentStatistics(
1795
2640
  {
1796
- dateFrom: asString7(range["date_from"]),
1797
- dateTo: asString7(range["date_to"]),
1798
- interval: asString7(range["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 response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment/bulk`, { items });
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
- return this.httpClient.request(method, url, options);
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("Validation failed", this.parseValidationErrors(responseText));
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(`HTTP error ${response.status}`, response.status, responseText);
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", 0, responseText);
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", 0, responseText);
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("Validation failed", this.parseValidationErrors(responseText));
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(`HTTP error ${response.status}`, response.status, responseText);
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", 0, responseText);
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", 0, responseText);
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("Validation failed", this.parseValidationErrors(responseText));
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(`HTTP error ${response.status}`, response.status, responseText);
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 DESCRIPTIONS6 = {
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 DESCRIPTIONS6[state];
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.validateWithGatewayRetry(body, 2);
2585
- }
2586
- async validateWithGatewayRetry(body, attemptsLeft) {
2587
- const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate`, body);
2588
- if (response.status === 504) {
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
- const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/multiple`, body);
2603
- const data = await this.parseJsonResponse(response);
2604
- return VatCollection.fromResponse(data);
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
- const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/schema`, body);
2609
- return this.parseJsonResponse(response);
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
- const response = await this.sendRequest("GET", `${this.baseUrl}/vat/state/${encodeURIComponent(vatUid)}`);
2613
- const data = await this.parseJsonResponse(response);
2614
- return VatResource.fromArray(data);
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
- const response = await this.sendRequest("GET", url);
2620
- const data = await this.parseJsonResponse(response);
2621
- return VatCollection.fromResponse(data);
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
- const response = await this.sendRequest("GET", url);
2630
- const data = await this.parseJsonResponse(response);
2631
- return VatCollection.fromResponse(data);
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
- const response = await this.sendBinaryRequest("GET", url);
2637
- const buffer = await response.arrayBuffer();
2638
- return new Uint8Array(buffer);
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
- const response = await this.sendBinaryRequest("GET", url);
2663
- const buffer = await response.arrayBuffer();
2664
- return new Uint8Array(buffer);
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(`HTTP error ${response.status}`, response.status, text);
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(`HTTP error ${response.status}`, response.status, responseText);
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", 0, responseText);
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", 0, responseText);
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.1.0";
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(this.baseUrl, options.apiKey, tokenStorage, retryClient);
2853
- this.eReporting = new EReportingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
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
- toSmartEnrichmentStatus
3986
+ toPeppolLookupStatus,
3987
+ toSmartEnrichmentMode,
3988
+ toSmartEnrichmentStatus,
3989
+ verifyWebhookSignature
2937
3990
  });
2938
3991
  //# sourceMappingURL=index.cjs.map