@taxora/sdk 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,13 +24,19 @@ __export(index_exports, {
24
24
  AuthEndpoint: () => AuthEndpoint,
25
25
  AuthRetryHttpClient: () => AuthRetryHttpClient,
26
26
  AuthenticationException: () => AuthenticationException,
27
+ BillingEndpoint: () => BillingEndpoint,
27
28
  CompanyAddress: () => CompanyAddress,
28
29
  CompanyEndpoint: () => CompanyEndpoint,
30
+ ComplianceDeliveryAction: () => ComplianceDeliveryAction,
31
+ ComplianceDeliveryDecision: () => ComplianceDeliveryDecision,
29
32
  ComplianceDocumentType: () => ComplianceDocumentType,
30
33
  ComplianceEnrollment: () => ComplianceEnrollment,
31
34
  ComplianceEnrollmentPage: () => ComplianceEnrollmentPage,
32
35
  ComplianceEnrollmentStatus: () => ComplianceEnrollmentStatus,
36
+ ComplianceOfferings: () => ComplianceOfferings,
33
37
  ComplianceService: () => ComplianceService,
38
+ ComplianceTax: () => ComplianceTax,
39
+ ComplianceTaxCategory: () => ComplianceTaxCategory,
34
40
  ComplianceTaxReport: () => ComplianceTaxReport,
35
41
  ComplianceTaxReportPage: () => ComplianceTaxReportPage,
36
42
  ComplianceTaxReportState: () => ComplianceTaxReportState,
@@ -39,6 +45,8 @@ __export(index_exports, {
39
45
  ComplianceTransactionState: () => ComplianceTransactionState,
40
46
  ComplianceTransactionType: () => ComplianceTransactionType,
41
47
  DEFAULT_WEBHOOK_TOLERANCE_SECONDS: () => DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
48
+ EInvoicingQuota: () => EInvoicingQuota,
49
+ EInvoicingQuotaPeriod: () => EInvoicingQuotaPeriod,
42
50
  EReportingEndpoint: () => EReportingEndpoint,
43
51
  Environment: () => Environment,
44
52
  FetchHttpClient: () => FetchHttpClient,
@@ -73,6 +81,8 @@ __export(index_exports, {
73
81
  TaxoraClientFactory: () => TaxoraClientFactory,
74
82
  TaxoraException: () => TaxoraException,
75
83
  Token: () => Token,
84
+ UsageSummary: () => UsageSummary,
85
+ UsageSummaryPage: () => UsageSummaryPage,
76
86
  ValidationException: () => ValidationException,
77
87
  VatCertificateExport: () => VatCertificateExport,
78
88
  VatCollection: () => VatCollection,
@@ -84,6 +94,7 @@ __export(index_exports, {
84
94
  WEBHOOK_ID_HEADER: () => WEBHOOK_ID_HEADER,
85
95
  WEBHOOK_SIGNATURE_HEADER: () => WEBHOOK_SIGNATURE_HEADER,
86
96
  WebhookSignatureVerificationError: () => WebhookSignatureVerificationError,
97
+ calculateInvoiceTotals: () => calculateInvoiceTotals,
87
98
  computeWebhookSignature: () => computeWebhookSignature,
88
99
  constructWebhookEvent: () => constructWebhookEvent,
89
100
  describeApiError: () => describeApiError,
@@ -101,6 +112,8 @@ __export(index_exports, {
101
112
  generateWebhookSignatureHeader: () => generateWebhookSignatureHeader,
102
113
  getBaseUrl: () => getBaseUrl,
103
114
  getFailedVatStates: () => getFailedVatStates,
115
+ isTransactionBuyerRejected: () => isTransactionBuyerRejected,
116
+ parseComplianceTaxCategory: () => parseComplianceTaxCategory,
104
117
  parseLocalTaxNumbersCsv: () => parseLocalTaxNumbersCsv,
105
118
  parseWebhookSignatureHeader: () => parseWebhookSignatureHeader,
106
119
  toComplianceDocumentType: () => toComplianceDocumentType,
@@ -559,6 +572,356 @@ var AuthEndpoint = class {
559
572
  }
560
573
  };
561
574
 
575
+ // src/dto/EInvoicingQuota.ts
576
+ function asString(value, fallback = "") {
577
+ return typeof value === "string" ? value : fallback;
578
+ }
579
+ function asNullableString(value) {
580
+ return typeof value === "string" ? value : null;
581
+ }
582
+ function asInt(value) {
583
+ return typeof value === "number" ? Math.trunc(value) : Number(value ?? 0) || 0;
584
+ }
585
+ function asRecord(value) {
586
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
587
+ }
588
+ var EInvoicingQuotaPeriod = class _EInvoicingQuotaPeriod {
589
+ constructor(period, used, included, freeUsed, freeRemaining, billableUnits, amount, monthlyFee, total, state, byCountry, billedAt, manualReference, monthlyFeeReference) {
590
+ this.period = period;
591
+ this.used = used;
592
+ this.included = included;
593
+ this.freeUsed = freeUsed;
594
+ this.freeRemaining = freeRemaining;
595
+ this.billableUnits = billableUnits;
596
+ this.amount = amount;
597
+ this.monthlyFee = monthlyFee;
598
+ this.total = total;
599
+ this.state = state;
600
+ this.byCountry = byCountry;
601
+ this.billedAt = billedAt;
602
+ this.manualReference = manualReference;
603
+ this.monthlyFeeReference = monthlyFeeReference;
604
+ }
605
+ period;
606
+ used;
607
+ included;
608
+ freeUsed;
609
+ freeRemaining;
610
+ billableUnits;
611
+ amount;
612
+ monthlyFee;
613
+ total;
614
+ state;
615
+ byCountry;
616
+ billedAt;
617
+ manualReference;
618
+ monthlyFeeReference;
619
+ static fromArray(data) {
620
+ const byCountry = {};
621
+ if (Array.isArray(data["by_country"])) {
622
+ for (const row of data["by_country"]) {
623
+ const entry = asRecord(row);
624
+ if (entry && typeof entry["country"] === "string") {
625
+ byCountry[entry["country"]] = asInt(entry["used"]);
626
+ }
627
+ }
628
+ }
629
+ return new _EInvoicingQuotaPeriod(
630
+ asString(data["period"]),
631
+ asInt(data["used"]),
632
+ asInt(data["included"]),
633
+ asInt(data["free_used"]),
634
+ asInt(data["free_remaining"]),
635
+ asInt(data["billable_units"]),
636
+ asString(data["amount"], "0.00"),
637
+ asNullableString(data["monthly_fee"]),
638
+ asString(data["total"], "0.00"),
639
+ asString(data["state"], "open"),
640
+ byCountry,
641
+ asNullableString(data["billed_at"]),
642
+ asNullableString(data["manual_reference"]),
643
+ asNullableString(data["monthly_fee_reference"])
644
+ );
645
+ }
646
+ };
647
+ var EInvoicingQuota = class _EInvoicingQuota {
648
+ constructor(billing, plan, currency, included, tiers, countries, monthlyFee, current, history) {
649
+ this.billing = billing;
650
+ this.plan = plan;
651
+ this.currency = currency;
652
+ this.included = included;
653
+ this.tiers = tiers;
654
+ this.countries = countries;
655
+ this.monthlyFee = monthlyFee;
656
+ this.current = current;
657
+ this.history = history;
658
+ }
659
+ billing;
660
+ plan;
661
+ currency;
662
+ included;
663
+ tiers;
664
+ countries;
665
+ monthlyFee;
666
+ current;
667
+ history;
668
+ static fromArray(data) {
669
+ const tiers = [];
670
+ if (Array.isArray(data["tiers"])) {
671
+ for (const row of data["tiers"]) {
672
+ const tier = asRecord(row);
673
+ if (tier) {
674
+ tiers.push({
675
+ upTo: tier["up_to"] === null || tier["up_to"] === void 0 ? null : asInt(tier["up_to"]),
676
+ unitPrice: asString(tier["unit_price"], "0")
677
+ });
678
+ }
679
+ }
680
+ }
681
+ const history = [];
682
+ if (Array.isArray(data["history"])) {
683
+ for (const row of data["history"]) {
684
+ const period = asRecord(row);
685
+ if (period) history.push(EInvoicingQuotaPeriod.fromArray(period));
686
+ }
687
+ }
688
+ return new _EInvoicingQuota(
689
+ asString(data["billing"], "invoice"),
690
+ asNullableString(data["plan"]),
691
+ asString(data["currency"], "EUR"),
692
+ asInt(data["included"]),
693
+ tiers,
694
+ Array.isArray(data["countries"]) ? data["countries"].map(String) : [],
695
+ asNullableString(data["monthly_fee"]),
696
+ EInvoicingQuotaPeriod.fromArray(asRecord(data["current"]) ?? {}),
697
+ history
698
+ );
699
+ }
700
+ };
701
+
702
+ // src/dto/UsageSummary.ts
703
+ function asRecord2(value) {
704
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
705
+ }
706
+ function asRecordList(value) {
707
+ return Array.isArray(value) ? value.filter((row) => asRecord2(row) !== null) : [];
708
+ }
709
+ function asString2(value, fallback = "") {
710
+ return typeof value === "string" ? value : fallback;
711
+ }
712
+ function asNullableString2(value) {
713
+ return typeof value === "string" ? value : null;
714
+ }
715
+ function asNumber(value) {
716
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
717
+ }
718
+ function asNullableNumber(value) {
719
+ if (value === null || value === void 0 || value === "") return null;
720
+ const num = Number(value);
721
+ return Number.isFinite(num) ? num : null;
722
+ }
723
+ var UsageSummary = class _UsageSummary {
724
+ constructor(id, companyId, feature, country, regime, billingPeriod, eventCount, billableAmount, state, stateLabel, periodClosedAt, billingTriggeredAt, billedAt, stripeInvoiceId, stripeSubscriptionItemId, billingNotes, events, createdAt, updatedAt) {
725
+ this.id = id;
726
+ this.companyId = companyId;
727
+ this.feature = feature;
728
+ this.country = country;
729
+ this.regime = regime;
730
+ this.billingPeriod = billingPeriod;
731
+ this.eventCount = eventCount;
732
+ this.billableAmount = billableAmount;
733
+ this.state = state;
734
+ this.stateLabel = stateLabel;
735
+ this.periodClosedAt = periodClosedAt;
736
+ this.billingTriggeredAt = billingTriggeredAt;
737
+ this.billedAt = billedAt;
738
+ this.stripeInvoiceId = stripeInvoiceId;
739
+ this.stripeSubscriptionItemId = stripeSubscriptionItemId;
740
+ this.billingNotes = billingNotes;
741
+ this.events = events;
742
+ this.createdAt = createdAt;
743
+ this.updatedAt = updatedAt;
744
+ }
745
+ id;
746
+ companyId;
747
+ feature;
748
+ country;
749
+ regime;
750
+ billingPeriod;
751
+ eventCount;
752
+ billableAmount;
753
+ state;
754
+ stateLabel;
755
+ periodClosedAt;
756
+ billingTriggeredAt;
757
+ billedAt;
758
+ stripeInvoiceId;
759
+ stripeSubscriptionItemId;
760
+ billingNotes;
761
+ events;
762
+ createdAt;
763
+ updatedAt;
764
+ static fromArray(data) {
765
+ const amount = data["billable_amount"];
766
+ return new _UsageSummary(
767
+ asNumber(data["id"]),
768
+ asNumber(data["company_id"]),
769
+ asString2(data["feature"]),
770
+ asNullableString2(data["country"]),
771
+ asNullableString2(data["regime"]),
772
+ asString2(data["billing_period"]),
773
+ asNumber(data["event_count"]),
774
+ typeof amount === "string" ? amount : typeof amount === "number" ? String(amount) : null,
775
+ asNullableString2(data["state"]),
776
+ asNullableString2(data["state_label"]),
777
+ asNullableString2(data["period_closed_at"]),
778
+ asNullableString2(data["billing_triggered_at"]),
779
+ asNullableString2(data["billed_at"]),
780
+ asNullableString2(data["stripe_invoice_id"]),
781
+ asNullableString2(data["stripe_subscription_item_id"]),
782
+ asNullableString2(data["billing_notes"]),
783
+ asRecordList(data["events"]).map((event) => ({
784
+ id: asNumber(event["id"]),
785
+ usageSummaryId: asNullableNumber(event["usage_summary_id"]),
786
+ feature: asString2(event["feature"]),
787
+ country: asNullableString2(event["country"]),
788
+ regime: asNullableString2(event["regime"]),
789
+ billingPeriod: asString2(event["billing_period"]),
790
+ resourceType: asNullableString2(event["resource_type"]),
791
+ resourceId: asNullableNumber(event["resource_id"]),
792
+ stripeUsageRecordId: asNullableString2(event["stripe_usage_record_id"]),
793
+ stripeReportedAt: asNullableString2(event["stripe_reported_at"]),
794
+ createdAt: asNullableString2(event["created_at"])
795
+ })),
796
+ asNullableString2(data["created_at"]),
797
+ asNullableString2(data["updated_at"])
798
+ );
799
+ }
800
+ };
801
+ var UsageSummaryPage = class _UsageSummaryPage {
802
+ constructor(rows, currentPage, perPage, total, lastPage) {
803
+ this.rows = rows;
804
+ this.currentPage = currentPage;
805
+ this.perPage = perPage;
806
+ this.total = total;
807
+ this.lastPage = lastPage;
808
+ }
809
+ rows;
810
+ currentPage;
811
+ perPage;
812
+ total;
813
+ lastPage;
814
+ static fromResponse(payload) {
815
+ const obj = asRecord2(payload) ?? {};
816
+ const rows = asRecordList(obj["data"]).map(UsageSummary.fromArray);
817
+ const meta = asRecord2(obj["meta"]) ?? {};
818
+ const num = (value, fallback) => typeof value === "number" ? value : fallback;
819
+ return new _UsageSummaryPage(
820
+ rows,
821
+ num(meta["current_page"], 1),
822
+ num(meta["per_page"], rows.length),
823
+ num(meta["total"], rows.length),
824
+ num(meta["last_page"], 1)
825
+ );
826
+ }
827
+ [Symbol.iterator]() {
828
+ return this.rows[Symbol.iterator]();
829
+ }
830
+ get length() {
831
+ return this.rows.length;
832
+ }
833
+ };
834
+
835
+ // src/endpoints/BillingEndpoint.ts
836
+ var PERIOD_REGEX = /^\d{4}-(0[1-9]|1[0-2])$/;
837
+ var BillingEndpoint = class {
838
+ constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
839
+ this.baseUrl = baseUrl;
840
+ this.apiKey = apiKey;
841
+ this.tokenStorage = tokenStorage;
842
+ this.httpClient = httpClient;
843
+ this.retryPolicy = retryPolicy;
844
+ }
845
+ baseUrl;
846
+ apiKey;
847
+ tokenStorage;
848
+ httpClient;
849
+ retryPolicy;
850
+ /** Paginated usage summaries, newest billing period first. `perPage` is clamped to 1-100 server-side. */
851
+ async listUsageSummaries(page = 1, perPage = 25) {
852
+ const params = new URLSearchParams();
853
+ params.set("page", String(page));
854
+ params.set("per_page", String(perPage));
855
+ const data = await this.getJson(`${this.baseUrl}/billing/usage-summaries?${params.toString()}`);
856
+ return UsageSummaryPage.fromResponse(data);
857
+ }
858
+ /**
859
+ * The usage summaries of one billing period (YYYY-MM), one per feature,
860
+ * each with its usage events. A period without usage yields a 404 (HttpException).
861
+ */
862
+ async getUsageSummaries(period) {
863
+ if (!PERIOD_REGEX.test(period)) {
864
+ throw new TaxoraException(`Invalid billing period "${period}". Expected YYYY-MM, e.g. "2026-09".`);
865
+ }
866
+ const data = await this.getJson(`${this.baseUrl}/billing/usage-summaries/${period}`);
867
+ return Array.isArray(data) ? data.filter((row) => typeof row === "object" && row !== null).map(UsageSummary.fromArray) : [];
868
+ }
869
+ /**
870
+ * The e-invoicing allowance: documents included per month, used, still free and
871
+ * what is due for the documents beyond it — one pool over all e-invoicing
872
+ * countries, current month first, then up to `months - 1` earlier months (1-24).
873
+ * Resolves to null when the company has no e-invoicing.
874
+ */
875
+ async getEInvoicingQuota(months = 6) {
876
+ if (!Number.isInteger(months) || months < 1 || months > 24) {
877
+ throw new TaxoraException("months must be an integer between 1 and 24.");
878
+ }
879
+ const data = await this.getJson(`${this.baseUrl}/billing/einvoicing-quota?months=${months}`);
880
+ if (typeof data !== "object" || data === null || Array.isArray(data) || !("billing" in data)) {
881
+ return null;
882
+ }
883
+ return EInvoicingQuota.fromArray(data);
884
+ }
885
+ buildHeaders() {
886
+ const headers = {
887
+ "Content-Type": "application/json",
888
+ "x-api-key": this.apiKey
889
+ };
890
+ const token = this.tokenStorage.get();
891
+ if (token) {
892
+ headers["Authorization"] = `Bearer ${token.accessToken}`;
893
+ }
894
+ return headers;
895
+ }
896
+ /** GET + envelope unwrap (`{success, data}` → data, which may be an object or an array). */
897
+ async getJson(url) {
898
+ return withRetries(this.retryPolicy, "billing request", async () => {
899
+ const response = await this.httpClient.request("GET", url, { headers: this.buildHeaders() });
900
+ const responseText = await response.text();
901
+ if (!response.ok) {
902
+ throw new HttpException(
903
+ describeApiError(responseText, response.status),
904
+ response.status,
905
+ responseText,
906
+ {},
907
+ response.headers.get("retry-after")
908
+ );
909
+ }
910
+ let parsed;
911
+ try {
912
+ parsed = JSON.parse(responseText);
913
+ } catch {
914
+ throw new HttpException("Invalid JSON response", response.status, responseText);
915
+ }
916
+ if (typeof parsed !== "object" || parsed === null) {
917
+ throw new HttpException("Unexpected response format", response.status, responseText);
918
+ }
919
+ const obj = parsed;
920
+ return obj["data"] !== void 0 && obj["data"] !== null ? obj["data"] : obj;
921
+ });
922
+ }
923
+ };
924
+
562
925
  // src/dto/CompanyResource.ts
563
926
  function normalizeCompanyResource(data) {
564
927
  const apiRateLimit = typeof data["api_rate_limit"] === "number" ? data["api_rate_limit"] : void 0;
@@ -624,6 +987,67 @@ var CompanyEndpoint = class {
624
987
  }
625
988
  };
626
989
 
990
+ // src/enums/ComplianceDeliveryAction.ts
991
+ var ComplianceDeliveryAction = {
992
+ /** The buyer can receive e-invoices: create the transaction, Taxora sends it. */
993
+ EINVOICE: "einvoice",
994
+ /** The buyer is not on the network: send your regular invoice yourself (a transaction would end as not_required and is not billed). */
995
+ REGULAR_INVOICE: "regular_invoice",
996
+ /** No definite directory answer yet: create the transaction anyway, Taxora asks again before sending. */
997
+ EINVOICE_PENDING: "einvoice_pending"
998
+ };
999
+
1000
+ // src/enums/PeppolLookupStatus.ts
1001
+ var PeppolLookupStatus = {
1002
+ REACHABLE: "reachable",
1003
+ NOT_REACHABLE: "not_reachable",
1004
+ PENDING: "pending",
1005
+ UNKNOWN: "unknown"
1006
+ };
1007
+ var VALUES = Object.values(PeppolLookupStatus);
1008
+ function toPeppolLookupStatus(value) {
1009
+ if (typeof value === "string") {
1010
+ const normalized = value.trim().toLowerCase();
1011
+ if (VALUES.includes(normalized)) {
1012
+ return normalized;
1013
+ }
1014
+ }
1015
+ return PeppolLookupStatus.UNKNOWN;
1016
+ }
1017
+ var DESCRIPTIONS = {
1018
+ [PeppolLookupStatus.REACHABLE]: "The participant is registered on Peppol and can receive e-invoices.",
1019
+ [PeppolLookupStatus.NOT_REACHABLE]: "The participant is not registered on Peppol.",
1020
+ [PeppolLookupStatus.PENDING]: "The directory lookup is still resolving \u2014 ask again in a few seconds.",
1021
+ [PeppolLookupStatus.UNKNOWN]: "Unknown lookup status."
1022
+ };
1023
+ function describePeppolLookupStatus(status) {
1024
+ return DESCRIPTIONS[status];
1025
+ }
1026
+
1027
+ // src/dto/ComplianceDeliveryDecision.ts
1028
+ var ComplianceDeliveryDecision = class _ComplianceDeliveryDecision {
1029
+ constructor(action, lookup) {
1030
+ this.action = action;
1031
+ this.lookup = lookup;
1032
+ }
1033
+ action;
1034
+ lookup;
1035
+ /** True unless the buyer is not on the network. */
1036
+ get sendsEInvoice() {
1037
+ return this.action !== ComplianceDeliveryAction.REGULAR_INVOICE;
1038
+ }
1039
+ static fromLookup(lookup) {
1040
+ switch (lookup.status) {
1041
+ case PeppolLookupStatus.REACHABLE:
1042
+ return new _ComplianceDeliveryDecision(ComplianceDeliveryAction.EINVOICE, lookup);
1043
+ case PeppolLookupStatus.NOT_REACHABLE:
1044
+ return new _ComplianceDeliveryDecision(ComplianceDeliveryAction.REGULAR_INVOICE, lookup);
1045
+ default:
1046
+ return new _ComplianceDeliveryDecision(ComplianceDeliveryAction.EINVOICE_PENDING, lookup);
1047
+ }
1048
+ }
1049
+ };
1050
+
627
1051
  // src/enums/ComplianceEnrollmentStatus.ts
628
1052
  var ComplianceEnrollmentStatus = {
629
1053
  PENDING_DATA: "pending_data",
@@ -635,17 +1059,17 @@ var ComplianceEnrollmentStatus = {
635
1059
  ERROR_ACTIVATION: "error_activation",
636
1060
  UNKNOWN: "unknown"
637
1061
  };
638
- var VALUES = Object.values(ComplianceEnrollmentStatus);
1062
+ var VALUES2 = Object.values(ComplianceEnrollmentStatus);
639
1063
  function toComplianceEnrollmentStatus(value) {
640
1064
  if (typeof value === "string") {
641
1065
  const normalized = value.trim().toLowerCase();
642
- if (VALUES.includes(normalized)) {
1066
+ if (VALUES2.includes(normalized)) {
643
1067
  return normalized;
644
1068
  }
645
1069
  }
646
1070
  return ComplianceEnrollmentStatus.UNKNOWN;
647
1071
  }
648
- var DESCRIPTIONS = {
1072
+ var DESCRIPTIONS2 = {
649
1073
  [ComplianceEnrollmentStatus.PENDING_DATA]: "Awaiting merchant data before provisioning can start.",
650
1074
  [ComplianceEnrollmentStatus.ACCOUNT_CREATED]: "Provider account exists. Regime not yet activated.",
651
1075
  [ComplianceEnrollmentStatus.REGIME_ACTIVATED]: "Compliance regime activated at provider. Ready to submit transactions.",
@@ -656,7 +1080,7 @@ var DESCRIPTIONS = {
656
1080
  [ComplianceEnrollmentStatus.UNKNOWN]: "Unknown status."
657
1081
  };
658
1082
  function describeComplianceEnrollmentStatus(status) {
659
- return DESCRIPTIONS[status];
1083
+ return DESCRIPTIONS2[status];
660
1084
  }
661
1085
 
662
1086
  // src/enums/ComplianceService.ts
@@ -665,33 +1089,33 @@ var ComplianceService = {
665
1089
  E_INVOICING: "e_invoicing",
666
1090
  UNKNOWN: "unknown"
667
1091
  };
668
- var VALUES2 = Object.values(ComplianceService);
1092
+ var VALUES3 = Object.values(ComplianceService);
669
1093
  function toComplianceService(value) {
670
1094
  if (typeof value === "string") {
671
1095
  const normalized = value.trim().toLowerCase();
672
- if (VALUES2.includes(normalized)) {
1096
+ if (VALUES3.includes(normalized)) {
673
1097
  return normalized;
674
1098
  }
675
1099
  }
676
1100
  return ComplianceService.UNKNOWN;
677
1101
  }
678
- var DESCRIPTIONS2 = {
1102
+ var DESCRIPTIONS3 = {
679
1103
  [ComplianceService.E_REPORTING]: "E-reporting of transaction data to the tax authority (France: DGFiP Flux 10).",
680
1104
  [ComplianceService.E_INVOICING]: "Structured e-invoices delivered to the buyer (Norway: EHF / Peppol BIS 3.0).",
681
1105
  [ComplianceService.UNKNOWN]: "Unknown service."
682
1106
  };
683
1107
  function describeComplianceService(service) {
684
- return DESCRIPTIONS2[service];
1108
+ return DESCRIPTIONS3[service];
685
1109
  }
686
1110
 
687
1111
  // src/dto/ComplianceEnrollment.ts
688
- function asString(value, fallback = "") {
1112
+ function asString3(value, fallback = "") {
689
1113
  return typeof value === "string" ? value : fallback;
690
1114
  }
691
- function asNullableString(value) {
1115
+ function asNullableString3(value) {
692
1116
  return typeof value === "string" ? value : null;
693
1117
  }
694
- function asNumber(value) {
1118
+ function asNumber2(value) {
695
1119
  return typeof value === "number" ? value : Number(value ?? 0) || 0;
696
1120
  }
697
1121
  var ComplianceEnrollment = class _ComplianceEnrollment {
@@ -738,29 +1162,114 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
738
1162
  static fromArray(data) {
739
1163
  const regimeConfig = typeof data["regime_config"] === "object" && data["regime_config"] !== null && !Array.isArray(data["regime_config"]) ? data["regime_config"] : null;
740
1164
  return new _ComplianceEnrollment(
741
- asNumber(data["id"]),
742
- asNumber(data["company_id"]),
743
- asString(data["country"]),
744
- asString(data["regime"]),
745
- asString(data["provider"]),
1165
+ asNumber2(data["id"]),
1166
+ asNumber2(data["company_id"]),
1167
+ asString3(data["country"]),
1168
+ asString3(data["regime"]),
1169
+ asString3(data["provider"]),
746
1170
  toComplianceEnrollmentStatus(data["status"]),
747
- asString(data["status_label"]),
748
- asNullableString(data["status_error"]),
749
- asNullableString(data["provider_account_id"]),
750
- asNullableString(data["tax_id"]),
751
- asNullableString(data["company_register_id"]),
752
- asNullableString(data["company_register_scheme"]),
1171
+ asString3(data["status_label"]),
1172
+ asNullableString3(data["status_error"]),
1173
+ asNullableString3(data["provider_account_id"]),
1174
+ asNullableString3(data["tax_id"]),
1175
+ asNullableString3(data["company_register_id"]),
1176
+ asNullableString3(data["company_register_scheme"]),
753
1177
  regimeConfig,
754
- asNullableString(data["reporting_start_date"]),
755
- asNullableString(data["notification_email"]),
1178
+ asNullableString3(data["reporting_start_date"]),
1179
+ asNullableString3(data["notification_email"]),
756
1180
  data["auto_send"] === true,
757
- asNullableString(data["created_at"]),
758
- asNullableString(data["updated_at"]),
1181
+ asNullableString3(data["created_at"]),
1182
+ asNullableString3(data["updated_at"]),
759
1183
  toComplianceService(data["service"])
760
1184
  );
761
1185
  }
762
1186
  };
763
1187
 
1188
+ // src/dto/ComplianceOfferings.ts
1189
+ function asRecord3(value) {
1190
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1191
+ }
1192
+ function asRecordList2(value) {
1193
+ return Array.isArray(value) ? value.filter((row) => asRecord3(row) !== null) : [];
1194
+ }
1195
+ function asString4(value, fallback = "") {
1196
+ return typeof value === "string" ? value : fallback;
1197
+ }
1198
+ function asNullableString4(value) {
1199
+ return typeof value === "string" ? value : null;
1200
+ }
1201
+ function asNullableNumber2(value) {
1202
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1203
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) return Number(value);
1204
+ return null;
1205
+ }
1206
+ function asStringList(value) {
1207
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1208
+ }
1209
+ function toRequires(value) {
1210
+ const record = asRecord3(value);
1211
+ return record === null ? null : { country: asString4(record["country"]), service: asString4(record["service"]) };
1212
+ }
1213
+ var ComplianceOfferings = class _ComplianceOfferings {
1214
+ constructor(offerings, coverage, access, accessByCountry) {
1215
+ this.offerings = offerings;
1216
+ this.coverage = coverage;
1217
+ this.access = access;
1218
+ this.accessByCountry = accessByCountry;
1219
+ }
1220
+ offerings;
1221
+ coverage;
1222
+ access;
1223
+ accessByCountry;
1224
+ static fromArray(data) {
1225
+ const offerings = asRecordList2(data["data"]).map((row) => ({
1226
+ country: asString4(row["country"]),
1227
+ service: asString4(row["service"]),
1228
+ currency: asString4(row["currency"], "EUR"),
1229
+ active: row["active"] === true,
1230
+ viaStripe: row["via_stripe"] === true,
1231
+ plan: asNullableString4(row["plan"]),
1232
+ status: asNullableString4(row["status"]),
1233
+ enrolled: row["enrolled"] === true,
1234
+ plans: asRecordList2(row["plans"]).map((plan) => ({
1235
+ key: asString4(plan["key"]),
1236
+ monthlyFee: asNullableNumber2(plan["monthly_fee"]),
1237
+ setupFee: asNullableNumber2(plan["setup_fee"]),
1238
+ usageTiers: Array.isArray(plan["usage_tiers"]) ? plan["usage_tiers"] : [],
1239
+ volumeLimit: plan["volume_limit"] ?? null,
1240
+ contactOnly: plan["contact_only"] === true,
1241
+ checkoutAvailable: plan["checkout_available"] === true,
1242
+ requires: toRequires(plan["requires"]),
1243
+ eligible: plan["eligible"] === true
1244
+ }))
1245
+ }));
1246
+ const coverage = asRecordList2(data["coverage"]).map((row) => ({
1247
+ country: asString4(row["country"]),
1248
+ availability: asString4(row["availability"]),
1249
+ services: asStringList(row["services"]),
1250
+ network: asNullableString4(row["network"]),
1251
+ format: asNullableString4(row["format"]),
1252
+ mandate: asNullableString4(row["mandate"]),
1253
+ support: asString4(row["support"], "yes")
1254
+ }));
1255
+ const access = asRecord3(data["access"]) ?? {};
1256
+ const accessByCountry = {};
1257
+ for (const [country, services] of Object.entries(asRecord3(data["access_by_country"]) ?? {})) {
1258
+ const flags = {};
1259
+ for (const [service, value] of Object.entries(asRecord3(services) ?? {})) {
1260
+ flags[service] = value === true;
1261
+ }
1262
+ accessByCountry[country] = flags;
1263
+ }
1264
+ return new _ComplianceOfferings(
1265
+ offerings,
1266
+ coverage,
1267
+ { eReporting: access["e_reporting"] === true, eInvoicing: access["e_invoicing"] === true },
1268
+ accessByCountry
1269
+ );
1270
+ }
1271
+ };
1272
+
764
1273
  // src/dto/ComplianceEnrollmentPage.ts
765
1274
  var ComplianceEnrollmentPage = class _ComplianceEnrollmentPage {
766
1275
  constructor(rows, currentPage, perPage, total, lastPage) {
@@ -806,17 +1315,17 @@ var ComplianceTaxReportState = {
806
1315
  ERROR: "error",
807
1316
  UNKNOWN: "unknown"
808
1317
  };
809
- var VALUES3 = Object.values(ComplianceTaxReportState);
1318
+ var VALUES4 = Object.values(ComplianceTaxReportState);
810
1319
  function toComplianceTaxReportState(value) {
811
1320
  if (typeof value === "string") {
812
1321
  const normalized = value.trim().toLowerCase();
813
- if (VALUES3.includes(normalized)) {
1322
+ if (VALUES4.includes(normalized)) {
814
1323
  return normalized;
815
1324
  }
816
1325
  }
817
1326
  return ComplianceTaxReportState.UNKNOWN;
818
1327
  }
819
- var DESCRIPTIONS3 = {
1328
+ var DESCRIPTIONS4 = {
820
1329
  [ComplianceTaxReportState.NEW]: "Created at provider, waiting for the daily ledger batch.",
821
1330
  [ComplianceTaxReportState.SENT]: "Ledger transmitted to PPF.",
822
1331
  [ComplianceTaxReportState.ACKNOWLEDGED]: "PPF acknowledged receipt of the ledger.",
@@ -826,20 +1335,20 @@ var DESCRIPTIONS3 = {
826
1335
  [ComplianceTaxReportState.UNKNOWN]: "Unknown state."
827
1336
  };
828
1337
  function describeComplianceTaxReportState(state) {
829
- return DESCRIPTIONS3[state];
1338
+ return DESCRIPTIONS4[state];
830
1339
  }
831
1340
 
832
1341
  // src/dto/ComplianceTaxReport.ts
833
- function asString2(value, fallback = "") {
1342
+ function asString5(value, fallback = "") {
834
1343
  return typeof value === "string" ? value : fallback;
835
1344
  }
836
- function asNullableString2(value) {
1345
+ function asNullableString5(value) {
837
1346
  return typeof value === "string" ? value : null;
838
1347
  }
839
- function asNumber2(value) {
1348
+ function asNumber3(value) {
840
1349
  return typeof value === "number" ? value : Number(value ?? 0) || 0;
841
1350
  }
842
- function asNullableNumber(value) {
1351
+ function asNullableNumber3(value) {
843
1352
  return typeof value === "number" ? value : null;
844
1353
  }
845
1354
  function asMoney(value) {
@@ -890,25 +1399,25 @@ var ComplianceTaxReport = class _ComplianceTaxReport {
890
1399
  updatedAt;
891
1400
  static fromArray(data) {
892
1401
  return new _ComplianceTaxReport(
893
- asNumber2(data["id"]),
894
- asNumber2(data["company_id"]),
895
- asNullableNumber(data["compliance_transaction_id"]),
896
- asNullableNumber(data["ledger_id"]),
897
- asString2(data["country"]),
898
- asString2(data["regime"]),
1402
+ asNumber3(data["id"]),
1403
+ asNumber3(data["company_id"]),
1404
+ asNullableNumber3(data["compliance_transaction_id"]),
1405
+ asNullableNumber3(data["ledger_id"]),
1406
+ asString5(data["country"]),
1407
+ asString5(data["regime"]),
899
1408
  toComplianceTaxReportState(data["state"]),
900
- asString2(data["state_label"]),
1409
+ asString5(data["state_label"]),
901
1410
  data["is_terminal"] === true,
902
- asNullableString2(data["provider_tax_report_id"]),
903
- asNullableString2(data["provider_ledger_id"]),
1411
+ asNullableString5(data["provider_tax_report_id"]),
1412
+ asNullableString5(data["provider_ledger_id"]),
904
1413
  asMoney(data["base_amount"]),
905
1414
  asMoney(data["tax_amount"]),
906
1415
  asMoney(data["total_amount"]),
907
- asNullableString2(data["refusal_reason"]),
908
- asNullableString2(data["registered_at"]),
909
- asNullableString2(data["refused_at"]),
910
- asNullableString2(data["created_at"]),
911
- asNullableString2(data["updated_at"])
1416
+ asNullableString5(data["refusal_reason"]),
1417
+ asNullableString5(data["registered_at"]),
1418
+ asNullableString5(data["refused_at"]),
1419
+ asNullableString5(data["created_at"]),
1420
+ asNullableString5(data["updated_at"])
912
1421
  );
913
1422
  }
914
1423
  };
@@ -954,23 +1463,23 @@ var ComplianceDocumentType = {
954
1463
  CREDIT_NOTE: "credit_note",
955
1464
  UNKNOWN: "unknown"
956
1465
  };
957
- var VALUES4 = Object.values(ComplianceDocumentType);
1466
+ var VALUES5 = Object.values(ComplianceDocumentType);
958
1467
  function toComplianceDocumentType(value) {
959
1468
  if (typeof value === "string") {
960
1469
  const normalized = value.trim().toLowerCase();
961
- if (VALUES4.includes(normalized)) {
1470
+ if (VALUES5.includes(normalized)) {
962
1471
  return normalized;
963
1472
  }
964
1473
  }
965
1474
  return ComplianceDocumentType.UNKNOWN;
966
1475
  }
967
- var DESCRIPTIONS4 = {
1476
+ var DESCRIPTIONS5 = {
968
1477
  [ComplianceDocumentType.INVOICE]: "Commercial invoice.",
969
1478
  [ComplianceDocumentType.CREDIT_NOTE]: "Credit note correcting a previously issued invoice.",
970
1479
  [ComplianceDocumentType.UNKNOWN]: "Unknown document type."
971
1480
  };
972
1481
  function describeComplianceDocumentType(type) {
973
- return DESCRIPTIONS4[type];
1482
+ return DESCRIPTIONS5[type];
974
1483
  }
975
1484
 
976
1485
  // src/enums/ComplianceTransactionState.ts
@@ -979,27 +1488,30 @@ var ComplianceTransactionState = {
979
1488
  SENDING: "sending",
980
1489
  SUBMITTED: "submitted",
981
1490
  ERROR: "error",
1491
+ /** E-invoicing only: the buyer cannot receive e-invoices — nothing was sent or billed; send your regular invoice. */
1492
+ NOT_REQUIRED: "not_required",
982
1493
  UNKNOWN: "unknown"
983
1494
  };
984
- var VALUES5 = Object.values(ComplianceTransactionState);
1495
+ var VALUES6 = Object.values(ComplianceTransactionState);
985
1496
  function toComplianceTransactionState(value) {
986
1497
  if (typeof value === "string") {
987
1498
  const normalized = value.trim().toLowerCase();
988
- if (VALUES5.includes(normalized)) {
1499
+ if (VALUES6.includes(normalized)) {
989
1500
  return normalized;
990
1501
  }
991
1502
  }
992
1503
  return ComplianceTransactionState.UNKNOWN;
993
1504
  }
994
- var DESCRIPTIONS5 = {
1505
+ var DESCRIPTIONS6 = {
995
1506
  [ComplianceTransactionState.PENDING]: "Recorded locally, not yet submitted to the provider.",
996
1507
  [ComplianceTransactionState.SENDING]: "Submission to the provider in progress.",
997
1508
  [ComplianceTransactionState.SUBMITTED]: "Successfully submitted to the provider.",
998
1509
  [ComplianceTransactionState.ERROR]: "Submission failed. See submissionError.",
1510
+ [ComplianceTransactionState.NOT_REQUIRED]: "The buyer cannot receive e-invoices, so none was sent or billed. Send your regular invoice instead.",
999
1511
  [ComplianceTransactionState.UNKNOWN]: "Unknown state."
1000
1512
  };
1001
1513
  function describeComplianceTransactionState(state) {
1002
- return DESCRIPTIONS5[state];
1514
+ return DESCRIPTIONS6[state];
1003
1515
  }
1004
1516
 
1005
1517
  // src/enums/ComplianceTransactionType.ts
@@ -1011,17 +1523,17 @@ var ComplianceTransactionType = {
1011
1523
  CROSSBORDER_INBOUND: "crossborder_inbound",
1012
1524
  UNKNOWN: "unknown"
1013
1525
  };
1014
- var VALUES6 = Object.values(ComplianceTransactionType);
1526
+ var VALUES7 = Object.values(ComplianceTransactionType);
1015
1527
  function toComplianceTransactionType(value) {
1016
1528
  if (typeof value === "string") {
1017
1529
  const normalized = value.trim().toLowerCase();
1018
- if (VALUES6.includes(normalized)) {
1530
+ if (VALUES7.includes(normalized)) {
1019
1531
  return normalized;
1020
1532
  }
1021
1533
  }
1022
1534
  return ComplianceTransactionType.UNKNOWN;
1023
1535
  }
1024
- var DESCRIPTIONS6 = {
1536
+ var DESCRIPTIONS7 = {
1025
1537
  [ComplianceTransactionType.B2C_OUTBOUND]: "Sale to a private person in the merchant country (DGFiP Flux 10.3).",
1026
1538
  [ComplianceTransactionType.B2B_DOMESTIC_OUTBOUND]: "Sale to a domestic business (DGFiP Flux 1, Phase 2).",
1027
1539
  [ComplianceTransactionType.B2B_DOMESTIC_INBOUND]: "Purchase from a domestic business (DGFiP Flux 1, Phase 2).",
@@ -1030,17 +1542,17 @@ var DESCRIPTIONS6 = {
1030
1542
  [ComplianceTransactionType.UNKNOWN]: "Unknown transaction type."
1031
1543
  };
1032
1544
  function describeComplianceTransactionType(type) {
1033
- return DESCRIPTIONS6[type];
1545
+ return DESCRIPTIONS7[type];
1034
1546
  }
1035
1547
 
1036
1548
  // src/dto/ComplianceTransaction.ts
1037
- function asString3(value, fallback = "") {
1549
+ function asString6(value, fallback = "") {
1038
1550
  return typeof value === "string" ? value : fallback;
1039
1551
  }
1040
- function asNullableString3(value) {
1552
+ function asNullableString6(value) {
1041
1553
  return typeof value === "string" ? value : null;
1042
1554
  }
1043
- function asNumber3(value) {
1555
+ function asNumber4(value) {
1044
1556
  return typeof value === "number" ? value : Number(value ?? 0) || 0;
1045
1557
  }
1046
1558
  function asMoney2(value) {
@@ -1048,34 +1560,55 @@ function asMoney2(value) {
1048
1560
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
1049
1561
  return "0.00";
1050
1562
  }
1051
- function asNullableNumber2(value) {
1563
+ function asNullableNumber4(value) {
1052
1564
  if (typeof value === "number" && Number.isFinite(value)) return value;
1053
1565
  if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) return Number(value);
1054
1566
  return null;
1055
1567
  }
1056
- function asRecord(value) {
1568
+ function asRecord4(value) {
1057
1569
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1058
1570
  }
1059
- function asRecordList(value) {
1060
- return Array.isArray(value) ? value.filter((row) => asRecord(row) !== null) : [];
1571
+ function asRecordList3(value) {
1572
+ return Array.isArray(value) ? value.filter((row) => asRecord4(row) !== null) : [];
1573
+ }
1574
+ function toInvoicePdf(value) {
1575
+ const pdf = asRecord4(value);
1576
+ if (pdf === null) return null;
1577
+ return {
1578
+ filename: asNullableString6(pdf["filename"]),
1579
+ size: asNullableNumber4(pdf["size"]),
1580
+ attached: pdf["attached"] === true
1581
+ };
1582
+ }
1583
+ function toEurEquivalent(value) {
1584
+ const eur = asRecord4(value);
1585
+ if (eur === null) return null;
1586
+ return {
1587
+ subtotal: asMoney2(eur["subtotal"]),
1588
+ taxAmount: asMoney2(eur["tax_amount"]),
1589
+ total: asMoney2(eur["total"]),
1590
+ rate: asString6(eur["rate"]),
1591
+ rateDate: asNullableString6(eur["rate_date"]),
1592
+ source: asString6(eur["source"], "ECB")
1593
+ };
1061
1594
  }
1062
1595
  function toInvoiceLines(providerPayload) {
1063
- const invoice = asRecord(asRecord(providerPayload)?.["invoice"]);
1064
- return asRecordList(invoice?.["invoice_lines_attributes"]).map((line) => ({
1065
- description: asString3(line["description"]),
1066
- quantity: asNullableNumber2(line["quantity"]),
1596
+ const invoice = asRecord4(asRecord4(providerPayload)?.["invoice"]);
1597
+ return asRecordList3(invoice?.["invoice_lines_attributes"]).map((line) => ({
1598
+ description: asString6(line["description"]),
1599
+ quantity: asNullableNumber4(line["quantity"]),
1067
1600
  price: asMoney2(line["price"]),
1068
- unit: asNullableNumber2(line["unit"]),
1069
- taxes: asRecordList(line["taxes_attributes"]).map((tax) => ({
1070
- name: asString3(tax["name"]),
1071
- percent: asNullableNumber2(tax["percent"]),
1072
- category: asNullableString3(tax["category"]),
1073
- comment: asNullableString3(tax["comment"])
1601
+ unit: asNullableNumber4(line["unit"]),
1602
+ taxes: asRecordList3(line["taxes_attributes"]).map((tax) => ({
1603
+ name: asString6(tax["name"]),
1604
+ percent: asNullableNumber4(tax["percent"]),
1605
+ category: asNullableString6(tax["category"]),
1606
+ comment: asNullableString6(tax["comment"])
1074
1607
  }))
1075
1608
  }));
1076
1609
  }
1077
1610
  var ComplianceTransaction = class _ComplianceTransaction {
1078
- 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 = []) {
1611
+ 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 = [], documentUrl = null, deliveryCheck = null, invoicePdf = null, internalReference = null, pdfUrl = null, buyerRejected = false, rejectionReason = null, eurEquivalent = null) {
1079
1612
  this.id = id;
1080
1613
  this.companyId = companyId;
1081
1614
  this.complianceEnrollmentId = complianceEnrollmentId;
@@ -1114,6 +1647,14 @@ var ComplianceTransaction = class _ComplianceTransaction {
1114
1647
  this.amendedDate = amendedDate;
1115
1648
  this.providerState = providerState;
1116
1649
  this.invoiceLines = invoiceLines;
1650
+ this.documentUrl = documentUrl;
1651
+ this.deliveryCheck = deliveryCheck;
1652
+ this.invoicePdf = invoicePdf;
1653
+ this.internalReference = internalReference;
1654
+ this.pdfUrl = pdfUrl;
1655
+ this.buyerRejected = buyerRejected;
1656
+ this.rejectionReason = rejectionReason;
1657
+ this.eurEquivalent = eurEquivalent;
1117
1658
  }
1118
1659
  id;
1119
1660
  companyId;
@@ -1153,50 +1694,66 @@ var ComplianceTransaction = class _ComplianceTransaction {
1153
1694
  amendedDate;
1154
1695
  providerState;
1155
1696
  invoiceLines;
1697
+ documentUrl;
1698
+ deliveryCheck;
1699
+ invoicePdf;
1700
+ internalReference;
1701
+ pdfUrl;
1702
+ buyerRejected;
1703
+ rejectionReason;
1704
+ eurEquivalent;
1156
1705
  get isCreditNote() {
1157
1706
  return this.documentType === ComplianceDocumentType.CREDIT_NOTE;
1158
1707
  }
1159
1708
  static fromArray(data) {
1160
1709
  const taxReport = typeof data["tax_report"] === "object" && data["tax_report"] !== null && !Array.isArray(data["tax_report"]) ? ComplianceTaxReport.fromArray(data["tax_report"]) : void 0;
1161
1710
  return new _ComplianceTransaction(
1162
- asNumber3(data["id"]),
1163
- asNumber3(data["company_id"]),
1164
- asNumber3(data["compliance_enrollment_id"]),
1165
- asString3(data["country"]),
1166
- asString3(data["regime"]),
1711
+ asNumber4(data["id"]),
1712
+ asNumber4(data["company_id"]),
1713
+ asNumber4(data["compliance_enrollment_id"]),
1714
+ asString6(data["country"]),
1715
+ asString6(data["regime"]),
1167
1716
  toComplianceTransactionType(data["transaction_type"]),
1168
- asString3(data["transaction_type_label"]),
1717
+ asString6(data["transaction_type_label"]),
1169
1718
  toComplianceTransactionState(data["state"]),
1170
- asString3(data["state_label"]),
1171
- asString3(data["invoice_number"]),
1172
- asNullableString3(data["invoice_date"]),
1173
- asNullableString3(data["due_date"]),
1174
- asString3(data["currency"], "EUR"),
1719
+ asString6(data["state_label"]),
1720
+ asString6(data["invoice_number"]),
1721
+ asNullableString6(data["invoice_date"]),
1722
+ asNullableString6(data["due_date"]),
1723
+ asString6(data["currency"], "EUR"),
1175
1724
  asMoney2(data["subtotal"]),
1176
1725
  asMoney2(data["tax_amount"]),
1177
1726
  asMoney2(data["total"]),
1178
- asNullableString3(data["counterparty_name"]),
1179
- asNullableString3(data["counterparty_country"]),
1180
- asNullableString3(data["counterparty_vat_number"]),
1727
+ asNullableString6(data["counterparty_name"]),
1728
+ asNullableString6(data["counterparty_country"]),
1729
+ asNullableString6(data["counterparty_vat_number"]),
1181
1730
  data["is_paid"] === true,
1182
- asNullableString3(data["paid_at"]),
1183
- asNullableString3(data["provider_invoice_id"]),
1184
- asNullableString3(data["submission_error"]),
1185
- asNullableString3(data["reported_at"]),
1731
+ asNullableString6(data["paid_at"]),
1732
+ asNullableString6(data["provider_invoice_id"]),
1733
+ asNullableString6(data["submission_error"]),
1734
+ asNullableString6(data["reported_at"]),
1186
1735
  taxReport,
1187
- asNullableString3(data["created_at"]),
1188
- asNullableString3(data["updated_at"]),
1189
- asNullableString3(data["counterparty_register_id"]),
1190
- asNullableString3(data["counterparty_address"]),
1191
- asNullableString3(data["counterparty_city"]),
1192
- asNullableString3(data["counterparty_postalcode"]),
1193
- asNullableString3(data["counterparty_email"]),
1194
- asNullableString3(data["buyer_reference"]),
1736
+ asNullableString6(data["created_at"]),
1737
+ asNullableString6(data["updated_at"]),
1738
+ asNullableString6(data["counterparty_register_id"]),
1739
+ asNullableString6(data["counterparty_address"]),
1740
+ asNullableString6(data["counterparty_city"]),
1741
+ asNullableString6(data["counterparty_postalcode"]),
1742
+ asNullableString6(data["counterparty_email"]),
1743
+ asNullableString6(data["buyer_reference"]),
1195
1744
  data["document_type"] === void 0 || data["document_type"] === null ? ComplianceDocumentType.INVOICE : toComplianceDocumentType(data["document_type"]),
1196
- asNullableString3(data["amended_number"]),
1197
- asNullableString3(data["amended_date"]),
1198
- asNullableString3(data["provider_state"]),
1199
- toInvoiceLines(data["provider_payload"])
1745
+ asNullableString6(data["amended_number"]),
1746
+ asNullableString6(data["amended_date"]),
1747
+ asNullableString6(data["provider_state"]),
1748
+ toInvoiceLines(data["provider_payload"]),
1749
+ asNullableString6(data["document_url"]),
1750
+ asRecord4(data["delivery_check"]),
1751
+ toInvoicePdf(data["invoice_pdf"]),
1752
+ asNullableString6(data["internal_reference"]),
1753
+ asNullableString6(data["pdf_url"]),
1754
+ data["buyer_rejected"] === true,
1755
+ asNullableString6(data["rejection_reason"]),
1756
+ toEurEquivalent(data["eur_equivalent"])
1200
1757
  );
1201
1758
  }
1202
1759
  };
@@ -1245,48 +1802,21 @@ var ImportResult = class _ImportResult {
1245
1802
  }
1246
1803
  created;
1247
1804
  skippedDuplicates;
1248
- errors;
1249
- static fromArray(data) {
1250
- const errors = (Array.isArray(data["errors"]) ? data["errors"] : []).filter(
1251
- (row) => typeof row === "string"
1252
- );
1253
- const num = (value) => typeof value === "number" ? value : 0;
1254
- return new _ImportResult(num(data["created"]), num(data["skipped_duplicates"]), errors);
1255
- }
1256
- };
1257
-
1258
- // src/enums/PeppolLookupStatus.ts
1259
- var PeppolLookupStatus = {
1260
- REACHABLE: "reachable",
1261
- NOT_REACHABLE: "not_reachable",
1262
- PENDING: "pending",
1263
- UNKNOWN: "unknown"
1264
- };
1265
- var VALUES7 = Object.values(PeppolLookupStatus);
1266
- function toPeppolLookupStatus(value) {
1267
- if (typeof value === "string") {
1268
- const normalized = value.trim().toLowerCase();
1269
- if (VALUES7.includes(normalized)) {
1270
- return normalized;
1271
- }
1805
+ errors;
1806
+ static fromArray(data) {
1807
+ const errors = (Array.isArray(data["errors"]) ? data["errors"] : []).filter(
1808
+ (row) => typeof row === "string"
1809
+ );
1810
+ const num = (value) => typeof value === "number" ? value : 0;
1811
+ return new _ImportResult(num(data["created"]), num(data["skipped_duplicates"]), errors);
1272
1812
  }
1273
- return PeppolLookupStatus.UNKNOWN;
1274
- }
1275
- var DESCRIPTIONS7 = {
1276
- [PeppolLookupStatus.REACHABLE]: "The participant is registered on Peppol and can receive e-invoices.",
1277
- [PeppolLookupStatus.NOT_REACHABLE]: "The participant is not registered on Peppol.",
1278
- [PeppolLookupStatus.PENDING]: "The directory lookup is still resolving \u2014 ask again in a few seconds.",
1279
- [PeppolLookupStatus.UNKNOWN]: "Unknown lookup status."
1280
1813
  };
1281
- function describePeppolLookupStatus(status) {
1282
- return DESCRIPTIONS7[status];
1283
- }
1284
1814
 
1285
1815
  // src/dto/PeppolLookupResult.ts
1286
- function asString4(value, fallback = "") {
1816
+ function asString7(value, fallback = "") {
1287
1817
  return typeof value === "string" ? value : fallback;
1288
1818
  }
1289
- function asNullableString4(value) {
1819
+ function asNullableString7(value) {
1290
1820
  return typeof value === "string" ? value : null;
1291
1821
  }
1292
1822
  var PeppolLookupResult = class _PeppolLookupResult {
@@ -1317,21 +1847,21 @@ var PeppolLookupResult = class _PeppolLookupResult {
1317
1847
  return new _PeppolLookupResult(
1318
1848
  toPeppolLookupStatus(data["status"]),
1319
1849
  typeof data["reachable"] === "boolean" ? data["reachable"] : null,
1320
- asString4(data["country"]),
1321
- asString4(data["scheme"]),
1322
- asString4(data["id"]),
1850
+ asString7(data["country"]),
1851
+ asString7(data["scheme"]),
1852
+ asString7(data["id"]),
1323
1853
  documentTypes,
1324
- asNullableString4(data["transport_type_code"]),
1325
- asNullableString4(data["checked_at"])
1854
+ asNullableString7(data["transport_type_code"]),
1855
+ asNullableString7(data["checked_at"])
1326
1856
  );
1327
1857
  }
1328
1858
  };
1329
1859
 
1330
1860
  // src/dto/RegistryCompany.ts
1331
- function asString5(value, fallback = "") {
1861
+ function asString8(value, fallback = "") {
1332
1862
  return typeof value === "string" ? value : fallback;
1333
1863
  }
1334
- function asNullableString5(value) {
1864
+ function asNullableString8(value) {
1335
1865
  return typeof value === "string" ? value : null;
1336
1866
  }
1337
1867
  var RegistryCompany = class _RegistryCompany {
@@ -1363,27 +1893,27 @@ var RegistryCompany = class _RegistryCompany {
1363
1893
  country;
1364
1894
  static fromArray(data) {
1365
1895
  return new _RegistryCompany(
1366
- asString5(data["org_number"]),
1367
- asString5(data["company_name"]),
1368
- asNullableString5(data["organisation_form"]),
1896
+ asString8(data["org_number"]),
1897
+ asString8(data["company_name"]),
1898
+ asNullableString8(data["organisation_form"]),
1369
1899
  data["vat_registered"] === true,
1370
- asNullableString5(data["vat_number"]),
1900
+ asNullableString8(data["vat_number"]),
1371
1901
  data["enterprise_register"] === true,
1372
1902
  data["bankrupt"] === true,
1373
1903
  data["under_liquidation"] === true,
1374
- asNullableString5(data["address"]),
1375
- asNullableString5(data["postalcode"]),
1376
- asNullableString5(data["city"]),
1377
- asString5(data["country"])
1904
+ asNullableString8(data["address"]),
1905
+ asNullableString8(data["postalcode"]),
1906
+ asNullableString8(data["city"]),
1907
+ asString8(data["country"])
1378
1908
  );
1379
1909
  }
1380
1910
  };
1381
1911
 
1382
1912
  // src/dto/RevenueStatistics.ts
1383
- function asString6(value, fallback = "") {
1913
+ function asString9(value, fallback = "") {
1384
1914
  return typeof value === "string" ? value : fallback;
1385
1915
  }
1386
- function asNumber4(value) {
1916
+ function asNumber5(value) {
1387
1917
  return typeof value === "number" ? value : Number(value ?? 0) || 0;
1388
1918
  }
1389
1919
  function asMoney3(value) {
@@ -1395,7 +1925,7 @@ function asRecords(value) {
1395
1925
  }
1396
1926
  function toTotals(data) {
1397
1927
  return {
1398
- count: asNumber4(data["count"]),
1928
+ count: asNumber5(data["count"]),
1399
1929
  subtotal: asMoney3(data["subtotal"]),
1400
1930
  taxAmount: asMoney3(data["tax_amount"]),
1401
1931
  total: asMoney3(data["total"])
@@ -1432,35 +1962,35 @@ var RevenueStatistics = class _RevenueStatistics {
1432
1962
  typeof data["totals"] === "object" && data["totals"] !== null ? data["totals"] : {}
1433
1963
  );
1434
1964
  const timeSeries = asRecords(data["time_series"]).map((row) => ({
1435
- bucket: asString6(row["bucket"]),
1965
+ bucket: asString9(row["bucket"]),
1436
1966
  ...toTotals(row)
1437
1967
  }));
1438
1968
  const byType = asRecords(data["by_type"]).map((row) => ({
1439
- type: asString6(row["type"]),
1440
- typeLabel: asString6(row["type_label"]),
1441
- count: asNumber4(row["count"]),
1969
+ type: asString9(row["type"]),
1970
+ typeLabel: asString9(row["type_label"]),
1971
+ count: asNumber5(row["count"]),
1442
1972
  total: asMoney3(row["total"])
1443
1973
  }));
1444
1974
  const byCountry = asRecords(data["by_country"]).map((row) => ({
1445
- country: asString6(row["country"], "unknown"),
1446
- count: asNumber4(row["count"]),
1975
+ country: asString9(row["country"], "unknown"),
1976
+ count: asNumber5(row["count"]),
1447
1977
  total: asMoney3(row["total"])
1448
1978
  }));
1449
1979
  const byState = asRecords(data["by_state"]).map((row) => ({
1450
- state: asString6(row["state"]),
1451
- stateLabel: asString6(row["state_label"]),
1452
- count: asNumber4(row["count"]),
1980
+ state: asString9(row["state"]),
1981
+ stateLabel: asString9(row["state_label"]),
1982
+ count: asNumber5(row["count"]),
1453
1983
  total: asMoney3(row["total"])
1454
1984
  }));
1455
1985
  const byCurrency = asRecords(data["by_currency"]).map((row) => ({
1456
- currency: asString6(row["currency"]),
1986
+ currency: asString9(row["currency"]),
1457
1987
  ...toTotals(row)
1458
1988
  }));
1459
1989
  return new _RevenueStatistics(
1460
- asString6(range["date_from"]) || void 0,
1461
- asString6(range["date_to"]) || void 0,
1462
- asString6(range["interval"]) || void 0,
1463
- asString6(data["primary_currency"], "EUR"),
1990
+ asString9(range["date_from"]) || void 0,
1991
+ asString9(range["date_to"]) || void 0,
1992
+ asString9(range["interval"]) || void 0,
1993
+ asString9(data["primary_currency"], "EUR"),
1464
1994
  data["is_multi_currency"] === true,
1465
1995
  totals,
1466
1996
  timeSeries,
@@ -1473,10 +2003,10 @@ var RevenueStatistics = class _RevenueStatistics {
1473
2003
  };
1474
2004
 
1475
2005
  // src/dto/SireneLookupResult.ts
1476
- function asString7(value, fallback = "") {
2006
+ function asString10(value, fallback = "") {
1477
2007
  return typeof value === "string" ? value : fallback;
1478
2008
  }
1479
- function asNullableString6(value) {
2009
+ function asNullableString9(value) {
1480
2010
  return typeof value === "string" ? value : null;
1481
2011
  }
1482
2012
  var SireneLookupResult = class _SireneLookupResult {
@@ -1502,24 +2032,24 @@ var SireneLookupResult = class _SireneLookupResult {
1502
2032
  postalcode;
1503
2033
  static fromArray(data) {
1504
2034
  return new _SireneLookupResult(
1505
- asString7(data["company_name"]),
1506
- asString7(data["siren"]),
1507
- asNullableString6(data["siret"]),
1508
- asNullableString6(data["naf_code"]),
1509
- asNullableString6(data["naf_full"]),
1510
- asNullableString6(data["enterprise_size"]),
1511
- asNullableString6(data["address"]),
1512
- asNullableString6(data["city"]),
1513
- asNullableString6(data["postalcode"])
2035
+ asString10(data["company_name"]),
2036
+ asString10(data["siren"]),
2037
+ asNullableString9(data["siret"]),
2038
+ asNullableString9(data["naf_code"]),
2039
+ asNullableString9(data["naf_full"]),
2040
+ asNullableString9(data["enterprise_size"]),
2041
+ asNullableString9(data["address"]),
2042
+ asNullableString9(data["city"]),
2043
+ asNullableString9(data["postalcode"])
1514
2044
  );
1515
2045
  }
1516
2046
  };
1517
2047
 
1518
2048
  // src/dto/VatRates.ts
1519
- function asString8(value, fallback = "") {
2049
+ function asString11(value, fallback = "") {
1520
2050
  return typeof value === "string" ? value : fallback;
1521
2051
  }
1522
- function asNumber5(value) {
2052
+ function asNumber6(value) {
1523
2053
  return typeof value === "number" ? value : Number(value ?? 0) || 0;
1524
2054
  }
1525
2055
  var VatRates = class _VatRates {
@@ -1533,13 +2063,13 @@ var VatRates = class _VatRates {
1533
2063
  rates;
1534
2064
  static fromArray(data) {
1535
2065
  const rates = (Array.isArray(data["rates"]) ? data["rates"] : []).filter((row) => typeof row === "object" && row !== null).map((row) => ({
1536
- percent: asNumber5(row["percent"]),
1537
- category: asString8(row["category"]),
1538
- labelKey: asString8(row["label_key"]),
1539
- taxName: asString8(row["tax_name"]),
2066
+ percent: asNumber6(row["percent"]),
2067
+ category: asString11(row["category"]),
2068
+ labelKey: asString11(row["label_key"]),
2069
+ taxName: asString11(row["tax_name"]),
1540
2070
  requiresVatex: row["requires_vatex"] === true
1541
2071
  }));
1542
- return new _VatRates(asString8(data["country"]), asString8(data["currency"], "EUR"), rates);
2072
+ return new _VatRates(asString11(data["country"]), asString11(data["currency"], "EUR"), rates);
1543
2073
  }
1544
2074
  };
1545
2075
 
@@ -1593,15 +2123,39 @@ var NorwegianOrgNumber = {
1593
2123
  }
1594
2124
  };
1595
2125
 
2126
+ // src/enums/ComplianceTaxCategory.ts
2127
+ var ComplianceTaxCategory = {
2128
+ STANDARD: "S",
2129
+ REDUCED: "AA",
2130
+ SUPER_REDUCED: "AAA",
2131
+ INTRA_COMMUNITY: "K",
2132
+ EXPORT: "G",
2133
+ REVERSE_CHARGE: "AE",
2134
+ EXEMPT: "E",
2135
+ ZERO: "Z"
2136
+ };
2137
+ var CODES = Object.values(ComplianceTaxCategory);
2138
+ function parseComplianceTaxCategory(value) {
2139
+ if (typeof value !== "string") return null;
2140
+ const code = value.trim().toUpperCase();
2141
+ return CODES.includes(code) ? code : null;
2142
+ }
2143
+ var COMPLIANCE_TAX_CATEGORY_CODES = CODES;
2144
+
1596
2145
  // src/endpoints/EReportingEndpoint.ts
1597
2146
  var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
1598
2147
  var NAF_CODE_REGEX = /^\d{2}$/;
1599
2148
  var COUNTRY_REGEX = /^[A-Za-z]{2}$/;
1600
2149
  var PEPPOL_SCHEME_REGEX = /^\d{4}$/;
2150
+ var INVOICE_PDF_MAX_BYTES = 10 * 1024 * 1024;
2151
+ var INVOICE_PDF_MAGIC = [37, 80, 68, 70, 45];
1601
2152
  var INTERVALS = ["day", "week", "month"];
1602
2153
  var SERVICES = ["e_reporting", "e_invoicing"];
1603
2154
  var ENTERPRISE_SIZES = ["micro", "pme", "eti", "ge"];
1604
2155
  var TYPE_OPERATIONS = ["services", "goods", "mixed"];
2156
+ var NOTIFICATION_LANGUAGES = ["de", "en"];
2157
+ var NOTIFICATION_DIGESTS = ["off", "daily", "weekly"];
2158
+ var NOTIFICATION_MAX_RECIPIENTS = 5;
1605
2159
  var TRANSACTION_TYPES = [
1606
2160
  "b2c_outbound",
1607
2161
  "b2b_domestic_outbound",
@@ -1772,6 +2326,19 @@ var EReportingEndpoint = class {
1772
2326
  const data = await this.parseJsonResponse(response);
1773
2327
  return PeppolLookupResult.fromArray(data);
1774
2328
  }
2329
+ /**
2330
+ * What to do with an invoice to this buyer: peppolLookup() turned into an action,
2331
+ * e.g. in a shop checkout when a business buyer enters its number.
2332
+ *
2333
+ * - `einvoice`: create the transaction, Taxora sends the e-invoice;
2334
+ * - `regular_invoice`: the buyer is not on the network — send your regular invoice
2335
+ * yourself (a transaction would end as not_required and is not billed);
2336
+ * - `einvoice_pending`: no definite answer yet — create the transaction anyway,
2337
+ * Taxora asks the directory again before sending.
2338
+ */
2339
+ async decideDelivery(country, id, scheme) {
2340
+ return ComplianceDeliveryDecision.fromLookup(await this.peppolLookup(country, id, scheme));
2341
+ }
1775
2342
  // ---------------------------------------------------------------------
1776
2343
  // Transactions
1777
2344
  // ---------------------------------------------------------------------
@@ -1796,6 +2363,9 @@ var EReportingEndpoint = class {
1796
2363
  this.validateInvoiceLines(input.invoiceLines);
1797
2364
  this.validateOptionalCodes(input);
1798
2365
  this.validateBuyerFields(input);
2366
+ this.validateInternalReference(input.internalReference);
2367
+ this.validateInvoicePdfFilename(input);
2368
+ this.validateDocument(input);
1799
2369
  const body = {
1800
2370
  compliance_enrollment_id: input.complianceEnrollmentId,
1801
2371
  transaction_type: input.transactionType,
@@ -1811,23 +2381,104 @@ var EReportingEndpoint = class {
1811
2381
  const data = await this.parseJsonResponse(response);
1812
2382
  return ComplianceTransaction.fromArray(data);
1813
2383
  }
1814
- /** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type / enrollment / country. */
1815
- async listTransactions(filters = {}) {
1816
- if (filters.complianceEnrollmentId !== void 0) {
1817
- this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
2384
+ /**
2385
+ * Issue a credit note for an invoice you sent through Taxora. Everything that must
2386
+ * match the corrected invoice is taken from `original`: enrollment, transaction type,
2387
+ * currency, buyer (name, register id, address, e-mail, VAT number, buyer reference) —
2388
+ * and the reference to the corrected invoice (number and date) is set for you.
2389
+ *
2390
+ * Without invoiceLines and amounts it credits the whole invoice: lines and amounts are
2391
+ * taken over as they are (the document type, not the sign, makes it a credit note).
2392
+ * For a partial credit pass invoiceLines, subtotal, taxAmount and total together.
2393
+ *
2394
+ * Not possible for an invoice that was never sent to the buyer (state not_required)
2395
+ * or for a credit note. A buyer rejected the invoice? Correct it with a credit note and
2396
+ * send a new invoice (`original.buyerRejected`).
2397
+ */
2398
+ async createCreditNote(original, input) {
2399
+ if (original.documentType === ComplianceDocumentType.CREDIT_NOTE) {
2400
+ throw new TaxoraException("A credit note cannot be credited again; issue a new invoice instead.");
1818
2401
  }
1819
- if (filters.country !== void 0 && !COUNTRY_REGEX.test(filters.country)) {
1820
- throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
2402
+ if (original.state === ComplianceTransactionState.NOT_REQUIRED) {
2403
+ throw new TaxoraException(
2404
+ "The invoice was never sent as an e-invoice (the buyer is not on the network), so there is nothing to credit; correct your regular invoice instead."
2405
+ );
1821
2406
  }
1822
- const params = new URLSearchParams();
1823
- if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
1824
- if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
1825
- if (filters.state !== void 0) params.set("state", filters.state);
1826
- if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
1827
- if (filters.complianceEnrollmentId !== void 0) {
1828
- params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
2407
+ if (original.invoiceNumber === "") {
2408
+ throw new TaxoraException("The original transaction has no invoice number.");
1829
2409
  }
1830
- if (filters.country !== void 0) params.set("country", filters.country.toUpperCase());
2410
+ if (original.transactionType === "unknown") {
2411
+ throw new TaxoraException("The original transaction has an unknown transaction type.");
2412
+ }
2413
+ const partial = [input.invoiceLines, input.subtotal, input.taxAmount, input.total];
2414
+ if (![0, 4].includes(partial.filter((value) => value !== void 0).length)) {
2415
+ throw new TaxoraException("For a partial credit pass invoiceLines, subtotal, taxAmount and total together.");
2416
+ }
2417
+ let invoiceLines;
2418
+ let subtotal = input.subtotal;
2419
+ let taxAmount = input.taxAmount;
2420
+ let total = input.total;
2421
+ if (input.invoiceLines !== void 0) {
2422
+ invoiceLines = input.invoiceLines;
2423
+ } else {
2424
+ invoiceLines = original.invoiceLines.map((line) => ({
2425
+ description: line.description,
2426
+ quantity: line.quantity ?? 0,
2427
+ price: Number(line.price),
2428
+ ...line.unit !== null ? { unit: line.unit } : {},
2429
+ taxes: line.taxes.map((tax) => ({
2430
+ name: tax.name,
2431
+ percent: tax.percent ?? 0,
2432
+ category: this.storedTaxCategory(tax.category),
2433
+ ...tax.comment !== null ? { comment: tax.comment } : {}
2434
+ }))
2435
+ }));
2436
+ if (invoiceLines.length === 0) {
2437
+ throw new TaxoraException(
2438
+ "The original transaction carries no invoice lines; pass invoiceLines and the amounts explicitly."
2439
+ );
2440
+ }
2441
+ subtotal = Number(original.subtotal);
2442
+ taxAmount = Number(original.taxAmount);
2443
+ total = Number(original.total);
2444
+ }
2445
+ const buyer = Object.fromEntries(
2446
+ Object.entries({
2447
+ counterpartyName: original.counterpartyName,
2448
+ counterpartyCountry: original.counterpartyCountry,
2449
+ counterpartyVatNumber: original.counterpartyVatNumber,
2450
+ counterpartyRegisterId: original.counterpartyRegisterId,
2451
+ counterpartyAddress: original.counterpartyAddress,
2452
+ counterpartyCity: original.counterpartyCity,
2453
+ counterpartyPostalcode: original.counterpartyPostalcode,
2454
+ counterpartyEmail: original.counterpartyEmail,
2455
+ buyerReference: original.buyerReference
2456
+ }).filter(([, value]) => value !== null && value !== "")
2457
+ );
2458
+ return this.createTransaction({
2459
+ complianceEnrollmentId: original.complianceEnrollmentId,
2460
+ transactionType: original.transactionType,
2461
+ invoiceNumber: input.creditNoteNumber,
2462
+ invoiceDate: input.invoiceDate ?? /* @__PURE__ */ new Date(),
2463
+ subtotal: subtotal ?? 0,
2464
+ total: total ?? 0,
2465
+ invoiceLines,
2466
+ currency: original.currency,
2467
+ ...taxAmount !== void 0 ? { taxAmount } : {},
2468
+ ...buyer,
2469
+ isCreditNote: true,
2470
+ amendedNumber: original.invoiceNumber,
2471
+ ...original.invoiceDate !== null ? { amendedDate: original.invoiceDate } : {},
2472
+ ...input.extraInfo !== void 0 ? { extraInfo: input.extraInfo } : {},
2473
+ ...input.internalReference !== void 0 ? { internalReference: input.internalReference } : {},
2474
+ ...input.submitNow !== void 0 ? { submitNow: input.submitNow } : {},
2475
+ ...input.invoicePdf !== void 0 ? { invoicePdf: input.invoicePdf } : {},
2476
+ ...input.invoicePdfFilename !== void 0 ? { invoicePdfFilename: input.invoicePdfFilename } : {}
2477
+ });
2478
+ }
2479
+ /** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type / enrollment / country / search / internal reference / provider state. */
2480
+ async listTransactions(filters = {}) {
2481
+ const params = this.buildTransactionFilterParams(filters);
1831
2482
  if (filters.page !== void 0) params.set("page", String(filters.page));
1832
2483
  if (filters.perPage !== void 0) params.set("per_page", String(filters.perPage));
1833
2484
  const queryString = params.toString();
@@ -1835,6 +2486,27 @@ var EReportingEndpoint = class {
1835
2486
  const response = await this.sendRequest("GET", url);
1836
2487
  return ComplianceTransactionPage.fromResponse(await this.parseJsonResponse(response));
1837
2488
  }
2489
+ /**
2490
+ * All transactions matching the filters as raw CSV for bookkeeping
2491
+ * (semicolon-separated) — the same filters as listTransactions() without
2492
+ * pagination. Columns: invoice_number, internal_reference, invoice_date,
2493
+ * due_date, country, transaction_type, document_type, state, delivery_state,
2494
+ * buyer_rejected, rejection_reason, counterparty_name, counterparty_country,
2495
+ * counterparty_vat_number, currency, subtotal, tax_amount, total, eur_rate,
2496
+ * eur_rate_date, eur_subtotal, eur_tax_amount, eur_total, provider_invoice_id,
2497
+ * created_at. The eur_* columns hold the ECB reference conversion for non-EUR
2498
+ * invoices (empty for EUR). Max 50,000 rows server-side. The body is returned
2499
+ * verbatim, a UTF-8 BOM included when the API sends one.
2500
+ */
2501
+ async exportTransactionsCsv(filters = {}) {
2502
+ const queryString = this.buildTransactionFilterParams(filters).toString();
2503
+ const url = `${this.baseUrl}/compliance/transactions/export.csv${queryString ? `?${queryString}` : ""}`;
2504
+ const response = await this.sendDownloadRequest(url, "text/csv, application/json");
2505
+ if (!response.ok) {
2506
+ await this.parseJsonResponse(response);
2507
+ }
2508
+ return new TextDecoder("utf-8", { ignoreBOM: true }).decode(await response.arrayBuffer());
2509
+ }
1838
2510
  /** Get a single transaction by id (includes its tax report when one exists). */
1839
2511
  async getTransaction(id) {
1840
2512
  this.validateId(id, "id");
@@ -1850,6 +2522,8 @@ var EReportingEndpoint = class {
1850
2522
  if (input.invoiceLines !== void 0) this.validateInvoiceLines(input.invoiceLines);
1851
2523
  this.validateOptionalCodes(input);
1852
2524
  this.validateBuyerFields(input);
2525
+ this.validateInternalReference(input.internalReference);
2526
+ this.validateInvoicePdfFilename(input);
1853
2527
  const body = {};
1854
2528
  if (input.transactionType !== void 0) body["transaction_type"] = input.transactionType;
1855
2529
  if (input.invoiceNumber !== void 0) body["invoice_number"] = input.invoiceNumber;
@@ -1884,6 +2558,47 @@ var EReportingEndpoint = class {
1884
2558
  const data = await this.parseJsonResponse(response);
1885
2559
  return ComplianceTransaction.fromArray(data);
1886
2560
  }
2561
+ /**
2562
+ * Download the legal e-invoice document of a sent transaction (e-invoicing,
2563
+ * e.g. the EHF/UBL XML for Norway). Available once `documentUrl` on the
2564
+ * transaction is set.
2565
+ *
2566
+ * Errors: 404 (HttpException) for an unknown transaction; 409 (HttpException)
2567
+ * when no document exists (yet) — e.g. e-reporting, not sent, or the network
2568
+ * has not produced it yet (try again in a few minutes); 502 on provider failure.
2569
+ */
2570
+ async getTransactionDocument(id) {
2571
+ this.validateId(id, "id");
2572
+ const response = await this.sendDownloadRequest(`${this.baseUrl}/compliance/transactions/${id}/document`, "*/*");
2573
+ if (!response.ok) {
2574
+ await this.parseJsonResponse(response);
2575
+ }
2576
+ return {
2577
+ content: new Uint8Array(await response.arrayBuffer()),
2578
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
2579
+ filename: this.filenameFromDisposition(response.headers.get("content-disposition"))
2580
+ };
2581
+ }
2582
+ /**
2583
+ * Download the invoice as a readable PDF — the seller's attached PDF, or the
2584
+ * provider's rendering of the e-invoice. Available once `pdfUrl` on the
2585
+ * transaction is set.
2586
+ *
2587
+ * Errors: 404 (HttpException) for an unknown transaction; 409 (HttpException)
2588
+ * when no PDF exists (yet); 502 on provider failure.
2589
+ */
2590
+ async getTransactionPdf(id) {
2591
+ this.validateId(id, "id");
2592
+ const response = await this.sendDownloadRequest(`${this.baseUrl}/compliance/transactions/${id}/pdf`, "*/*");
2593
+ if (!response.ok) {
2594
+ await this.parseJsonResponse(response);
2595
+ }
2596
+ return {
2597
+ content: new Uint8Array(await response.arrayBuffer()),
2598
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
2599
+ filename: this.filenameFromDisposition(response.headers.get("content-disposition"))
2600
+ };
2601
+ }
1887
2602
  /**
1888
2603
  * Bulk-import transactions from a semicolon-separated CSV (one row per invoice
1889
2604
  * line; rows sharing an invoice_number are grouped into one transaction).
@@ -1915,6 +2630,9 @@ var EReportingEndpoint = class {
1915
2630
  * months and a monthly interval.
1916
2631
  */
1917
2632
  async getRevenueStatistics(filters = {}) {
2633
+ if (filters.complianceEnrollmentId !== void 0) {
2634
+ this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
2635
+ }
1918
2636
  if (filters.interval !== void 0 && !INTERVALS.includes(filters.interval)) {
1919
2637
  throw new TaxoraException(
1920
2638
  `Invalid statistics interval "${String(filters.interval)}". Expected one of: ${INTERVALS.join(", ")}.`
@@ -1926,12 +2644,42 @@ var EReportingEndpoint = class {
1926
2644
  if (filters.interval !== void 0) params.set("interval", filters.interval);
1927
2645
  if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
1928
2646
  if (filters.state !== void 0) params.set("state", filters.state);
2647
+ if (filters.complianceEnrollmentId !== void 0) {
2648
+ params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
2649
+ }
1929
2650
  const queryString = params.toString();
1930
2651
  const url = `${this.baseUrl}/compliance/revenue-statistics${queryString ? `?${queryString}` : ""}`;
1931
2652
  const response = await this.sendRequest("GET", url);
1932
2653
  const data = await this.parseJsonResponse(response);
1933
2654
  return RevenueStatistics.fromArray(data);
1934
2655
  }
2656
+ /**
2657
+ * Net and VAT per month, currency and tax rate as raw CSV (semicolon-separated,
2658
+ * computed from the invoice lines) — for reconciling the reported figures with
2659
+ * your VAT return. Same filters as getRevenueStatistics() (without
2660
+ * `interval`); defaults to the last 12 months. The body is returned verbatim,
2661
+ * a UTF-8 BOM included when the API sends one.
2662
+ */
2663
+ async exportRevenueTaxRatesCsv(filters = {}) {
2664
+ if (filters.complianceEnrollmentId !== void 0) {
2665
+ this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
2666
+ }
2667
+ const params = new URLSearchParams();
2668
+ if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
2669
+ if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
2670
+ if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
2671
+ if (filters.state !== void 0) params.set("state", filters.state);
2672
+ if (filters.complianceEnrollmentId !== void 0) {
2673
+ params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
2674
+ }
2675
+ const queryString = params.toString();
2676
+ const url = `${this.baseUrl}/compliance/revenue-statistics/tax-rates.csv${queryString ? `?${queryString}` : ""}`;
2677
+ const response = await this.sendDownloadRequest(url, "text/csv, application/json");
2678
+ if (!response.ok) {
2679
+ await this.parseJsonResponse(response);
2680
+ }
2681
+ return new TextDecoder("utf-8", { ignoreBOM: true }).decode(await response.arrayBuffer());
2682
+ }
1935
2683
  /**
1936
2684
  * The canonical VAT rates & DGFiP tax categories for a reporting country
1937
2685
  * (the seller's reporting-country rates, not the counterparty's). Falls back
@@ -1946,12 +2694,19 @@ var EReportingEndpoint = class {
1946
2694
  const data = await this.parseJsonResponse(response);
1947
2695
  return VatRates.fromArray(data);
1948
2696
  }
1949
- /** Paginated list of the company's tax reports (newest first), optionally filtered by state. */
1950
- async listTaxReports(page = 1, perPage = 25, state) {
2697
+ /**
2698
+ * Paginated list of the company's tax reports (newest first), optionally
2699
+ * filtered by state and by country (2-letter ISO code, case-insensitive).
2700
+ */
2701
+ async listTaxReports(page = 1, perPage = 25, state, country) {
2702
+ if (country !== void 0 && !COUNTRY_REGEX.test(country)) {
2703
+ throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
2704
+ }
1951
2705
  const params = new URLSearchParams();
1952
2706
  params.set("page", String(page));
1953
2707
  params.set("per_page", String(perPage));
1954
2708
  if (state !== void 0) params.set("state", state);
2709
+ if (country !== void 0) params.set("country", country.toUpperCase());
1955
2710
  const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/tax-reports?${params.toString()}`);
1956
2711
  return ComplianceTaxReportPage.fromResponse(await this.parseJsonResponse(response));
1957
2712
  }
@@ -1993,8 +2748,177 @@ var EReportingEndpoint = class {
1993
2748
  await this.parseJsonResponse(response);
1994
2749
  }
1995
2750
  // ---------------------------------------------------------------------
2751
+ // Self-service plans (Stripe) — not gated by compliance access
2752
+ // ---------------------------------------------------------------------
2753
+ /**
2754
+ * Bookable compliance plans per country (net EUR prices), every country
2755
+ * Taxora reaches, and which services the company has unlocked. Works for
2756
+ * companies without compliance access.
2757
+ */
2758
+ async getOfferings() {
2759
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/offerings`);
2760
+ return ComplianceOfferings.fromArray(await this.parseJsonResponse(response));
2761
+ }
2762
+ /**
2763
+ * Start a Stripe Checkout for a plan (see getOfferings() — `checkoutAvailable`).
2764
+ * Open the returned `url` in the browser. After payment Stripe redirects to
2765
+ * the Taxora portal, which activates the plan; call confirmCheckout() with the
2766
+ * session id to activate it from your side (idempotent).
2767
+ *
2768
+ * Errors: 422 (ValidationException) for an unknown or contact-only plan or a
2769
+ * plan that is already active; 502 (HttpException) on a Stripe error.
2770
+ */
2771
+ async createCheckout(country, plan) {
2772
+ if (!COUNTRY_REGEX.test(country)) {
2773
+ throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
2774
+ }
2775
+ if (plan.trim() === "" || plan.length > 30) {
2776
+ throw new TaxoraException("plan must be a non-empty plan key of at most 30 characters.");
2777
+ }
2778
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/checkout`, {
2779
+ country: country.toUpperCase(),
2780
+ plan
2781
+ });
2782
+ const data = await this.parseJsonResponse(response);
2783
+ return { id: String(data["id"] ?? ""), url: String(data["url"] ?? "") };
2784
+ }
2785
+ /** Confirm a finished Stripe Checkout and activate its plan. Idempotent. */
2786
+ async confirmCheckout(sessionId) {
2787
+ if (sessionId.trim() === "" || sessionId.length > 255) {
2788
+ throw new TaxoraException("sessionId must be a non-empty string of at most 255 characters.");
2789
+ }
2790
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/checkout/confirm`, {
2791
+ session_id: sessionId
2792
+ });
2793
+ const data = await this.parseJsonResponse(response);
2794
+ const str = (value) => typeof value === "string" ? value : null;
2795
+ return {
2796
+ active: data["active"] === true,
2797
+ country: str(data["country"]),
2798
+ service: str(data["service"]),
2799
+ plan: str(data["plan"]),
2800
+ status: str(data["status"])
2801
+ };
2802
+ }
2803
+ /**
2804
+ * URL of the Stripe billing portal for the company's compliance plans
2805
+ * (invoices, payment method, cancellation). The portal returns to the Taxora
2806
+ * portal. 404 (HttpException) when no plan was bought via Stripe.
2807
+ */
2808
+ async createBillingPortalSession() {
2809
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/billing-portal`);
2810
+ const data = await this.parseJsonResponse(response);
2811
+ return String(data["url"] ?? "");
2812
+ }
2813
+ // ---------------------------------------------------------------------
2814
+ // Notification emails
2815
+ // ---------------------------------------------------------------------
2816
+ /**
2817
+ * The company's opt-in e-reporting / e-invoicing emails: recipients, language,
2818
+ * the "buyer not on Peppol" email and the digest. Readable by every member.
2819
+ */
2820
+ async getNotificationSettings() {
2821
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/notification-settings`);
2822
+ return this.toNotificationSettings(await this.parseJsonResponse(response));
2823
+ }
2824
+ /**
2825
+ * Change the company's e-reporting / e-invoicing emails; only the provided
2826
+ * fields change. `notOnPeppol` only reports invoices held back after it was
2827
+ * switched on.
2828
+ *
2829
+ * Errors: 422 (ValidationException) when an email is switched on without any
2830
+ * recipient; 403 (HttpException, body code `admin_only` or `read_only_role`)
2831
+ * for users who are not company admins.
2832
+ */
2833
+ async updateNotificationSettings(input) {
2834
+ const body = {};
2835
+ if (input.recipients !== void 0) {
2836
+ if (!Array.isArray(input.recipients)) {
2837
+ throw new TaxoraException("recipients must be an array of email addresses.");
2838
+ }
2839
+ if (input.recipients.length > NOTIFICATION_MAX_RECIPIENTS) {
2840
+ throw new TaxoraException(`recipients must not contain more than ${NOTIFICATION_MAX_RECIPIENTS} entries.`);
2841
+ }
2842
+ body["recipients"] = input.recipients.map((recipient, index) => {
2843
+ const email = typeof recipient === "string" ? recipient.trim() : recipient;
2844
+ this.validateEmail(email, `recipients[${index}]`);
2845
+ if (email.length > 191) {
2846
+ throw new TaxoraException(`recipients[${index}] must not exceed 191 characters.`);
2847
+ }
2848
+ return email;
2849
+ });
2850
+ }
2851
+ if (input.language !== void 0) {
2852
+ if (!NOTIFICATION_LANGUAGES.includes(input.language)) {
2853
+ throw new TaxoraException(
2854
+ `Invalid language "${String(input.language)}". Expected one of: ${NOTIFICATION_LANGUAGES.join(", ")}.`
2855
+ );
2856
+ }
2857
+ body["language"] = input.language;
2858
+ }
2859
+ if (input.notOnPeppol !== void 0) {
2860
+ if (typeof input.notOnPeppol !== "boolean") {
2861
+ throw new TaxoraException("notOnPeppol must be a boolean.");
2862
+ }
2863
+ body["not_on_peppol"] = input.notOnPeppol;
2864
+ }
2865
+ if (input.digest !== void 0) {
2866
+ if (!NOTIFICATION_DIGESTS.includes(input.digest)) {
2867
+ throw new TaxoraException(
2868
+ `Invalid digest "${String(input.digest)}". Expected one of: ${NOTIFICATION_DIGESTS.join(", ")}.`
2869
+ );
2870
+ }
2871
+ body["digest"] = input.digest;
2872
+ }
2873
+ if (Object.keys(body).length === 0) {
2874
+ throw new TaxoraException("updateNotificationSettings requires at least one field to update.");
2875
+ }
2876
+ const response = await this.sendRequest("PUT", `${this.baseUrl}/compliance/notification-settings`, body);
2877
+ return this.toNotificationSettings(await this.parseJsonResponse(response));
2878
+ }
2879
+ toNotificationSettings(data) {
2880
+ const recipients = Array.isArray(data["recipients"]) ? data["recipients"].filter((r) => typeof r === "string" && r !== "") : [];
2881
+ const digest = data["digest"];
2882
+ return {
2883
+ recipients,
2884
+ language: data["language"] === "en" ? "en" : "de",
2885
+ notOnPeppol: data["not_on_peppol"] === true,
2886
+ digest: digest === "daily" || digest === "weekly" ? digest : "off",
2887
+ digestSentAt: typeof data["digest_sent_at"] === "string" ? data["digest_sent_at"] : null
2888
+ };
2889
+ }
2890
+ // ---------------------------------------------------------------------
1996
2891
  // Input validation (fail fast, before any request is sent)
1997
2892
  // ---------------------------------------------------------------------
2893
+ /** Validated transaction-list filters as query params (shared by listTransactions and exportTransactionsCsv). */
2894
+ buildTransactionFilterParams(filters) {
2895
+ if (filters.complianceEnrollmentId !== void 0) {
2896
+ this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
2897
+ }
2898
+ if (filters.country !== void 0 && !COUNTRY_REGEX.test(filters.country)) {
2899
+ throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
2900
+ }
2901
+ if (filters.search !== void 0 && filters.search.length > 100) {
2902
+ throw new TaxoraException("search must not exceed 100 characters.");
2903
+ }
2904
+ this.validateInternalReference(filters.internalReference);
2905
+ if (filters.providerState !== void 0 && !/^[a-z_]{1,30}$/.test(filters.providerState)) {
2906
+ throw new TaxoraException('providerState must be 1-30 lowercase letters or underscores (e.g. "refused").');
2907
+ }
2908
+ const params = new URLSearchParams();
2909
+ if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
2910
+ if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
2911
+ if (filters.state !== void 0) params.set("state", filters.state);
2912
+ if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
2913
+ if (filters.complianceEnrollmentId !== void 0) {
2914
+ params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
2915
+ }
2916
+ if (filters.country !== void 0) params.set("country", filters.country.toUpperCase());
2917
+ if (filters.search !== void 0) params.set("search", filters.search);
2918
+ if (filters.internalReference !== void 0) params.set("internal_reference", filters.internalReference);
2919
+ if (filters.providerState !== void 0) params.set("provider_state", filters.providerState);
2920
+ return params;
2921
+ }
1998
2922
  validateEnrollmentInput(input) {
1999
2923
  if (input.siret === void 0 && input.siren === void 0) {
2000
2924
  throw new TaxoraException("Either siret or siren is required.");
@@ -2120,14 +3044,101 @@ var EReportingEndpoint = class {
2120
3044
  if (line.taxes.length === 0) {
2121
3045
  throw new TaxoraException(`invoiceLines[${index}].taxes must contain at least one tax.`);
2122
3046
  }
3047
+ line.taxes.forEach((tax, taxIndex) => {
3048
+ const where = `invoiceLines[${index}].taxes[${taxIndex}]`;
3049
+ const category = parseComplianceTaxCategory(tax.category);
3050
+ if (category === null) {
3051
+ throw new TaxoraException(
3052
+ `${where}.category "${String(tax.category)}" is unknown. Valid: ${COMPLIANCE_TAX_CATEGORY_CODES.join(", ")}.`
3053
+ );
3054
+ }
3055
+ if (!Number.isFinite(tax.percent) || tax.percent < 0 || tax.percent > 100) {
3056
+ throw new TaxoraException(`${where}.percent must be between 0 and 100.`);
3057
+ }
3058
+ if (category === "E" && (tax.comment === void 0 || tax.comment.trim() === "")) {
3059
+ throw new TaxoraException(
3060
+ `${where}: an exempt (category E) tax needs the exemption code (VATEX) as comment.`
3061
+ );
3062
+ }
3063
+ if (tax.comment !== void 0 && tax.comment.length > 255) {
3064
+ throw new TaxoraException(`${where}.comment must not exceed 255 characters.`);
3065
+ }
3066
+ });
2123
3067
  });
2124
3068
  }
3069
+ validateInvoicePdfFilename(input) {
3070
+ if (input.invoicePdfFilename === void 0) return;
3071
+ if (input.invoicePdfFilename.length > 120) {
3072
+ throw new TaxoraException("invoicePdfFilename must not exceed 120 characters.");
3073
+ }
3074
+ if (input.invoicePdf === void 0) {
3075
+ throw new TaxoraException("invoicePdfFilename requires invoicePdf.");
3076
+ }
3077
+ }
3078
+ validateInternalReference(internalReference) {
3079
+ if (internalReference !== void 0 && internalReference.length > 100) {
3080
+ throw new TaxoraException("internalReference must not exceed 100 characters.");
3081
+ }
3082
+ }
3083
+ /**
3084
+ * Relations that cannot be right: a due date before the invoice date, a credit note without (or an
3085
+ * invoice with) a corrected invoice, a credit note that reuses the number it corrects, a corrected
3086
+ * invoice dated after the credit note.
3087
+ */
3088
+ validateDocument(input) {
3089
+ for (const [field, value] of [
3090
+ ["subtotal", input.subtotal],
3091
+ ["taxAmount", input.taxAmount],
3092
+ ["total", input.total]
3093
+ ]) {
3094
+ if (value !== void 0 && !Number.isFinite(Number(value))) {
3095
+ throw new TaxoraException(`${field} must be a finite number.`);
3096
+ }
3097
+ }
3098
+ for (const line of input.invoiceLines ?? []) {
3099
+ if (!Number.isFinite(line.quantity) || !Number.isFinite(line.price)) {
3100
+ throw new TaxoraException("Every invoice line needs a numeric quantity and price.");
3101
+ }
3102
+ }
3103
+ const invoiceDate = this.formatDate(input.invoiceDate);
3104
+ if (input.dueDate !== void 0 && this.formatDate(input.dueDate) < invoiceDate) {
3105
+ throw new TaxoraException("dueDate must not be before invoiceDate.");
3106
+ }
3107
+ if (input.counterpartyVatNumber !== void 0 && input.counterpartyVatNumber.length > 50) {
3108
+ throw new TaxoraException("counterpartyVatNumber must not exceed 50 characters.");
3109
+ }
3110
+ const amended = input.amendedNumber?.trim() ?? "";
3111
+ if (input.isCreditNote === true && amended === "") {
3112
+ throw new TaxoraException("A credit note needs amendedNumber, the number of the invoice it corrects.");
3113
+ }
3114
+ if (input.isCreditNote !== true && amended !== "") {
3115
+ throw new TaxoraException("amendedNumber is only for credit notes; set isCreditNote to issue one.");
3116
+ }
3117
+ if (amended !== "" && amended === input.invoiceNumber.trim()) {
3118
+ throw new TaxoraException(
3119
+ "A credit note needs its own number; it cannot reuse the number of the invoice it corrects."
3120
+ );
3121
+ }
3122
+ if (input.amendedDate !== void 0 && this.formatDate(input.amendedDate) > invoiceDate) {
3123
+ throw new TaxoraException("amendedDate (the corrected invoice) must not be after the credit note date.");
3124
+ }
3125
+ }
3126
+ /** A category read back from a stored invoice; one that is not a known code cannot be re-sent. */
3127
+ storedTaxCategory(category) {
3128
+ const parsed = parseComplianceTaxCategory(category);
3129
+ if (parsed === null) {
3130
+ throw new TaxoraException(
3131
+ `The original invoice has an unknown tax category "${String(category)}"; pass invoiceLines and the amounts explicitly.`
3132
+ );
3133
+ }
3134
+ return parsed;
3135
+ }
2125
3136
  validateOptionalCodes(input) {
2126
- if (input.currency !== void 0 && input.currency.length !== 3) {
2127
- throw new TaxoraException("currency must be a 3-character ISO 4217 code.");
3137
+ if (input.currency !== void 0 && !/^[A-Za-z]{3}$/.test(input.currency)) {
3138
+ throw new TaxoraException("currency must be an ISO 4217 code, three letters (e.g. NOK, EUR).");
2128
3139
  }
2129
- if (input.counterpartyCountry !== void 0 && input.counterpartyCountry.length !== 2) {
2130
- throw new TaxoraException("counterpartyCountry must be a 2-character ISO 3166-1 alpha-2 code.");
3140
+ if (input.counterpartyCountry !== void 0 && !/^[A-Za-z]{2}$/.test(input.counterpartyCountry)) {
3141
+ throw new TaxoraException("counterpartyCountry must be an ISO 3166-1 alpha-2 code, two letters (e.g. NO).");
2131
3142
  }
2132
3143
  }
2133
3144
  /**
@@ -2170,7 +3181,7 @@ var EReportingEndpoint = class {
2170
3181
  const taxBuilt = {
2171
3182
  name: tax.name,
2172
3183
  percent: tax.percent,
2173
- category: tax.category
3184
+ category: parseComplianceTaxCategory(tax.category) ?? tax.category
2174
3185
  };
2175
3186
  if (tax.comment !== void 0) taxBuilt["comment"] = tax.comment;
2176
3187
  return taxBuilt;
@@ -2183,10 +3194,10 @@ var EReportingEndpoint = class {
2183
3194
  /** Applies the optional scalar fields shared by create and update. */
2184
3195
  applyOptionalTransactionFields(body, input) {
2185
3196
  if (input.dueDate !== void 0) body["due_date"] = this.formatDate(input.dueDate);
2186
- if (input.currency !== void 0) body["currency"] = input.currency;
3197
+ if (input.currency !== void 0) body["currency"] = input.currency.toUpperCase();
2187
3198
  if (input.taxAmount !== void 0) body["tax_amount"] = input.taxAmount;
2188
3199
  if (input.counterpartyName !== void 0) body["counterparty_name"] = input.counterpartyName;
2189
- if (input.counterpartyCountry !== void 0) body["counterparty_country"] = input.counterpartyCountry;
3200
+ if (input.counterpartyCountry !== void 0) body["counterparty_country"] = input.counterpartyCountry.toUpperCase();
2190
3201
  if (input.counterpartyVatNumber !== void 0) body["counterparty_vat_number"] = input.counterpartyVatNumber;
2191
3202
  if (input.providerContactId !== void 0) body["provider_contact_id"] = input.providerContactId;
2192
3203
  if (input.paymentMethod !== void 0) body["payment_method"] = input.paymentMethod;
@@ -2203,6 +3214,29 @@ var EReportingEndpoint = class {
2203
3214
  if (input.amendedNumber !== void 0) body["amended_number"] = input.amendedNumber;
2204
3215
  if (input.amendedDate !== void 0) body["amended_date"] = this.formatDate(input.amendedDate);
2205
3216
  if (input.extraInfo !== void 0) body["extra_info"] = input.extraInfo;
3217
+ if (input.internalReference !== void 0) body["internal_reference"] = input.internalReference;
3218
+ if (input.invoicePdf !== void 0) body["invoice_pdf"] = this.encodeInvoicePdf(input.invoicePdf);
3219
+ if (input.invoicePdfFilename !== void 0) body["invoice_pdf_filename"] = input.invoicePdfFilename;
3220
+ }
3221
+ /** Bytes → base64 (validated as a PDF of at most 10 MB); strings are taken as already base64. */
3222
+ encodeInvoicePdf(pdf) {
3223
+ if (typeof pdf === "string") {
3224
+ if (pdf.trim() === "") {
3225
+ throw new TaxoraException("invoicePdf must not be empty.");
3226
+ }
3227
+ return pdf;
3228
+ }
3229
+ const bytes = pdf instanceof Uint8Array ? pdf : new Uint8Array(pdf);
3230
+ if (bytes.byteLength === 0) {
3231
+ throw new TaxoraException("invoicePdf must not be empty.");
3232
+ }
3233
+ if (bytes.byteLength > INVOICE_PDF_MAX_BYTES) {
3234
+ throw new TaxoraException("invoicePdf must not exceed 10 MB.");
3235
+ }
3236
+ if (!INVOICE_PDF_MAGIC.every((byte, index) => bytes[index] === byte)) {
3237
+ throw new TaxoraException('invoicePdf must be a PDF file (content must start with "%PDF-").');
3238
+ }
3239
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
2206
3240
  }
2207
3241
  // ---------------------------------------------------------------------
2208
3242
  // HTTP plumbing (kept in sync with SmartEnrichmentEndpoint)
@@ -2226,6 +3260,30 @@ var EReportingEndpoint = class {
2226
3260
  const send = () => this.httpClient.request(method, url, options);
2227
3261
  return method === "GET" ? withResponseRetries(this.retryPolicy, "e-reporting request", send) : send();
2228
3262
  }
3263
+ /** GET for a non-JSON download (CSV / document): no JSON Content-Type, retried like other GETs. */
3264
+ async sendDownloadRequest(url, accept) {
3265
+ const headers = this.buildHeaders(false);
3266
+ headers["Accept"] = accept;
3267
+ return withResponseRetries(
3268
+ this.retryPolicy,
3269
+ "e-reporting download",
3270
+ () => this.httpClient.request("GET", url, { headers })
3271
+ );
3272
+ }
3273
+ /** `attachment; filename="x.xml"` (or RFC 5987 `filename*=UTF-8''x.xml`) → "x.xml". */
3274
+ filenameFromDisposition(header) {
3275
+ if (header === null) return null;
3276
+ const extended = /filename\*\s*=\s*(?:UTF-8|utf-8)''([^;]+)/.exec(header);
3277
+ if (extended?.[1] !== void 0) {
3278
+ try {
3279
+ return decodeURIComponent(extended[1].trim());
3280
+ } catch {
3281
+ }
3282
+ }
3283
+ const plain = /filename\s*=\s*"([^"]*)"|filename\s*=\s*([^;]+)/.exec(header);
3284
+ const name = (plain?.[1] ?? plain?.[2])?.trim();
3285
+ return name !== void 0 && name !== "" ? name : null;
3286
+ }
2229
3287
  /** Multipart upload: Content-Type is left to fetch so the boundary is set correctly. */
2230
3288
  async sendMultipartRequest(method, url, form) {
2231
3289
  return this.httpClient.request(method, url, { headers: this.buildHeaders(false), body: form });
@@ -2807,13 +3865,13 @@ var SmartEnrichmentHistoryPage = class _SmartEnrichmentHistoryPage {
2807
3865
  };
2808
3866
 
2809
3867
  // src/dto/SmartEnrichmentStatistics.ts
2810
- function asString9(value, fallback = "") {
3868
+ function asString12(value, fallback = "") {
2811
3869
  return typeof value === "string" ? value : fallback;
2812
3870
  }
2813
- function asNumber6(value) {
3871
+ function asNumber7(value) {
2814
3872
  return typeof value === "number" ? value : Number(value ?? 0) || 0;
2815
3873
  }
2816
- function asRecord2(value) {
3874
+ function asRecord5(value) {
2817
3875
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
2818
3876
  }
2819
3877
  function asRecords2(value) {
@@ -2836,39 +3894,39 @@ var SmartEnrichmentStatistics = class _SmartEnrichmentStatistics {
2836
3894
  byOutcome;
2837
3895
  confidenceBuckets;
2838
3896
  static fromArray(data) {
2839
- const range = asRecord2(data["range"]);
2840
- const totals = asRecord2(data["totals"]);
3897
+ const range = asRecord5(data["range"]);
3898
+ const totals = asRecord5(data["totals"]);
2841
3899
  const timeSeries = asRecords2(data["time_series"]).map((row) => ({
2842
- bucket: asString9(row["bucket"]),
2843
- jobs: asNumber6(row["jobs"]),
2844
- items: asNumber6(row["items"]),
2845
- found: asNumber6(row["found"])
3900
+ bucket: asString12(row["bucket"]),
3901
+ jobs: asNumber7(row["jobs"]),
3902
+ items: asNumber7(row["items"]),
3903
+ found: asNumber7(row["found"])
2846
3904
  }));
2847
3905
  const bySource = asRecords2(data["by_source"]).map((row) => ({
2848
- source: asString9(row["source"]),
2849
- jobs: asNumber6(row["jobs"]),
2850
- found: asNumber6(row["found"])
3906
+ source: asString12(row["source"]),
3907
+ jobs: asNumber7(row["jobs"]),
3908
+ found: asNumber7(row["found"])
2851
3909
  }));
2852
3910
  const byOutcome = asRecords2(data["by_outcome"]).map((row) => ({
2853
- outcome: asString9(row["outcome"]),
2854
- count: asNumber6(row["count"])
3911
+ outcome: asString12(row["outcome"]),
3912
+ count: asNumber7(row["count"])
2855
3913
  }));
2856
3914
  const confidenceBuckets = asRecords2(data["confidence_buckets"]).map((row) => ({
2857
- bucket: asString9(row["bucket"]),
2858
- count: asNumber6(row["count"])
3915
+ bucket: asString12(row["bucket"]),
3916
+ count: asNumber7(row["count"])
2859
3917
  }));
2860
3918
  return new _SmartEnrichmentStatistics(
2861
3919
  {
2862
- dateFrom: asString9(range["date_from"]),
2863
- dateTo: asString9(range["date_to"]),
2864
- interval: asString9(range["interval"])
3920
+ dateFrom: asString12(range["date_from"]),
3921
+ dateTo: asString12(range["date_to"]),
3922
+ interval: asString12(range["interval"])
2865
3923
  },
2866
3924
  {
2867
- jobs: asNumber6(totals["jobs"]),
2868
- items: asNumber6(totals["items"]),
2869
- found: asNumber6(totals["found"]),
2870
- foundRate: asNumber6(totals["found_rate"]),
2871
- avgConfidence: asNumber6(totals["avg_confidence"])
3925
+ jobs: asNumber7(totals["jobs"]),
3926
+ items: asNumber7(totals["items"]),
3927
+ found: asNumber7(totals["found"]),
3928
+ foundRate: asNumber7(totals["found_rate"]),
3929
+ avgConfidence: asNumber7(totals["avg_confidence"])
2872
3930
  },
2873
3931
  timeSeries,
2874
3932
  bySource,
@@ -4148,7 +5206,7 @@ var AuthRetryHttpClient = class {
4148
5206
  };
4149
5207
 
4150
5208
  // src/version.ts
4151
- var SDK_VERSION = "1.8.0";
5209
+ var SDK_VERSION = "1.9.0";
4152
5210
 
4153
5211
  // src/http/SdkVersionHttpClient.ts
4154
5212
  var SDK_VERSION_HEADER = "X-Taxora-SDK-Version";
@@ -4175,6 +5233,7 @@ var TaxoraClient = class {
4175
5233
  company;
4176
5234
  smartEnrichment;
4177
5235
  eReporting;
5236
+ billing;
4178
5237
  baseUrl;
4179
5238
  constructor(options) {
4180
5239
  const environment = options.environment ?? Environment.SANDBOX;
@@ -4195,6 +5254,7 @@ var TaxoraClient = class {
4195
5254
  retryPolicy
4196
5255
  );
4197
5256
  this.eReporting = new EReportingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
5257
+ this.billing = new BillingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
4198
5258
  }
4199
5259
  };
4200
5260
 
@@ -4216,6 +5276,68 @@ var Language = {
4216
5276
  GERMAN: "de"
4217
5277
  };
4218
5278
 
5279
+ // src/support/calculateInvoiceTotals.ts
5280
+ function round2(value) {
5281
+ const magnitude = Math.round(Number((Math.abs(value) * 100).toPrecision(15)));
5282
+ return (value < 0 ? -magnitude : magnitude) / 100;
5283
+ }
5284
+ function calculateInvoiceTotals(lines, options = {}) {
5285
+ let net = 0;
5286
+ let tax = 0;
5287
+ for (const line of lines) {
5288
+ const lineNet = round2(line.quantity * line.price);
5289
+ const percent = line.taxes.reduce((sum, t) => sum + t.percent, 0);
5290
+ net += lineNet;
5291
+ tax += round2(lineNet * percent / 100);
5292
+ }
5293
+ const subtotal = round2(net);
5294
+ const taxAmount = round2(tax);
5295
+ const total = round2(subtotal + taxAmount);
5296
+ let roundingDifference = 0;
5297
+ if (options.expectedTotal !== void 0) {
5298
+ if (!Number.isFinite(options.expectedTotal)) {
5299
+ throw new Error("The expected total must be numeric.");
5300
+ }
5301
+ const tolerance = options.tolerance ?? 0.05;
5302
+ roundingDifference = round2(options.expectedTotal - total);
5303
+ if (Math.abs(roundingDifference) > tolerance) {
5304
+ throw new Error(
5305
+ `The expected total ${options.expectedTotal.toFixed(2)} differs from the invoice lines (${total.toFixed(2)}) by ${roundingDifference.toFixed(2)}, more than the rounding tolerance of ${tolerance.toFixed(2)}. Check quantities, prices and tax rates.`
5306
+ );
5307
+ }
5308
+ }
5309
+ return { subtotal, taxAmount, total, roundingDifference };
5310
+ }
5311
+
5312
+ // src/support/complianceTax.ts
5313
+ var ComplianceTax = {
5314
+ /**
5315
+ * A taxed rate above 0 %, category S (Norway: 25 / 15 / 12 % are all S). Where a country files reduced rates
5316
+ * under their own category (France: AA / AAA), pass it as `category`.
5317
+ */
5318
+ rate(percent, name = "VAT", category = ComplianceTaxCategory.STANDARD) {
5319
+ if (!Number.isFinite(percent) || percent <= 0) {
5320
+ throw new Error("rate() needs a percent above 0; for 0 % use zeroRated(), exempt() or reverseCharge().");
5321
+ }
5322
+ return { name, percent, category };
5323
+ },
5324
+ /** 0 % but taxable (category Z). */
5325
+ zeroRated(name = "VAT") {
5326
+ return { name, percent: 0, category: ComplianceTaxCategory.ZERO };
5327
+ },
5328
+ /** Exempt from VAT (category E); the VATEX exemption code is mandatory. */
5329
+ exempt(exemptionCode, name = "VAT") {
5330
+ if (exemptionCode.trim() === "") {
5331
+ throw new Error("An exempt (category E) tax needs the exemption code (VATEX).");
5332
+ }
5333
+ return { name, percent: 0, category: ComplianceTaxCategory.EXEMPT, comment: exemptionCode };
5334
+ },
5335
+ /** Reverse charge (category AE); the mandatory legal text is added by Taxora. */
5336
+ reverseCharge(name = "VAT") {
5337
+ return { name, percent: 0, category: ComplianceTaxCategory.REVERSE_CHARGE };
5338
+ }
5339
+ };
5340
+
4219
5341
  // src/webhooks/WebhookSignature.ts
4220
5342
  var import_node_crypto = require("crypto");
4221
5343
  var WEBHOOK_SIGNATURE_HEADER = "Taxora-Signature";
@@ -4334,19 +5456,30 @@ function toBytes(rawBody) {
4334
5456
  "invalid_body"
4335
5457
  );
4336
5458
  }
5459
+
5460
+ // src/webhooks/WebhookEvent.ts
5461
+ function isTransactionBuyerRejected(object) {
5462
+ return object.buyer_rejected === true;
5463
+ }
4337
5464
  // Annotate the CommonJS export names for ESM import in node:
4338
5465
  0 && (module.exports = {
4339
5466
  ApiVersion,
4340
5467
  AuthEndpoint,
4341
5468
  AuthRetryHttpClient,
4342
5469
  AuthenticationException,
5470
+ BillingEndpoint,
4343
5471
  CompanyAddress,
4344
5472
  CompanyEndpoint,
5473
+ ComplianceDeliveryAction,
5474
+ ComplianceDeliveryDecision,
4345
5475
  ComplianceDocumentType,
4346
5476
  ComplianceEnrollment,
4347
5477
  ComplianceEnrollmentPage,
4348
5478
  ComplianceEnrollmentStatus,
5479
+ ComplianceOfferings,
4349
5480
  ComplianceService,
5481
+ ComplianceTax,
5482
+ ComplianceTaxCategory,
4350
5483
  ComplianceTaxReport,
4351
5484
  ComplianceTaxReportPage,
4352
5485
  ComplianceTaxReportState,
@@ -4355,6 +5488,8 @@ function toBytes(rawBody) {
4355
5488
  ComplianceTransactionState,
4356
5489
  ComplianceTransactionType,
4357
5490
  DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
5491
+ EInvoicingQuota,
5492
+ EInvoicingQuotaPeriod,
4358
5493
  EReportingEndpoint,
4359
5494
  Environment,
4360
5495
  FetchHttpClient,
@@ -4389,6 +5524,8 @@ function toBytes(rawBody) {
4389
5524
  TaxoraClientFactory,
4390
5525
  TaxoraException,
4391
5526
  Token,
5527
+ UsageSummary,
5528
+ UsageSummaryPage,
4392
5529
  ValidationException,
4393
5530
  VatCertificateExport,
4394
5531
  VatCollection,
@@ -4400,6 +5537,7 @@ function toBytes(rawBody) {
4400
5537
  WEBHOOK_ID_HEADER,
4401
5538
  WEBHOOK_SIGNATURE_HEADER,
4402
5539
  WebhookSignatureVerificationError,
5540
+ calculateInvoiceTotals,
4403
5541
  computeWebhookSignature,
4404
5542
  constructWebhookEvent,
4405
5543
  describeApiError,
@@ -4417,6 +5555,8 @@ function toBytes(rawBody) {
4417
5555
  generateWebhookSignatureHeader,
4418
5556
  getBaseUrl,
4419
5557
  getFailedVatStates,
5558
+ isTransactionBuyerRejected,
5559
+ parseComplianceTaxCategory,
4420
5560
  parseLocalTaxNumbersCsv,
4421
5561
  parseWebhookSignatureHeader,
4422
5562
  toComplianceDocumentType,