@taxora/sdk 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +191 -0
- package/dist/index.cjs +741 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +497 -20
- package/dist/index.d.ts +497 -20
- package/dist/index.js +719 -85
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -323,13 +323,7 @@ async function withResponseRetries(policy, operation, send) {
|
|
|
323
323
|
if (!policy.shouldRetry(response.status, attempt, retryAfter)) {
|
|
324
324
|
if (attempt > 1 && policy.isRetryable(response.status, retryAfter)) {
|
|
325
325
|
const body = await response.text();
|
|
326
|
-
const last = new HttpException(
|
|
327
|
-
describeApiError(body, response.status),
|
|
328
|
-
response.status,
|
|
329
|
-
body,
|
|
330
|
-
{},
|
|
331
|
-
retryAfter
|
|
332
|
-
);
|
|
326
|
+
const last = new HttpException(describeApiError(body, response.status), response.status, body, {}, retryAfter);
|
|
333
327
|
throw HttpException.afterAttempts(operation, attempt, last);
|
|
334
328
|
}
|
|
335
329
|
return response;
|
|
@@ -546,6 +540,31 @@ function describeComplianceEnrollmentStatus(status) {
|
|
|
546
540
|
return DESCRIPTIONS[status];
|
|
547
541
|
}
|
|
548
542
|
|
|
543
|
+
// src/enums/ComplianceService.ts
|
|
544
|
+
var ComplianceService = {
|
|
545
|
+
E_REPORTING: "e_reporting",
|
|
546
|
+
E_INVOICING: "e_invoicing",
|
|
547
|
+
UNKNOWN: "unknown"
|
|
548
|
+
};
|
|
549
|
+
var VALUES2 = Object.values(ComplianceService);
|
|
550
|
+
function toComplianceService(value) {
|
|
551
|
+
if (typeof value === "string") {
|
|
552
|
+
const normalized = value.trim().toLowerCase();
|
|
553
|
+
if (VALUES2.includes(normalized)) {
|
|
554
|
+
return normalized;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return ComplianceService.UNKNOWN;
|
|
558
|
+
}
|
|
559
|
+
var DESCRIPTIONS2 = {
|
|
560
|
+
[ComplianceService.E_REPORTING]: "E-reporting of transaction data to the tax authority (France: DGFiP Flux 10).",
|
|
561
|
+
[ComplianceService.E_INVOICING]: "Structured e-invoices delivered to the buyer (Norway: EHF / Peppol BIS 3.0).",
|
|
562
|
+
[ComplianceService.UNKNOWN]: "Unknown service."
|
|
563
|
+
};
|
|
564
|
+
function describeComplianceService(service) {
|
|
565
|
+
return DESCRIPTIONS2[service];
|
|
566
|
+
}
|
|
567
|
+
|
|
549
568
|
// src/dto/ComplianceEnrollment.ts
|
|
550
569
|
function asString(value, fallback = "") {
|
|
551
570
|
return typeof value === "string" ? value : fallback;
|
|
@@ -557,7 +576,7 @@ function asNumber(value) {
|
|
|
557
576
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
558
577
|
}
|
|
559
578
|
var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
560
|
-
constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt) {
|
|
579
|
+
constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt, service = ComplianceService.UNKNOWN) {
|
|
561
580
|
this.id = id;
|
|
562
581
|
this.companyId = companyId;
|
|
563
582
|
this.country = country;
|
|
@@ -576,6 +595,7 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
576
595
|
this.autoSend = autoSend;
|
|
577
596
|
this.createdAt = createdAt;
|
|
578
597
|
this.updatedAt = updatedAt;
|
|
598
|
+
this.service = service;
|
|
579
599
|
}
|
|
580
600
|
id;
|
|
581
601
|
companyId;
|
|
@@ -595,6 +615,7 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
595
615
|
autoSend;
|
|
596
616
|
createdAt;
|
|
597
617
|
updatedAt;
|
|
618
|
+
service;
|
|
598
619
|
static fromArray(data) {
|
|
599
620
|
const regimeConfig = typeof data["regime_config"] === "object" && data["regime_config"] !== null && !Array.isArray(data["regime_config"]) ? data["regime_config"] : null;
|
|
600
621
|
return new _ComplianceEnrollment(
|
|
@@ -615,7 +636,8 @@ var ComplianceEnrollment = class _ComplianceEnrollment {
|
|
|
615
636
|
asNullableString(data["notification_email"]),
|
|
616
637
|
data["auto_send"] === true,
|
|
617
638
|
asNullableString(data["created_at"]),
|
|
618
|
-
asNullableString(data["updated_at"])
|
|
639
|
+
asNullableString(data["updated_at"]),
|
|
640
|
+
toComplianceService(data["service"])
|
|
619
641
|
);
|
|
620
642
|
}
|
|
621
643
|
};
|
|
@@ -665,17 +687,17 @@ var ComplianceTaxReportState = {
|
|
|
665
687
|
ERROR: "error",
|
|
666
688
|
UNKNOWN: "unknown"
|
|
667
689
|
};
|
|
668
|
-
var
|
|
690
|
+
var VALUES3 = Object.values(ComplianceTaxReportState);
|
|
669
691
|
function toComplianceTaxReportState(value) {
|
|
670
692
|
if (typeof value === "string") {
|
|
671
693
|
const normalized = value.trim().toLowerCase();
|
|
672
|
-
if (
|
|
694
|
+
if (VALUES3.includes(normalized)) {
|
|
673
695
|
return normalized;
|
|
674
696
|
}
|
|
675
697
|
}
|
|
676
698
|
return ComplianceTaxReportState.UNKNOWN;
|
|
677
699
|
}
|
|
678
|
-
var
|
|
700
|
+
var DESCRIPTIONS3 = {
|
|
679
701
|
[ComplianceTaxReportState.NEW]: "Created at provider, waiting for the daily ledger batch.",
|
|
680
702
|
[ComplianceTaxReportState.SENT]: "Ledger transmitted to PPF.",
|
|
681
703
|
[ComplianceTaxReportState.ACKNOWLEDGED]: "PPF acknowledged receipt of the ledger.",
|
|
@@ -685,7 +707,7 @@ var DESCRIPTIONS2 = {
|
|
|
685
707
|
[ComplianceTaxReportState.UNKNOWN]: "Unknown state."
|
|
686
708
|
};
|
|
687
709
|
function describeComplianceTaxReportState(state) {
|
|
688
|
-
return
|
|
710
|
+
return DESCRIPTIONS3[state];
|
|
689
711
|
}
|
|
690
712
|
|
|
691
713
|
// src/dto/ComplianceTaxReport.ts
|
|
@@ -807,6 +829,31 @@ var ComplianceTaxReportPage = class _ComplianceTaxReportPage {
|
|
|
807
829
|
}
|
|
808
830
|
};
|
|
809
831
|
|
|
832
|
+
// src/enums/ComplianceDocumentType.ts
|
|
833
|
+
var ComplianceDocumentType = {
|
|
834
|
+
INVOICE: "invoice",
|
|
835
|
+
CREDIT_NOTE: "credit_note",
|
|
836
|
+
UNKNOWN: "unknown"
|
|
837
|
+
};
|
|
838
|
+
var VALUES4 = Object.values(ComplianceDocumentType);
|
|
839
|
+
function toComplianceDocumentType(value) {
|
|
840
|
+
if (typeof value === "string") {
|
|
841
|
+
const normalized = value.trim().toLowerCase();
|
|
842
|
+
if (VALUES4.includes(normalized)) {
|
|
843
|
+
return normalized;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return ComplianceDocumentType.UNKNOWN;
|
|
847
|
+
}
|
|
848
|
+
var DESCRIPTIONS4 = {
|
|
849
|
+
[ComplianceDocumentType.INVOICE]: "Commercial invoice.",
|
|
850
|
+
[ComplianceDocumentType.CREDIT_NOTE]: "Credit note correcting a previously issued invoice.",
|
|
851
|
+
[ComplianceDocumentType.UNKNOWN]: "Unknown document type."
|
|
852
|
+
};
|
|
853
|
+
function describeComplianceDocumentType(type) {
|
|
854
|
+
return DESCRIPTIONS4[type];
|
|
855
|
+
}
|
|
856
|
+
|
|
810
857
|
// src/enums/ComplianceTransactionState.ts
|
|
811
858
|
var ComplianceTransactionState = {
|
|
812
859
|
PENDING: "pending",
|
|
@@ -815,17 +862,17 @@ var ComplianceTransactionState = {
|
|
|
815
862
|
ERROR: "error",
|
|
816
863
|
UNKNOWN: "unknown"
|
|
817
864
|
};
|
|
818
|
-
var
|
|
865
|
+
var VALUES5 = Object.values(ComplianceTransactionState);
|
|
819
866
|
function toComplianceTransactionState(value) {
|
|
820
867
|
if (typeof value === "string") {
|
|
821
868
|
const normalized = value.trim().toLowerCase();
|
|
822
|
-
if (
|
|
869
|
+
if (VALUES5.includes(normalized)) {
|
|
823
870
|
return normalized;
|
|
824
871
|
}
|
|
825
872
|
}
|
|
826
873
|
return ComplianceTransactionState.UNKNOWN;
|
|
827
874
|
}
|
|
828
|
-
var
|
|
875
|
+
var DESCRIPTIONS5 = {
|
|
829
876
|
[ComplianceTransactionState.PENDING]: "Recorded locally, not yet submitted to the provider.",
|
|
830
877
|
[ComplianceTransactionState.SENDING]: "Submission to the provider in progress.",
|
|
831
878
|
[ComplianceTransactionState.SUBMITTED]: "Successfully submitted to the provider.",
|
|
@@ -833,7 +880,7 @@ var DESCRIPTIONS3 = {
|
|
|
833
880
|
[ComplianceTransactionState.UNKNOWN]: "Unknown state."
|
|
834
881
|
};
|
|
835
882
|
function describeComplianceTransactionState(state) {
|
|
836
|
-
return
|
|
883
|
+
return DESCRIPTIONS5[state];
|
|
837
884
|
}
|
|
838
885
|
|
|
839
886
|
// src/enums/ComplianceTransactionType.ts
|
|
@@ -845,17 +892,17 @@ var ComplianceTransactionType = {
|
|
|
845
892
|
CROSSBORDER_INBOUND: "crossborder_inbound",
|
|
846
893
|
UNKNOWN: "unknown"
|
|
847
894
|
};
|
|
848
|
-
var
|
|
895
|
+
var VALUES6 = Object.values(ComplianceTransactionType);
|
|
849
896
|
function toComplianceTransactionType(value) {
|
|
850
897
|
if (typeof value === "string") {
|
|
851
898
|
const normalized = value.trim().toLowerCase();
|
|
852
|
-
if (
|
|
899
|
+
if (VALUES6.includes(normalized)) {
|
|
853
900
|
return normalized;
|
|
854
901
|
}
|
|
855
902
|
}
|
|
856
903
|
return ComplianceTransactionType.UNKNOWN;
|
|
857
904
|
}
|
|
858
|
-
var
|
|
905
|
+
var DESCRIPTIONS6 = {
|
|
859
906
|
[ComplianceTransactionType.B2C_OUTBOUND]: "Sale to a private person in the merchant country (DGFiP Flux 10.3).",
|
|
860
907
|
[ComplianceTransactionType.B2B_DOMESTIC_OUTBOUND]: "Sale to a domestic business (DGFiP Flux 1, Phase 2).",
|
|
861
908
|
[ComplianceTransactionType.B2B_DOMESTIC_INBOUND]: "Purchase from a domestic business (DGFiP Flux 1, Phase 2).",
|
|
@@ -864,7 +911,7 @@ var DESCRIPTIONS4 = {
|
|
|
864
911
|
[ComplianceTransactionType.UNKNOWN]: "Unknown transaction type."
|
|
865
912
|
};
|
|
866
913
|
function describeComplianceTransactionType(type) {
|
|
867
|
-
return
|
|
914
|
+
return DESCRIPTIONS6[type];
|
|
868
915
|
}
|
|
869
916
|
|
|
870
917
|
// src/dto/ComplianceTransaction.ts
|
|
@@ -882,8 +929,34 @@ function asMoney2(value) {
|
|
|
882
929
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
883
930
|
return "0.00";
|
|
884
931
|
}
|
|
932
|
+
function asNullableNumber2(value) {
|
|
933
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
934
|
+
if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) return Number(value);
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
function asRecord(value) {
|
|
938
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
939
|
+
}
|
|
940
|
+
function asRecordList(value) {
|
|
941
|
+
return Array.isArray(value) ? value.filter((row) => asRecord(row) !== null) : [];
|
|
942
|
+
}
|
|
943
|
+
function toInvoiceLines(providerPayload) {
|
|
944
|
+
const invoice = asRecord(asRecord(providerPayload)?.["invoice"]);
|
|
945
|
+
return asRecordList(invoice?.["invoice_lines_attributes"]).map((line) => ({
|
|
946
|
+
description: asString3(line["description"]),
|
|
947
|
+
quantity: asNullableNumber2(line["quantity"]),
|
|
948
|
+
price: asMoney2(line["price"]),
|
|
949
|
+
unit: asNullableNumber2(line["unit"]),
|
|
950
|
+
taxes: asRecordList(line["taxes_attributes"]).map((tax) => ({
|
|
951
|
+
name: asString3(tax["name"]),
|
|
952
|
+
percent: asNullableNumber2(tax["percent"]),
|
|
953
|
+
category: asNullableString3(tax["category"]),
|
|
954
|
+
comment: asNullableString3(tax["comment"])
|
|
955
|
+
}))
|
|
956
|
+
}));
|
|
957
|
+
}
|
|
885
958
|
var ComplianceTransaction = class _ComplianceTransaction {
|
|
886
|
-
constructor(id, companyId, complianceEnrollmentId, country, regime, transactionType, transactionTypeLabel, state, stateLabel, invoiceNumber, invoiceDate, dueDate, currency, subtotal, taxAmount, total, counterpartyName, counterpartyCountry, counterpartyVatNumber, isPaid, paidAt, providerInvoiceId, submissionError, reportedAt, taxReport, createdAt, updatedAt) {
|
|
959
|
+
constructor(id, companyId, complianceEnrollmentId, country, regime, transactionType, transactionTypeLabel, state, stateLabel, invoiceNumber, invoiceDate, dueDate, currency, subtotal, taxAmount, total, counterpartyName, counterpartyCountry, counterpartyVatNumber, isPaid, paidAt, providerInvoiceId, submissionError, reportedAt, taxReport, createdAt, updatedAt, counterpartyRegisterId = null, counterpartyAddress = null, counterpartyCity = null, counterpartyPostalcode = null, counterpartyEmail = null, buyerReference = null, documentType = ComplianceDocumentType.INVOICE, amendedNumber = null, amendedDate = null, providerState = null, invoiceLines = []) {
|
|
887
960
|
this.id = id;
|
|
888
961
|
this.companyId = companyId;
|
|
889
962
|
this.complianceEnrollmentId = complianceEnrollmentId;
|
|
@@ -911,6 +984,17 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
911
984
|
this.taxReport = taxReport;
|
|
912
985
|
this.createdAt = createdAt;
|
|
913
986
|
this.updatedAt = updatedAt;
|
|
987
|
+
this.counterpartyRegisterId = counterpartyRegisterId;
|
|
988
|
+
this.counterpartyAddress = counterpartyAddress;
|
|
989
|
+
this.counterpartyCity = counterpartyCity;
|
|
990
|
+
this.counterpartyPostalcode = counterpartyPostalcode;
|
|
991
|
+
this.counterpartyEmail = counterpartyEmail;
|
|
992
|
+
this.buyerReference = buyerReference;
|
|
993
|
+
this.documentType = documentType;
|
|
994
|
+
this.amendedNumber = amendedNumber;
|
|
995
|
+
this.amendedDate = amendedDate;
|
|
996
|
+
this.providerState = providerState;
|
|
997
|
+
this.invoiceLines = invoiceLines;
|
|
914
998
|
}
|
|
915
999
|
id;
|
|
916
1000
|
companyId;
|
|
@@ -939,6 +1023,20 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
939
1023
|
taxReport;
|
|
940
1024
|
createdAt;
|
|
941
1025
|
updatedAt;
|
|
1026
|
+
counterpartyRegisterId;
|
|
1027
|
+
counterpartyAddress;
|
|
1028
|
+
counterpartyCity;
|
|
1029
|
+
counterpartyPostalcode;
|
|
1030
|
+
counterpartyEmail;
|
|
1031
|
+
buyerReference;
|
|
1032
|
+
documentType;
|
|
1033
|
+
amendedNumber;
|
|
1034
|
+
amendedDate;
|
|
1035
|
+
providerState;
|
|
1036
|
+
invoiceLines;
|
|
1037
|
+
get isCreditNote() {
|
|
1038
|
+
return this.documentType === ComplianceDocumentType.CREDIT_NOTE;
|
|
1039
|
+
}
|
|
942
1040
|
static fromArray(data) {
|
|
943
1041
|
const taxReport = typeof data["tax_report"] === "object" && data["tax_report"] !== null && !Array.isArray(data["tax_report"]) ? ComplianceTaxReport.fromArray(data["tax_report"]) : void 0;
|
|
944
1042
|
return new _ComplianceTransaction(
|
|
@@ -968,7 +1066,18 @@ var ComplianceTransaction = class _ComplianceTransaction {
|
|
|
968
1066
|
asNullableString3(data["reported_at"]),
|
|
969
1067
|
taxReport,
|
|
970
1068
|
asNullableString3(data["created_at"]),
|
|
971
|
-
asNullableString3(data["updated_at"])
|
|
1069
|
+
asNullableString3(data["updated_at"]),
|
|
1070
|
+
asNullableString3(data["counterparty_register_id"]),
|
|
1071
|
+
asNullableString3(data["counterparty_address"]),
|
|
1072
|
+
asNullableString3(data["counterparty_city"]),
|
|
1073
|
+
asNullableString3(data["counterparty_postalcode"]),
|
|
1074
|
+
asNullableString3(data["counterparty_email"]),
|
|
1075
|
+
asNullableString3(data["buyer_reference"]),
|
|
1076
|
+
data["document_type"] === void 0 || data["document_type"] === null ? ComplianceDocumentType.INVOICE : toComplianceDocumentType(data["document_type"]),
|
|
1077
|
+
asNullableString3(data["amended_number"]),
|
|
1078
|
+
asNullableString3(data["amended_date"]),
|
|
1079
|
+
asNullableString3(data["provider_state"]),
|
|
1080
|
+
toInvoiceLines(data["provider_payload"])
|
|
972
1081
|
);
|
|
973
1082
|
}
|
|
974
1083
|
};
|
|
@@ -1027,10 +1136,134 @@ var ImportResult = class _ImportResult {
|
|
|
1027
1136
|
}
|
|
1028
1137
|
};
|
|
1029
1138
|
|
|
1030
|
-
// src/
|
|
1139
|
+
// src/enums/PeppolLookupStatus.ts
|
|
1140
|
+
var PeppolLookupStatus = {
|
|
1141
|
+
REACHABLE: "reachable",
|
|
1142
|
+
NOT_REACHABLE: "not_reachable",
|
|
1143
|
+
PENDING: "pending",
|
|
1144
|
+
UNKNOWN: "unknown"
|
|
1145
|
+
};
|
|
1146
|
+
var VALUES7 = Object.values(PeppolLookupStatus);
|
|
1147
|
+
function toPeppolLookupStatus(value) {
|
|
1148
|
+
if (typeof value === "string") {
|
|
1149
|
+
const normalized = value.trim().toLowerCase();
|
|
1150
|
+
if (VALUES7.includes(normalized)) {
|
|
1151
|
+
return normalized;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return PeppolLookupStatus.UNKNOWN;
|
|
1155
|
+
}
|
|
1156
|
+
var DESCRIPTIONS7 = {
|
|
1157
|
+
[PeppolLookupStatus.REACHABLE]: "The participant is registered on Peppol and can receive e-invoices.",
|
|
1158
|
+
[PeppolLookupStatus.NOT_REACHABLE]: "The participant is not registered on Peppol.",
|
|
1159
|
+
[PeppolLookupStatus.PENDING]: "The directory lookup is still resolving \u2014 ask again in a few seconds.",
|
|
1160
|
+
[PeppolLookupStatus.UNKNOWN]: "Unknown lookup status."
|
|
1161
|
+
};
|
|
1162
|
+
function describePeppolLookupStatus(status) {
|
|
1163
|
+
return DESCRIPTIONS7[status];
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/dto/PeppolLookupResult.ts
|
|
1031
1167
|
function asString4(value, fallback = "") {
|
|
1032
1168
|
return typeof value === "string" ? value : fallback;
|
|
1033
1169
|
}
|
|
1170
|
+
function asNullableString4(value) {
|
|
1171
|
+
return typeof value === "string" ? value : null;
|
|
1172
|
+
}
|
|
1173
|
+
var PeppolLookupResult = class _PeppolLookupResult {
|
|
1174
|
+
constructor(status, reachable, country, scheme, id, documentTypes, transportTypeCode, checkedAt) {
|
|
1175
|
+
this.status = status;
|
|
1176
|
+
this.reachable = reachable;
|
|
1177
|
+
this.country = country;
|
|
1178
|
+
this.scheme = scheme;
|
|
1179
|
+
this.id = id;
|
|
1180
|
+
this.documentTypes = documentTypes;
|
|
1181
|
+
this.transportTypeCode = transportTypeCode;
|
|
1182
|
+
this.checkedAt = checkedAt;
|
|
1183
|
+
}
|
|
1184
|
+
status;
|
|
1185
|
+
reachable;
|
|
1186
|
+
country;
|
|
1187
|
+
scheme;
|
|
1188
|
+
id;
|
|
1189
|
+
documentTypes;
|
|
1190
|
+
transportTypeCode;
|
|
1191
|
+
checkedAt;
|
|
1192
|
+
/** True while the directory is still resolving — ask again in a few seconds. */
|
|
1193
|
+
get isPending() {
|
|
1194
|
+
return this.status === PeppolLookupStatus.PENDING;
|
|
1195
|
+
}
|
|
1196
|
+
static fromArray(data) {
|
|
1197
|
+
const documentTypes = Array.isArray(data["document_types"]) ? data["document_types"].filter((t) => typeof t === "string") : [];
|
|
1198
|
+
return new _PeppolLookupResult(
|
|
1199
|
+
toPeppolLookupStatus(data["status"]),
|
|
1200
|
+
typeof data["reachable"] === "boolean" ? data["reachable"] : null,
|
|
1201
|
+
asString4(data["country"]),
|
|
1202
|
+
asString4(data["scheme"]),
|
|
1203
|
+
asString4(data["id"]),
|
|
1204
|
+
documentTypes,
|
|
1205
|
+
asNullableString4(data["transport_type_code"]),
|
|
1206
|
+
asNullableString4(data["checked_at"])
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/dto/RegistryCompany.ts
|
|
1212
|
+
function asString5(value, fallback = "") {
|
|
1213
|
+
return typeof value === "string" ? value : fallback;
|
|
1214
|
+
}
|
|
1215
|
+
function asNullableString5(value) {
|
|
1216
|
+
return typeof value === "string" ? value : null;
|
|
1217
|
+
}
|
|
1218
|
+
var RegistryCompany = class _RegistryCompany {
|
|
1219
|
+
constructor(orgNumber, companyName, organisationForm, vatRegistered, vatNumber, enterpriseRegister, bankrupt, underLiquidation, address, postalcode, city, country) {
|
|
1220
|
+
this.orgNumber = orgNumber;
|
|
1221
|
+
this.companyName = companyName;
|
|
1222
|
+
this.organisationForm = organisationForm;
|
|
1223
|
+
this.vatRegistered = vatRegistered;
|
|
1224
|
+
this.vatNumber = vatNumber;
|
|
1225
|
+
this.enterpriseRegister = enterpriseRegister;
|
|
1226
|
+
this.bankrupt = bankrupt;
|
|
1227
|
+
this.underLiquidation = underLiquidation;
|
|
1228
|
+
this.address = address;
|
|
1229
|
+
this.postalcode = postalcode;
|
|
1230
|
+
this.city = city;
|
|
1231
|
+
this.country = country;
|
|
1232
|
+
}
|
|
1233
|
+
orgNumber;
|
|
1234
|
+
companyName;
|
|
1235
|
+
organisationForm;
|
|
1236
|
+
vatRegistered;
|
|
1237
|
+
vatNumber;
|
|
1238
|
+
enterpriseRegister;
|
|
1239
|
+
bankrupt;
|
|
1240
|
+
underLiquidation;
|
|
1241
|
+
address;
|
|
1242
|
+
postalcode;
|
|
1243
|
+
city;
|
|
1244
|
+
country;
|
|
1245
|
+
static fromArray(data) {
|
|
1246
|
+
return new _RegistryCompany(
|
|
1247
|
+
asString5(data["org_number"]),
|
|
1248
|
+
asString5(data["company_name"]),
|
|
1249
|
+
asNullableString5(data["organisation_form"]),
|
|
1250
|
+
data["vat_registered"] === true,
|
|
1251
|
+
asNullableString5(data["vat_number"]),
|
|
1252
|
+
data["enterprise_register"] === true,
|
|
1253
|
+
data["bankrupt"] === true,
|
|
1254
|
+
data["under_liquidation"] === true,
|
|
1255
|
+
asNullableString5(data["address"]),
|
|
1256
|
+
asNullableString5(data["postalcode"]),
|
|
1257
|
+
asNullableString5(data["city"]),
|
|
1258
|
+
asString5(data["country"])
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
// src/dto/RevenueStatistics.ts
|
|
1264
|
+
function asString6(value, fallback = "") {
|
|
1265
|
+
return typeof value === "string" ? value : fallback;
|
|
1266
|
+
}
|
|
1034
1267
|
function asNumber4(value) {
|
|
1035
1268
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
1036
1269
|
}
|
|
@@ -1080,35 +1313,35 @@ var RevenueStatistics = class _RevenueStatistics {
|
|
|
1080
1313
|
typeof data["totals"] === "object" && data["totals"] !== null ? data["totals"] : {}
|
|
1081
1314
|
);
|
|
1082
1315
|
const timeSeries = asRecords(data["time_series"]).map((row) => ({
|
|
1083
|
-
bucket:
|
|
1316
|
+
bucket: asString6(row["bucket"]),
|
|
1084
1317
|
...toTotals(row)
|
|
1085
1318
|
}));
|
|
1086
1319
|
const byType = asRecords(data["by_type"]).map((row) => ({
|
|
1087
|
-
type:
|
|
1088
|
-
typeLabel:
|
|
1320
|
+
type: asString6(row["type"]),
|
|
1321
|
+
typeLabel: asString6(row["type_label"]),
|
|
1089
1322
|
count: asNumber4(row["count"]),
|
|
1090
1323
|
total: asMoney3(row["total"])
|
|
1091
1324
|
}));
|
|
1092
1325
|
const byCountry = asRecords(data["by_country"]).map((row) => ({
|
|
1093
|
-
country:
|
|
1326
|
+
country: asString6(row["country"], "unknown"),
|
|
1094
1327
|
count: asNumber4(row["count"]),
|
|
1095
1328
|
total: asMoney3(row["total"])
|
|
1096
1329
|
}));
|
|
1097
1330
|
const byState = asRecords(data["by_state"]).map((row) => ({
|
|
1098
|
-
state:
|
|
1099
|
-
stateLabel:
|
|
1331
|
+
state: asString6(row["state"]),
|
|
1332
|
+
stateLabel: asString6(row["state_label"]),
|
|
1100
1333
|
count: asNumber4(row["count"]),
|
|
1101
1334
|
total: asMoney3(row["total"])
|
|
1102
1335
|
}));
|
|
1103
1336
|
const byCurrency = asRecords(data["by_currency"]).map((row) => ({
|
|
1104
|
-
currency:
|
|
1337
|
+
currency: asString6(row["currency"]),
|
|
1105
1338
|
...toTotals(row)
|
|
1106
1339
|
}));
|
|
1107
1340
|
return new _RevenueStatistics(
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1341
|
+
asString6(range["date_from"]) || void 0,
|
|
1342
|
+
asString6(range["date_to"]) || void 0,
|
|
1343
|
+
asString6(range["interval"]) || void 0,
|
|
1344
|
+
asString6(data["primary_currency"], "EUR"),
|
|
1112
1345
|
data["is_multi_currency"] === true,
|
|
1113
1346
|
totals,
|
|
1114
1347
|
timeSeries,
|
|
@@ -1121,10 +1354,10 @@ var RevenueStatistics = class _RevenueStatistics {
|
|
|
1121
1354
|
};
|
|
1122
1355
|
|
|
1123
1356
|
// src/dto/SireneLookupResult.ts
|
|
1124
|
-
function
|
|
1357
|
+
function asString7(value, fallback = "") {
|
|
1125
1358
|
return typeof value === "string" ? value : fallback;
|
|
1126
1359
|
}
|
|
1127
|
-
function
|
|
1360
|
+
function asNullableString6(value) {
|
|
1128
1361
|
return typeof value === "string" ? value : null;
|
|
1129
1362
|
}
|
|
1130
1363
|
var SireneLookupResult = class _SireneLookupResult {
|
|
@@ -1150,21 +1383,21 @@ var SireneLookupResult = class _SireneLookupResult {
|
|
|
1150
1383
|
postalcode;
|
|
1151
1384
|
static fromArray(data) {
|
|
1152
1385
|
return new _SireneLookupResult(
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1386
|
+
asString7(data["company_name"]),
|
|
1387
|
+
asString7(data["siren"]),
|
|
1388
|
+
asNullableString6(data["siret"]),
|
|
1389
|
+
asNullableString6(data["naf_code"]),
|
|
1390
|
+
asNullableString6(data["naf_full"]),
|
|
1391
|
+
asNullableString6(data["enterprise_size"]),
|
|
1392
|
+
asNullableString6(data["address"]),
|
|
1393
|
+
asNullableString6(data["city"]),
|
|
1394
|
+
asNullableString6(data["postalcode"])
|
|
1162
1395
|
);
|
|
1163
1396
|
}
|
|
1164
1397
|
};
|
|
1165
1398
|
|
|
1166
1399
|
// src/dto/VatRates.ts
|
|
1167
|
-
function
|
|
1400
|
+
function asString8(value, fallback = "") {
|
|
1168
1401
|
return typeof value === "string" ? value : fallback;
|
|
1169
1402
|
}
|
|
1170
1403
|
function asNumber5(value) {
|
|
@@ -1182,12 +1415,12 @@ var VatRates = class _VatRates {
|
|
|
1182
1415
|
static fromArray(data) {
|
|
1183
1416
|
const rates = (Array.isArray(data["rates"]) ? data["rates"] : []).filter((row) => typeof row === "object" && row !== null).map((row) => ({
|
|
1184
1417
|
percent: asNumber5(row["percent"]),
|
|
1185
|
-
category:
|
|
1186
|
-
labelKey:
|
|
1187
|
-
taxName:
|
|
1418
|
+
category: asString8(row["category"]),
|
|
1419
|
+
labelKey: asString8(row["label_key"]),
|
|
1420
|
+
taxName: asString8(row["tax_name"]),
|
|
1188
1421
|
requiresVatex: row["requires_vatex"] === true
|
|
1189
1422
|
}));
|
|
1190
|
-
return new _VatRates(
|
|
1423
|
+
return new _VatRates(asString8(data["country"]), asString8(data["currency"], "EUR"), rates);
|
|
1191
1424
|
}
|
|
1192
1425
|
};
|
|
1193
1426
|
|
|
@@ -1206,10 +1439,48 @@ var ValidationException = class extends TaxoraException {
|
|
|
1206
1439
|
}
|
|
1207
1440
|
};
|
|
1208
1441
|
|
|
1442
|
+
// src/support/NorwegianOrgNumber.ts
|
|
1443
|
+
var WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2];
|
|
1444
|
+
var NorwegianOrgNumber = {
|
|
1445
|
+
/** Peppol identifier scheme for Norwegian organisation numbers. */
|
|
1446
|
+
PEPPOL_SCHEME: "0192",
|
|
1447
|
+
/**
|
|
1448
|
+
* Strips whitespace, dots, dashes, a "0192:" Peppol scheme prefix, the "NO"
|
|
1449
|
+
* prefix and the "MVA" suffix: "NO 923 609 016 MVA", "923609016MVA" and
|
|
1450
|
+
* "923 609 016" all become "923609016". Does not validate.
|
|
1451
|
+
*/
|
|
1452
|
+
normalize(value) {
|
|
1453
|
+
let v = (value ?? "").trim().toUpperCase().replace(/[\s.-]/g, "");
|
|
1454
|
+
if (v.startsWith("0192:")) v = v.slice(5);
|
|
1455
|
+
if (v.startsWith("NO")) v = v.slice(2);
|
|
1456
|
+
if (v.endsWith("MVA")) v = v.slice(0, -3);
|
|
1457
|
+
return v;
|
|
1458
|
+
},
|
|
1459
|
+
/** True when the (normalized) value is 9 digits with a valid mod-11 check digit. */
|
|
1460
|
+
isValid(value) {
|
|
1461
|
+
const digits = NorwegianOrgNumber.normalize(value);
|
|
1462
|
+
if (!/^\d{9}$/.test(digits)) return false;
|
|
1463
|
+
let sum = 0;
|
|
1464
|
+
for (let i = 0; i < WEIGHTS.length; i++) {
|
|
1465
|
+
sum += Number(digits[i]) * WEIGHTS[i];
|
|
1466
|
+
}
|
|
1467
|
+
let check = 11 - sum % 11;
|
|
1468
|
+
if (check === 11) check = 0;
|
|
1469
|
+
return check !== 10 && check === Number(digits[8]);
|
|
1470
|
+
},
|
|
1471
|
+
/** "NO923609016MVA" — the Norwegian VAT number of an MVA-registered entity. Does not validate. */
|
|
1472
|
+
toVatNumber(value) {
|
|
1473
|
+
return `NO${NorwegianOrgNumber.normalize(value)}MVA`;
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1209
1477
|
// src/endpoints/EReportingEndpoint.ts
|
|
1210
1478
|
var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
1211
1479
|
var NAF_CODE_REGEX = /^\d{2}$/;
|
|
1480
|
+
var COUNTRY_REGEX = /^[A-Za-z]{2}$/;
|
|
1481
|
+
var PEPPOL_SCHEME_REGEX = /^\d{4}$/;
|
|
1212
1482
|
var INTERVALS = ["day", "week", "month"];
|
|
1483
|
+
var SERVICES = ["e_reporting", "e_invoicing"];
|
|
1213
1484
|
var ENTERPRISE_SIZES = ["micro", "pme", "eti", "ge"];
|
|
1214
1485
|
var TYPE_OPERATIONS = ["services", "goods", "mixed"];
|
|
1215
1486
|
var TRANSACTION_TYPES = [
|
|
@@ -1237,14 +1508,23 @@ var EReportingEndpoint = class {
|
|
|
1237
1508
|
// ---------------------------------------------------------------------
|
|
1238
1509
|
/**
|
|
1239
1510
|
* Create a compliance enrollment and provision the provider account. By
|
|
1240
|
-
* default the
|
|
1511
|
+
* default the country regime is activated right away (France: DGFiP tax
|
|
1512
|
+
* report setting; Norway: Peppol registration); pass `autoActivate: false`
|
|
1241
1513
|
* to only create the account.
|
|
1242
1514
|
*
|
|
1243
|
-
*
|
|
1244
|
-
* the
|
|
1515
|
+
* The input shape depends on `country`: omitted/"FR" takes the France input
|
|
1516
|
+
* (unchanged, backwards compatible), "NO" the Norway input — see also the
|
|
1517
|
+
* createNorwayEnrollment() shortcut.
|
|
1518
|
+
*
|
|
1519
|
+
* Errors: 403 (HttpException, body code `service_not_active`) when the company
|
|
1520
|
+
* lacks the requested service; on provider failure 502 (HttpException) — the
|
|
1521
|
+
* enrollment is then persisted in `error_account`/`error_activation` state so
|
|
1245
1522
|
* provisioning can be retried.
|
|
1246
1523
|
*/
|
|
1247
1524
|
async createEnrollment(input) {
|
|
1525
|
+
if (this.isNorwayEnrollmentInput(input)) {
|
|
1526
|
+
return this.createEnrollmentFromBody(this.buildNorwayEnrollmentBody(input));
|
|
1527
|
+
}
|
|
1248
1528
|
this.validateEnrollmentInput(input);
|
|
1249
1529
|
const body = {
|
|
1250
1530
|
email: input.email,
|
|
@@ -1254,6 +1534,7 @@ var EReportingEndpoint = class {
|
|
|
1254
1534
|
reporting_start_date: this.formatDate(input.reportingStartDate)
|
|
1255
1535
|
};
|
|
1256
1536
|
if (input.country !== void 0) body["country"] = input.country;
|
|
1537
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1257
1538
|
if (input.regime !== void 0) body["regime"] = input.regime;
|
|
1258
1539
|
if (input.siret !== void 0) body["siret"] = input.siret;
|
|
1259
1540
|
if (input.siren !== void 0) body["siren"] = input.siren;
|
|
@@ -1264,9 +1545,15 @@ var EReportingEndpoint = class {
|
|
|
1264
1545
|
if (input.postalcode !== void 0) body["postalcode"] = input.postalcode;
|
|
1265
1546
|
if (input.province !== void 0) body["province"] = input.province;
|
|
1266
1547
|
if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1548
|
+
return this.createEnrollmentFromBody(body);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Enroll the company for Norway e-invoicing (EHF / Peppol BIS 3.0) — shortcut
|
|
1552
|
+
* for createEnrollment({ country: 'NO', ... }). The organisation number is
|
|
1553
|
+
* checked (mod-11) and normalized to 9 digits before sending.
|
|
1554
|
+
*/
|
|
1555
|
+
async createNorwayEnrollment(input) {
|
|
1556
|
+
return this.createEnrollment({ ...input, country: "NO" });
|
|
1270
1557
|
}
|
|
1271
1558
|
/** Paginated list of the company's enrollments (newest first). `perPage` is capped at 100 server-side. */
|
|
1272
1559
|
async listEnrollments(page = 1, perPage = 25) {
|
|
@@ -1300,6 +1587,72 @@ var EReportingEndpoint = class {
|
|
|
1300
1587
|
const data = await this.parseJsonResponse(response);
|
|
1301
1588
|
return SireneLookupResult.fromArray(data);
|
|
1302
1589
|
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Look up companies in a national company register — currently Norway
|
|
1592
|
+
* (Brønnøysund Enhetsregisteret). `q` is an organisation number, a "NO…MVA"
|
|
1593
|
+
* VAT number or a company name; an exact number match yields one result.
|
|
1594
|
+
* Ideal for prefilling createNorwayEnrollment() and the buyer fields of an
|
|
1595
|
+
* e-invoice. For France use sireneLookup().
|
|
1596
|
+
*
|
|
1597
|
+
* Errors: 404 (HttpException) when nothing is found, 422 (ValidationException)
|
|
1598
|
+
* on invalid input / unsupported country, 429 (HttpException) when the
|
|
1599
|
+
* per-user rate limit (30/min) is exceeded.
|
|
1600
|
+
*/
|
|
1601
|
+
async registryLookup(country, q) {
|
|
1602
|
+
const normalizedCountry = this.normalizeLookupCountry(country);
|
|
1603
|
+
const query = q.trim();
|
|
1604
|
+
if (query === "") {
|
|
1605
|
+
throw new TaxoraException("q must not be empty.");
|
|
1606
|
+
}
|
|
1607
|
+
if (normalizedCountry === "NO") {
|
|
1608
|
+
const digits = NorwegianOrgNumber.normalize(query);
|
|
1609
|
+
if (/^\d{9}$/.test(digits) && !NorwegianOrgNumber.isValid(digits)) {
|
|
1610
|
+
throw new TaxoraException(`Invalid Norwegian organisation number "${query}" (check digit).`);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
const params = new URLSearchParams();
|
|
1614
|
+
params.set("country", normalizedCountry);
|
|
1615
|
+
params.set("q", query);
|
|
1616
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/registry-lookup?${params.toString()}`);
|
|
1617
|
+
const data = await this.parseJsonResponse(response);
|
|
1618
|
+
const results = Array.isArray(data["results"]) ? data["results"] : [];
|
|
1619
|
+
return results.filter((row) => typeof row === "object" && row !== null && !Array.isArray(row)).map((row) => RegistryCompany.fromArray(row));
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Check whether a participant (typically the buyer) can receive e-invoices
|
|
1623
|
+
* over Peppol. For Norway pass the organisation number (scheme defaults to
|
|
1624
|
+
* "0192"): `reachable === true` means the buyer is registered in ELMA and must
|
|
1625
|
+
* receive an EHF e-invoice. The directory resolves asynchronously —
|
|
1626
|
+
* `status === 'pending'` (`reachable === null`) means ask again in a few
|
|
1627
|
+
* seconds. Free of charge; definite answers are cached for 24h server-side.
|
|
1628
|
+
*
|
|
1629
|
+
* A Norwegian organisation number is checked (mod-11) and normalized before
|
|
1630
|
+
* sending. Errors: 422 (ValidationException) on invalid input, 502
|
|
1631
|
+
* (HttpException) on provider failure.
|
|
1632
|
+
*/
|
|
1633
|
+
async peppolLookup(country, id, scheme) {
|
|
1634
|
+
const normalizedCountry = this.normalizeLookupCountry(country);
|
|
1635
|
+
let participantId = id.trim();
|
|
1636
|
+
if (participantId === "") {
|
|
1637
|
+
throw new TaxoraException("id must not be empty.");
|
|
1638
|
+
}
|
|
1639
|
+
if (scheme !== void 0 && !PEPPOL_SCHEME_REGEX.test(scheme)) {
|
|
1640
|
+
throw new TaxoraException('scheme must be a 4-digit Peppol identifier scheme, e.g. "0192".');
|
|
1641
|
+
}
|
|
1642
|
+
if (normalizedCountry === "NO" && (scheme === void 0 || scheme === NorwegianOrgNumber.PEPPOL_SCHEME)) {
|
|
1643
|
+
if (!NorwegianOrgNumber.isValid(participantId)) {
|
|
1644
|
+
throw new TaxoraException(`Invalid Norwegian organisation number "${participantId}".`);
|
|
1645
|
+
}
|
|
1646
|
+
participantId = NorwegianOrgNumber.normalize(participantId);
|
|
1647
|
+
}
|
|
1648
|
+
const params = new URLSearchParams();
|
|
1649
|
+
params.set("country", normalizedCountry);
|
|
1650
|
+
params.set("id", participantId);
|
|
1651
|
+
if (scheme !== void 0) params.set("scheme", scheme);
|
|
1652
|
+
const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/peppol-lookup?${params.toString()}`);
|
|
1653
|
+
const data = await this.parseJsonResponse(response);
|
|
1654
|
+
return PeppolLookupResult.fromArray(data);
|
|
1655
|
+
}
|
|
1303
1656
|
// ---------------------------------------------------------------------
|
|
1304
1657
|
// Transactions
|
|
1305
1658
|
// ---------------------------------------------------------------------
|
|
@@ -1323,6 +1676,7 @@ var EReportingEndpoint = class {
|
|
|
1323
1676
|
this.validateInvoiceNumber(input.invoiceNumber);
|
|
1324
1677
|
this.validateInvoiceLines(input.invoiceLines);
|
|
1325
1678
|
this.validateOptionalCodes(input);
|
|
1679
|
+
this.validateBuyerFields(input);
|
|
1326
1680
|
const body = {
|
|
1327
1681
|
compliance_enrollment_id: input.complianceEnrollmentId,
|
|
1328
1682
|
transaction_type: input.transactionType,
|
|
@@ -1338,13 +1692,23 @@ var EReportingEndpoint = class {
|
|
|
1338
1692
|
const data = await this.parseJsonResponse(response);
|
|
1339
1693
|
return ComplianceTransaction.fromArray(data);
|
|
1340
1694
|
}
|
|
1341
|
-
/** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type. */
|
|
1695
|
+
/** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type / enrollment / country. */
|
|
1342
1696
|
async listTransactions(filters = {}) {
|
|
1697
|
+
if (filters.complianceEnrollmentId !== void 0) {
|
|
1698
|
+
this.validateId(filters.complianceEnrollmentId, "complianceEnrollmentId");
|
|
1699
|
+
}
|
|
1700
|
+
if (filters.country !== void 0 && !COUNTRY_REGEX.test(filters.country)) {
|
|
1701
|
+
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1702
|
+
}
|
|
1343
1703
|
const params = new URLSearchParams();
|
|
1344
1704
|
if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
|
|
1345
1705
|
if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
|
|
1346
1706
|
if (filters.state !== void 0) params.set("state", filters.state);
|
|
1347
1707
|
if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
|
|
1708
|
+
if (filters.complianceEnrollmentId !== void 0) {
|
|
1709
|
+
params.set("compliance_enrollment_id", String(filters.complianceEnrollmentId));
|
|
1710
|
+
}
|
|
1711
|
+
if (filters.country !== void 0) params.set("country", filters.country.toUpperCase());
|
|
1348
1712
|
if (filters.page !== void 0) params.set("page", String(filters.page));
|
|
1349
1713
|
if (filters.perPage !== void 0) params.set("per_page", String(filters.perPage));
|
|
1350
1714
|
const queryString = params.toString();
|
|
@@ -1366,6 +1730,7 @@ var EReportingEndpoint = class {
|
|
|
1366
1730
|
if (input.invoiceNumber !== void 0) this.validateInvoiceNumber(input.invoiceNumber);
|
|
1367
1731
|
if (input.invoiceLines !== void 0) this.validateInvoiceLines(input.invoiceLines);
|
|
1368
1732
|
this.validateOptionalCodes(input);
|
|
1733
|
+
this.validateBuyerFields(input);
|
|
1369
1734
|
const body = {};
|
|
1370
1735
|
if (input.transactionType !== void 0) body["transaction_type"] = input.transactionType;
|
|
1371
1736
|
if (input.invoiceNumber !== void 0) body["invoice_number"] = input.invoiceNumber;
|
|
@@ -1488,6 +1853,14 @@ var EReportingEndpoint = class {
|
|
|
1488
1853
|
* returned as HTTP 400 (surfaced as HttpException), not 422.
|
|
1489
1854
|
*/
|
|
1490
1855
|
async requestEReportingAccess(input = {}) {
|
|
1856
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1857
|
+
if (input.countries !== void 0) {
|
|
1858
|
+
input.countries.forEach((country, index) => {
|
|
1859
|
+
if (!COUNTRY_REGEX.test(country)) {
|
|
1860
|
+
throw new TaxoraException(`countries[${index}] must be a 2-character ISO 3166-1 alpha-2 code.`);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1491
1864
|
const body = {};
|
|
1492
1865
|
if (input.name !== void 0) body["name"] = input.name;
|
|
1493
1866
|
if (input.email !== void 0) body["email"] = input.email;
|
|
@@ -1495,6 +1868,8 @@ var EReportingEndpoint = class {
|
|
|
1495
1868
|
if (input.phone !== void 0) body["phone"] = input.phone;
|
|
1496
1869
|
if (input.message !== void 0) body["message"] = input.message;
|
|
1497
1870
|
if (input.language !== void 0) body["language"] = input.language;
|
|
1871
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1872
|
+
if (input.countries !== void 0) body["countries"] = input.countries.map((c) => c.toUpperCase());
|
|
1498
1873
|
const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/e-reporting-request`, body);
|
|
1499
1874
|
await this.parseJsonResponse(response);
|
|
1500
1875
|
}
|
|
@@ -1530,6 +1905,73 @@ var EReportingEndpoint = class {
|
|
|
1530
1905
|
if (input.country !== void 0 && input.country.length !== 2) {
|
|
1531
1906
|
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1532
1907
|
}
|
|
1908
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1909
|
+
}
|
|
1910
|
+
isNorwayEnrollmentInput(input) {
|
|
1911
|
+
return typeof input.country === "string" && input.country.trim().toUpperCase() === "NO";
|
|
1912
|
+
}
|
|
1913
|
+
/** Validates the Norway input and builds its wire body (org number normalized to 9 digits). */
|
|
1914
|
+
buildNorwayEnrollmentBody(input) {
|
|
1915
|
+
if (typeof input.orgNumber !== "string" || !NorwegianOrgNumber.isValid(input.orgNumber)) {
|
|
1916
|
+
throw new TaxoraException(
|
|
1917
|
+
"orgNumber must be a valid 9-digit Norwegian organisation number (mod-11 check digit)."
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
for (const field of ["companyName", "address", "city", "postalcode"]) {
|
|
1921
|
+
if (typeof input[field] !== "string" || input[field].trim() === "") {
|
|
1922
|
+
throw new TaxoraException(`${field} is required for a Norway enrollment.`);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
this.validateEmail(input.email, "email");
|
|
1926
|
+
if (input.service !== void 0) this.validateService(input.service);
|
|
1927
|
+
if (input.legalCountry !== void 0 && !COUNTRY_REGEX.test(input.legalCountry)) {
|
|
1928
|
+
throw new TaxoraException("legalCountry must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1929
|
+
}
|
|
1930
|
+
const body = {
|
|
1931
|
+
country: "NO",
|
|
1932
|
+
org_number: NorwegianOrgNumber.normalize(input.orgNumber),
|
|
1933
|
+
company_name: input.companyName,
|
|
1934
|
+
address: input.address,
|
|
1935
|
+
city: input.city,
|
|
1936
|
+
postalcode: input.postalcode,
|
|
1937
|
+
email: input.email
|
|
1938
|
+
};
|
|
1939
|
+
if (input.service !== void 0) body["service"] = input.service;
|
|
1940
|
+
if (input.regime !== void 0) body["regime"] = input.regime;
|
|
1941
|
+
if (input.vatNumber !== void 0) body["vat_number"] = input.vatNumber;
|
|
1942
|
+
if (input.province !== void 0) body["province"] = input.province;
|
|
1943
|
+
if (input.vatRegistered !== void 0) body["vat_registered"] = input.vatRegistered;
|
|
1944
|
+
if (input.enterpriseRegister !== void 0) body["enterprise_register"] = input.enterpriseRegister;
|
|
1945
|
+
if (input.reception !== void 0) body["reception"] = input.reception;
|
|
1946
|
+
if (input.legalCountry !== void 0) body["legal_country"] = input.legalCountry.toUpperCase();
|
|
1947
|
+
if (input.reportingStartDate !== void 0) {
|
|
1948
|
+
body["reporting_start_date"] = this.formatDate(input.reportingStartDate);
|
|
1949
|
+
}
|
|
1950
|
+
if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
|
|
1951
|
+
return body;
|
|
1952
|
+
}
|
|
1953
|
+
async createEnrollmentFromBody(body) {
|
|
1954
|
+
const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/enrollments`, body);
|
|
1955
|
+
const data = await this.parseJsonResponse(response);
|
|
1956
|
+
return ComplianceEnrollment.fromArray(data);
|
|
1957
|
+
}
|
|
1958
|
+
validateService(service) {
|
|
1959
|
+
if (!SERVICES.includes(service)) {
|
|
1960
|
+
throw new TaxoraException(`Invalid service "${String(service)}". Expected one of: ${SERVICES.join(", ")}.`);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
validateEmail(email, name) {
|
|
1964
|
+
if (typeof email !== "string" || email.trim() === "" || !email.includes("@")) {
|
|
1965
|
+
throw new TaxoraException(`${name} must be a valid email address.`);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
/** Lookup routes take a 2-letter country; the API expects it upper-case. */
|
|
1969
|
+
normalizeLookupCountry(country) {
|
|
1970
|
+
const normalized = country.trim().toUpperCase();
|
|
1971
|
+
if (!COUNTRY_REGEX.test(normalized)) {
|
|
1972
|
+
throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1973
|
+
}
|
|
1974
|
+
return normalized;
|
|
1533
1975
|
}
|
|
1534
1976
|
validateId(id, name) {
|
|
1535
1977
|
if (!Number.isInteger(id) || id <= 0) {
|
|
@@ -1569,6 +2011,33 @@ var EReportingEndpoint = class {
|
|
|
1569
2011
|
throw new TaxoraException("counterpartyCountry must be a 2-character ISO 3166-1 alpha-2 code.");
|
|
1570
2012
|
}
|
|
1571
2013
|
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Format checks for the buyer/document fields. Country rules (e.g. the Norway
|
|
2016
|
+
* org number being required) depend on the enrollment and stay server-side;
|
|
2017
|
+
* a register id is only check-digit validated when the buyer is Norwegian.
|
|
2018
|
+
*/
|
|
2019
|
+
validateBuyerFields(input) {
|
|
2020
|
+
if (input.counterpartyRegisterId !== void 0) {
|
|
2021
|
+
if (input.counterpartyRegisterId.length > 30) {
|
|
2022
|
+
throw new TaxoraException("counterpartyRegisterId must not exceed 30 characters.");
|
|
2023
|
+
}
|
|
2024
|
+
if (input.counterpartyCountry?.toUpperCase() === "NO" && !NorwegianOrgNumber.isValid(input.counterpartyRegisterId)) {
|
|
2025
|
+
throw new TaxoraException(
|
|
2026
|
+
"counterpartyRegisterId must be a valid 9-digit Norwegian organisation number (mod-11 check digit)."
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (input.counterpartyEmail !== void 0) this.validateEmail(input.counterpartyEmail, "counterpartyEmail");
|
|
2031
|
+
if (input.buyerReference !== void 0 && input.buyerReference.length > 100) {
|
|
2032
|
+
throw new TaxoraException("buyerReference must not exceed 100 characters.");
|
|
2033
|
+
}
|
|
2034
|
+
if (input.amendedNumber !== void 0 && input.amendedNumber.length > 50) {
|
|
2035
|
+
throw new TaxoraException("amendedNumber must not exceed 50 characters.");
|
|
2036
|
+
}
|
|
2037
|
+
if (input.extraInfo !== void 0 && input.extraInfo.length > 1e3) {
|
|
2038
|
+
throw new TaxoraException("extraInfo must not exceed 1000 characters.");
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1572
2041
|
// ---------------------------------------------------------------------
|
|
1573
2042
|
// Body building (camelCase input → snake_case wire format)
|
|
1574
2043
|
// ---------------------------------------------------------------------
|
|
@@ -1605,6 +2074,16 @@ var EReportingEndpoint = class {
|
|
|
1605
2074
|
if (input.remittanceInformation !== void 0) body["remittance_information"] = input.remittanceInformation;
|
|
1606
2075
|
if (input.paymentMethodText !== void 0) body["payment_method_text"] = input.paymentMethodText;
|
|
1607
2076
|
if (input.paymentTerms !== void 0) body["payment_terms"] = input.paymentTerms;
|
|
2077
|
+
if (input.counterpartyRegisterId !== void 0) body["counterparty_register_id"] = input.counterpartyRegisterId;
|
|
2078
|
+
if (input.counterpartyAddress !== void 0) body["counterparty_address"] = input.counterpartyAddress;
|
|
2079
|
+
if (input.counterpartyCity !== void 0) body["counterparty_city"] = input.counterpartyCity;
|
|
2080
|
+
if (input.counterpartyPostalcode !== void 0) body["counterparty_postalcode"] = input.counterpartyPostalcode;
|
|
2081
|
+
if (input.counterpartyEmail !== void 0) body["counterparty_email"] = input.counterpartyEmail;
|
|
2082
|
+
if (input.buyerReference !== void 0) body["buyer_reference"] = input.buyerReference;
|
|
2083
|
+
if (input.isCreditNote !== void 0) body["is_credit_note"] = input.isCreditNote;
|
|
2084
|
+
if (input.amendedNumber !== void 0) body["amended_number"] = input.amendedNumber;
|
|
2085
|
+
if (input.amendedDate !== void 0) body["amended_date"] = this.formatDate(input.amendedDate);
|
|
2086
|
+
if (input.extraInfo !== void 0) body["extra_info"] = input.extraInfo;
|
|
1608
2087
|
}
|
|
1609
2088
|
// ---------------------------------------------------------------------
|
|
1610
2089
|
// HTTP plumbing (kept in sync with SmartEnrichmentEndpoint)
|
|
@@ -1727,22 +2206,22 @@ var SmartEnrichmentMode = {
|
|
|
1727
2206
|
DEFAULT: "default",
|
|
1728
2207
|
COMPLEX: "complex"
|
|
1729
2208
|
};
|
|
1730
|
-
var
|
|
2209
|
+
var VALUES8 = Object.values(SmartEnrichmentMode);
|
|
1731
2210
|
function toSmartEnrichmentMode(value) {
|
|
1732
2211
|
if (typeof value === "string") {
|
|
1733
2212
|
const normalized = value.trim().toLowerCase();
|
|
1734
|
-
if (
|
|
2213
|
+
if (VALUES8.includes(normalized)) {
|
|
1735
2214
|
return normalized;
|
|
1736
2215
|
}
|
|
1737
2216
|
}
|
|
1738
2217
|
return void 0;
|
|
1739
2218
|
}
|
|
1740
|
-
var
|
|
2219
|
+
var DESCRIPTIONS8 = {
|
|
1741
2220
|
[SmartEnrichmentMode.DEFAULT]: "One provider searches; a second is consulted only if it finds nothing.",
|
|
1742
2221
|
[SmartEnrichmentMode.COMPLEX]: "Two providers search independently and are cross-checked against the tax authority. Costs more."
|
|
1743
2222
|
};
|
|
1744
2223
|
function describeSmartEnrichmentMode(mode) {
|
|
1745
|
-
return
|
|
2224
|
+
return DESCRIPTIONS8[mode];
|
|
1746
2225
|
}
|
|
1747
2226
|
|
|
1748
2227
|
// src/enums/SmartEnrichmentStatus.ts
|
|
@@ -1756,17 +2235,17 @@ var SmartEnrichmentStatus = {
|
|
|
1756
2235
|
FAILED: "failed",
|
|
1757
2236
|
UNKNOWN: "unknown"
|
|
1758
2237
|
};
|
|
1759
|
-
var
|
|
2238
|
+
var VALUES9 = Object.values(SmartEnrichmentStatus);
|
|
1760
2239
|
function toSmartEnrichmentStatus(value) {
|
|
1761
2240
|
if (typeof value === "string") {
|
|
1762
2241
|
const normalized = value.trim().toLowerCase();
|
|
1763
|
-
if (
|
|
2242
|
+
if (VALUES9.includes(normalized)) {
|
|
1764
2243
|
return normalized;
|
|
1765
2244
|
}
|
|
1766
2245
|
}
|
|
1767
2246
|
return SmartEnrichmentStatus.UNKNOWN;
|
|
1768
2247
|
}
|
|
1769
|
-
var
|
|
2248
|
+
var DESCRIPTIONS9 = {
|
|
1770
2249
|
[SmartEnrichmentStatus.FOUND]: "A VAT number was resolved and confirmed.",
|
|
1771
2250
|
[SmartEnrichmentStatus.NO_VAT_EXISTS]: "The company was identified but has no VAT number.",
|
|
1772
2251
|
[SmartEnrichmentStatus.NOT_FOUND]: "No sufficiently-confident match could be resolved.",
|
|
@@ -1777,7 +2256,7 @@ var DESCRIPTIONS6 = {
|
|
|
1777
2256
|
[SmartEnrichmentStatus.UNKNOWN]: "Unknown status."
|
|
1778
2257
|
};
|
|
1779
2258
|
function describeSmartEnrichmentStatus(status) {
|
|
1780
|
-
return
|
|
2259
|
+
return DESCRIPTIONS9[status];
|
|
1781
2260
|
}
|
|
1782
2261
|
|
|
1783
2262
|
// src/dto/SmartEnrichmentResource.ts
|
|
@@ -1995,13 +2474,13 @@ var SmartEnrichmentHistoryPage = class _SmartEnrichmentHistoryPage {
|
|
|
1995
2474
|
};
|
|
1996
2475
|
|
|
1997
2476
|
// src/dto/SmartEnrichmentStatistics.ts
|
|
1998
|
-
function
|
|
2477
|
+
function asString9(value, fallback = "") {
|
|
1999
2478
|
return typeof value === "string" ? value : fallback;
|
|
2000
2479
|
}
|
|
2001
2480
|
function asNumber6(value) {
|
|
2002
2481
|
return typeof value === "number" ? value : Number(value ?? 0) || 0;
|
|
2003
2482
|
}
|
|
2004
|
-
function
|
|
2483
|
+
function asRecord2(value) {
|
|
2005
2484
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2006
2485
|
}
|
|
2007
2486
|
function asRecords2(value) {
|
|
@@ -2024,32 +2503,32 @@ var SmartEnrichmentStatistics = class _SmartEnrichmentStatistics {
|
|
|
2024
2503
|
byOutcome;
|
|
2025
2504
|
confidenceBuckets;
|
|
2026
2505
|
static fromArray(data) {
|
|
2027
|
-
const range =
|
|
2028
|
-
const totals =
|
|
2506
|
+
const range = asRecord2(data["range"]);
|
|
2507
|
+
const totals = asRecord2(data["totals"]);
|
|
2029
2508
|
const timeSeries = asRecords2(data["time_series"]).map((row) => ({
|
|
2030
|
-
bucket:
|
|
2509
|
+
bucket: asString9(row["bucket"]),
|
|
2031
2510
|
jobs: asNumber6(row["jobs"]),
|
|
2032
2511
|
items: asNumber6(row["items"]),
|
|
2033
2512
|
found: asNumber6(row["found"])
|
|
2034
2513
|
}));
|
|
2035
2514
|
const bySource = asRecords2(data["by_source"]).map((row) => ({
|
|
2036
|
-
source:
|
|
2515
|
+
source: asString9(row["source"]),
|
|
2037
2516
|
jobs: asNumber6(row["jobs"]),
|
|
2038
2517
|
found: asNumber6(row["found"])
|
|
2039
2518
|
}));
|
|
2040
2519
|
const byOutcome = asRecords2(data["by_outcome"]).map((row) => ({
|
|
2041
|
-
outcome:
|
|
2520
|
+
outcome: asString9(row["outcome"]),
|
|
2042
2521
|
count: asNumber6(row["count"])
|
|
2043
2522
|
}));
|
|
2044
2523
|
const confidenceBuckets = asRecords2(data["confidence_buckets"]).map((row) => ({
|
|
2045
|
-
bucket:
|
|
2524
|
+
bucket: asString9(row["bucket"]),
|
|
2046
2525
|
count: asNumber6(row["count"])
|
|
2047
2526
|
}));
|
|
2048
2527
|
return new _SmartEnrichmentStatistics(
|
|
2049
2528
|
{
|
|
2050
|
-
dateFrom:
|
|
2051
|
-
dateTo:
|
|
2052
|
-
interval:
|
|
2529
|
+
dateFrom: asString9(range["date_from"]),
|
|
2530
|
+
dateTo: asString9(range["date_to"]),
|
|
2531
|
+
interval: asString9(range["interval"])
|
|
2053
2532
|
},
|
|
2054
2533
|
{
|
|
2055
2534
|
jobs: asNumber6(totals["jobs"]),
|
|
@@ -2264,7 +2743,10 @@ var SmartEnrichmentEndpoint = class {
|
|
|
2264
2743
|
async parseJsonResponse(response, acceptedStatuses = [200]) {
|
|
2265
2744
|
const responseText = await response.text();
|
|
2266
2745
|
if (response.status === 422) {
|
|
2267
|
-
throw new ValidationException(
|
|
2746
|
+
throw new ValidationException(
|
|
2747
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2748
|
+
this.parseValidationErrors(responseText)
|
|
2749
|
+
);
|
|
2268
2750
|
}
|
|
2269
2751
|
if (!response.ok && !acceptedStatuses.includes(response.status)) {
|
|
2270
2752
|
throw new HttpException(
|
|
@@ -2298,7 +2780,10 @@ var SmartEnrichmentEndpoint = class {
|
|
|
2298
2780
|
async parseRawJson(response) {
|
|
2299
2781
|
const responseText = await response.text();
|
|
2300
2782
|
if (response.status === 422) {
|
|
2301
|
-
throw new ValidationException(
|
|
2783
|
+
throw new ValidationException(
|
|
2784
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2785
|
+
this.parseValidationErrors(responseText)
|
|
2786
|
+
);
|
|
2302
2787
|
}
|
|
2303
2788
|
if (!response.ok) {
|
|
2304
2789
|
throw new HttpException(
|
|
@@ -2327,7 +2812,10 @@ var SmartEnrichmentEndpoint = class {
|
|
|
2327
2812
|
async parseCsvResponse(response) {
|
|
2328
2813
|
const responseText = await response.text();
|
|
2329
2814
|
if (response.status === 422) {
|
|
2330
|
-
throw new ValidationException(
|
|
2815
|
+
throw new ValidationException(
|
|
2816
|
+
jsonErrorMessage(responseText) ?? "Validation failed",
|
|
2817
|
+
this.parseValidationErrors(responseText)
|
|
2818
|
+
);
|
|
2331
2819
|
}
|
|
2332
2820
|
if (!response.ok) {
|
|
2333
2821
|
throw new HttpException(
|
|
@@ -2373,14 +2861,14 @@ var VatState = {
|
|
|
2373
2861
|
function getFailedVatStates() {
|
|
2374
2862
|
return [VatState.INVALID, VatState.FRAUD];
|
|
2375
2863
|
}
|
|
2376
|
-
var
|
|
2864
|
+
var DESCRIPTIONS10 = {
|
|
2377
2865
|
[VatState.VALID]: "The VAT number is valid and active.",
|
|
2378
2866
|
[VatState.INVALID]: "The VAT number is invalid or not registered.",
|
|
2379
2867
|
[VatState.FRAUD]: "The VAT number has been flagged as potentially fraudulent.",
|
|
2380
2868
|
[VatState.UNKNOWN]: "The VAT number state could not be determined."
|
|
2381
2869
|
};
|
|
2382
2870
|
function describeVatState(state) {
|
|
2383
|
-
return
|
|
2871
|
+
return DESCRIPTIONS10[state];
|
|
2384
2872
|
}
|
|
2385
2873
|
|
|
2386
2874
|
// src/dto/CompanyAddress.ts
|
|
@@ -3112,7 +3600,7 @@ var AuthRetryHttpClient = class {
|
|
|
3112
3600
|
};
|
|
3113
3601
|
|
|
3114
3602
|
// src/version.ts
|
|
3115
|
-
var SDK_VERSION = "1.
|
|
3603
|
+
var SDK_VERSION = "1.3.0";
|
|
3116
3604
|
|
|
3117
3605
|
// src/http/SdkVersionHttpClient.ts
|
|
3118
3606
|
var SDK_VERSION_HEADER = "X-Taxora-SDK-Version";
|
|
@@ -3151,7 +3639,13 @@ var TaxoraClient = class {
|
|
|
3151
3639
|
const retryClient = new AuthRetryHttpClient(versionedClient, tokenStorage, () => this.auth.refresh());
|
|
3152
3640
|
this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3153
3641
|
this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3154
|
-
this.smartEnrichment = new SmartEnrichmentEndpoint(
|
|
3642
|
+
this.smartEnrichment = new SmartEnrichmentEndpoint(
|
|
3643
|
+
this.baseUrl,
|
|
3644
|
+
options.apiKey,
|
|
3645
|
+
tokenStorage,
|
|
3646
|
+
retryClient,
|
|
3647
|
+
retryPolicy
|
|
3648
|
+
);
|
|
3155
3649
|
this.eReporting = new EReportingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
|
|
3156
3650
|
}
|
|
3157
3651
|
};
|
|
@@ -3173,6 +3667,125 @@ var Language = {
|
|
|
3173
3667
|
ENGLISH: "en",
|
|
3174
3668
|
GERMAN: "de"
|
|
3175
3669
|
};
|
|
3670
|
+
|
|
3671
|
+
// src/webhooks/WebhookSignature.ts
|
|
3672
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3673
|
+
var WEBHOOK_SIGNATURE_HEADER = "Taxora-Signature";
|
|
3674
|
+
var WEBHOOK_ID_HEADER = "Taxora-Webhook-Id";
|
|
3675
|
+
var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
3676
|
+
var SCHEME = "v1";
|
|
3677
|
+
var WebhookSignatureVerificationError = class extends TaxoraException {
|
|
3678
|
+
reason;
|
|
3679
|
+
constructor(message, reason, context = {}) {
|
|
3680
|
+
super(message, { ...context, reason });
|
|
3681
|
+
this.name = "WebhookSignatureVerificationError";
|
|
3682
|
+
this.reason = reason;
|
|
3683
|
+
}
|
|
3684
|
+
};
|
|
3685
|
+
function parseWebhookSignatureHeader(header) {
|
|
3686
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
3687
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
3688
|
+
throw new WebhookSignatureVerificationError(
|
|
3689
|
+
`Missing ${WEBHOOK_SIGNATURE_HEADER} header. Is signing enabled for this webhook endpoint?`,
|
|
3690
|
+
"missing_header"
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3693
|
+
let timestamp = null;
|
|
3694
|
+
const signatures = [];
|
|
3695
|
+
for (const part of value.split(",")) {
|
|
3696
|
+
const separator = part.indexOf("=");
|
|
3697
|
+
if (separator === -1) continue;
|
|
3698
|
+
const key = part.slice(0, separator).trim();
|
|
3699
|
+
const val = part.slice(separator + 1).trim();
|
|
3700
|
+
if (key === "t" && /^\d+$/.test(val)) {
|
|
3701
|
+
timestamp = Number(val);
|
|
3702
|
+
} else if (key === SCHEME && /^[0-9a-f]{64}$/i.test(val)) {
|
|
3703
|
+
signatures.push(val.toLowerCase());
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
if (timestamp === null || signatures.length === 0) {
|
|
3707
|
+
throw new WebhookSignatureVerificationError(
|
|
3708
|
+
`Malformed ${WEBHOOK_SIGNATURE_HEADER} header \u2014 expected "t=<unix>,v1=<hex>".`,
|
|
3709
|
+
"malformed_header"
|
|
3710
|
+
);
|
|
3711
|
+
}
|
|
3712
|
+
return { timestamp, signatures };
|
|
3713
|
+
}
|
|
3714
|
+
function computeWebhookSignature(rawBody, timestamp, secret) {
|
|
3715
|
+
return createHmac("sha256", secret).update(`${timestamp}.`).update(toBytes(rawBody)).digest("hex");
|
|
3716
|
+
}
|
|
3717
|
+
function generateWebhookSignatureHeader(rawBody, secret, timestamp = Math.floor(Date.now() / 1e3)) {
|
|
3718
|
+
const secrets = normalizeSecrets(secret);
|
|
3719
|
+
return [`t=${timestamp}`, ...secrets.map((s) => `${SCHEME}=${computeWebhookSignature(rawBody, timestamp, s)}`)].join(
|
|
3720
|
+
","
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
function verifyWebhookSignature(rawBody, header, secret, options = {}) {
|
|
3724
|
+
const secrets = normalizeSecrets(secret);
|
|
3725
|
+
const bytes = toBytes(rawBody);
|
|
3726
|
+
const { timestamp, signatures } = parseWebhookSignatureHeader(header);
|
|
3727
|
+
const tolerance = options.tolerance ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
3728
|
+
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
3729
|
+
if (tolerance > 0 && Math.abs(now - timestamp) > tolerance) {
|
|
3730
|
+
throw new WebhookSignatureVerificationError(
|
|
3731
|
+
`Timestamp outside the tolerance window (${tolerance}s). Possible replay \u2014 or check the server clock.`,
|
|
3732
|
+
"timestamp_outside_tolerance",
|
|
3733
|
+
{ timestamp, now, tolerance }
|
|
3734
|
+
);
|
|
3735
|
+
}
|
|
3736
|
+
const received = signatures.map((s) => Buffer.from(s, "hex"));
|
|
3737
|
+
for (const s of secrets) {
|
|
3738
|
+
const expected = Buffer.from(computeWebhookSignature(bytes, timestamp, s), "hex");
|
|
3739
|
+
let matched = false;
|
|
3740
|
+
for (const candidate of received) {
|
|
3741
|
+
if (candidate.length === expected.length && timingSafeEqual(candidate, expected)) {
|
|
3742
|
+
matched = true;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
if (matched) return true;
|
|
3746
|
+
}
|
|
3747
|
+
throw new WebhookSignatureVerificationError(
|
|
3748
|
+
'No signature matches the expected signature for this payload. Make sure you pass the raw request body (not re-serialised JSON) and the full secret including the "whsec_" prefix.',
|
|
3749
|
+
"no_matching_signature"
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
function constructWebhookEvent(rawBody, header, secret, options = {}) {
|
|
3753
|
+
verifyWebhookSignature(rawBody, header, secret, options);
|
|
3754
|
+
let parsed;
|
|
3755
|
+
try {
|
|
3756
|
+
parsed = JSON.parse(Buffer.from(toBytes(rawBody)).toString("utf8"));
|
|
3757
|
+
} catch {
|
|
3758
|
+
throw new WebhookSignatureVerificationError("Webhook payload is not valid JSON.", "invalid_payload");
|
|
3759
|
+
}
|
|
3760
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.id !== "string" || typeof parsed.event !== "string" || typeof parsed.data !== "object") {
|
|
3761
|
+
throw new WebhookSignatureVerificationError(
|
|
3762
|
+
"Webhook payload does not have the expected { id, event, timestamp, data } shape.",
|
|
3763
|
+
"invalid_payload"
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3766
|
+
return parsed;
|
|
3767
|
+
}
|
|
3768
|
+
function normalizeSecrets(secret) {
|
|
3769
|
+
const list = (typeof secret === "string" ? [secret] : [...secret]).filter(
|
|
3770
|
+
(s) => typeof s === "string" && s !== ""
|
|
3771
|
+
);
|
|
3772
|
+
if (list.length === 0) {
|
|
3773
|
+
throw new WebhookSignatureVerificationError(
|
|
3774
|
+
"No webhook signing secret given. Copy it from the Taxora dashboard (Developer \u2192 Webhooks).",
|
|
3775
|
+
"missing_secret"
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
return list;
|
|
3779
|
+
}
|
|
3780
|
+
function toBytes(rawBody) {
|
|
3781
|
+
if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
|
|
3782
|
+
if (rawBody instanceof Uint8Array) return rawBody;
|
|
3783
|
+
if (rawBody instanceof ArrayBuffer) return new Uint8Array(rawBody);
|
|
3784
|
+
throw new WebhookSignatureVerificationError(
|
|
3785
|
+
"Webhook body must be the raw request body (string, Buffer or Uint8Array). It looks like it was already parsed as JSON \u2014 use express.raw() / request.text() instead of a JSON body parser.",
|
|
3786
|
+
"invalid_body"
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3176
3789
|
export {
|
|
3177
3790
|
ApiVersion,
|
|
3178
3791
|
AuthEndpoint,
|
|
@@ -3180,9 +3793,11 @@ export {
|
|
|
3180
3793
|
AuthenticationException,
|
|
3181
3794
|
CompanyAddress,
|
|
3182
3795
|
CompanyEndpoint,
|
|
3796
|
+
ComplianceDocumentType,
|
|
3183
3797
|
ComplianceEnrollment,
|
|
3184
3798
|
ComplianceEnrollmentPage,
|
|
3185
3799
|
ComplianceEnrollmentStatus,
|
|
3800
|
+
ComplianceService,
|
|
3186
3801
|
ComplianceTaxReport,
|
|
3187
3802
|
ComplianceTaxReportPage,
|
|
3188
3803
|
ComplianceTaxReportState,
|
|
@@ -3190,6 +3805,7 @@ export {
|
|
|
3190
3805
|
ComplianceTransactionPage,
|
|
3191
3806
|
ComplianceTransactionState,
|
|
3192
3807
|
ComplianceTransactionType,
|
|
3808
|
+
DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
|
|
3193
3809
|
EReportingEndpoint,
|
|
3194
3810
|
Environment,
|
|
3195
3811
|
FetchHttpClient,
|
|
@@ -3198,8 +3814,12 @@ export {
|
|
|
3198
3814
|
InMemoryTokenStorage,
|
|
3199
3815
|
Language,
|
|
3200
3816
|
LoginIdentifier,
|
|
3817
|
+
NorwegianOrgNumber,
|
|
3818
|
+
PeppolLookupResult,
|
|
3819
|
+
PeppolLookupStatus,
|
|
3201
3820
|
ProviderDocument,
|
|
3202
3821
|
ProviderDocumentLine,
|
|
3822
|
+
RegistryCompany,
|
|
3203
3823
|
RetryPolicy,
|
|
3204
3824
|
RevenueStatistics,
|
|
3205
3825
|
ScoreBreakdown,
|
|
@@ -3224,21 +3844,35 @@ export {
|
|
|
3224
3844
|
VatResource,
|
|
3225
3845
|
VatState,
|
|
3226
3846
|
VatValidationAddressInput,
|
|
3847
|
+
WEBHOOK_ID_HEADER,
|
|
3848
|
+
WEBHOOK_SIGNATURE_HEADER,
|
|
3849
|
+
WebhookSignatureVerificationError,
|
|
3850
|
+
computeWebhookSignature,
|
|
3851
|
+
constructWebhookEvent,
|
|
3227
3852
|
describeApiError,
|
|
3853
|
+
describeComplianceDocumentType,
|
|
3228
3854
|
describeComplianceEnrollmentStatus,
|
|
3855
|
+
describeComplianceService,
|
|
3229
3856
|
describeComplianceTaxReportState,
|
|
3230
3857
|
describeComplianceTransactionState,
|
|
3231
3858
|
describeComplianceTransactionType,
|
|
3859
|
+
describePeppolLookupStatus,
|
|
3232
3860
|
describeSmartEnrichmentMode,
|
|
3233
3861
|
describeSmartEnrichmentStatus,
|
|
3234
3862
|
describeVatState,
|
|
3863
|
+
generateWebhookSignatureHeader,
|
|
3235
3864
|
getBaseUrl,
|
|
3236
3865
|
getFailedVatStates,
|
|
3866
|
+
parseWebhookSignatureHeader,
|
|
3867
|
+
toComplianceDocumentType,
|
|
3237
3868
|
toComplianceEnrollmentStatus,
|
|
3869
|
+
toComplianceService,
|
|
3238
3870
|
toComplianceTaxReportState,
|
|
3239
3871
|
toComplianceTransactionState,
|
|
3240
3872
|
toComplianceTransactionType,
|
|
3873
|
+
toPeppolLookupStatus,
|
|
3241
3874
|
toSmartEnrichmentMode,
|
|
3242
|
-
toSmartEnrichmentStatus
|
|
3875
|
+
toSmartEnrichmentStatus,
|
|
3876
|
+
verifyWebhookSignature
|
|
3243
3877
|
};
|
|
3244
3878
|
//# sourceMappingURL=index.js.map
|