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