@getpeppr/cli 0.9.0 → 0.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/CHANGELOG.md +37 -0
- package/dist/index.js +472 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -623,7 +623,10 @@ var ICD_CODES = /* @__PURE__ */ new Set([
|
|
|
623
623
|
"0242",
|
|
624
624
|
"0243",
|
|
625
625
|
"0244",
|
|
626
|
-
"0245"
|
|
626
|
+
"0245",
|
|
627
|
+
"0246",
|
|
628
|
+
"0247",
|
|
629
|
+
"0248"
|
|
627
630
|
]);
|
|
628
631
|
var ISO6523_ICD_CODES = Object.freeze({
|
|
629
632
|
has: (v) => ICD_CODES.has(v),
|
|
@@ -717,10 +720,41 @@ function formatVatRate(vatRate, field) {
|
|
|
717
720
|
}
|
|
718
721
|
return String(vatRate);
|
|
719
722
|
}
|
|
720
|
-
function
|
|
723
|
+
function assertValidBaseQuantity(baseQuantity, field) {
|
|
721
724
|
if (typeof baseQuantity !== "number" || !Number.isFinite(baseQuantity) || baseQuantity <= 0) {
|
|
722
725
|
throw new UblBuilderInputError("baseQuantity must be a finite number greater than zero.", field, "PEPPOL-EN16931-R121");
|
|
723
726
|
}
|
|
727
|
+
}
|
|
728
|
+
var ADJUSTMENT_CONTRACT_RULE = "GETPEPPR-ALLOWANCE-CHARGE-AMOUNT";
|
|
729
|
+
var ADJUSTMENT_AMOUNT_MESSAGE = "Allowance and charge amounts must be finite numbers greater than or equal to zero. An allowance reduces the amount and a charge increases it; encode the direction in the field, never in the sign.";
|
|
730
|
+
var DERIVED_AMOUNT_CONTRACT_RULE = "GETPEPPR-DERIVED-AMOUNT";
|
|
731
|
+
var NON_FINITE_DERIVED_AMOUNT_MESSAGE = "Derived amount is not finite (overflow). Reduce quantities, prices or adjustment amounts so every total stays representable.";
|
|
732
|
+
function survivesCents(value) {
|
|
733
|
+
return Number.isFinite(value) && Number.isFinite(value * 100);
|
|
734
|
+
}
|
|
735
|
+
function assertValidAdjustmentAmounts(items, field) {
|
|
736
|
+
if (items === void 0)
|
|
737
|
+
return;
|
|
738
|
+
if (!Array.isArray(items)) {
|
|
739
|
+
throw new UblBuilderInputError(`${field} must be an array \u2014 omit the field instead of sending ${items === null ? "null" : typeof items}`, field, ADJUSTMENT_CONTRACT_RULE);
|
|
740
|
+
}
|
|
741
|
+
for (const [i, item] of items.entries()) {
|
|
742
|
+
const amount = item?.amount;
|
|
743
|
+
if (item === null || typeof item !== "object" || typeof amount !== "number" || !Number.isFinite(amount) || amount < 0) {
|
|
744
|
+
throw new UblBuilderInputError(ADJUSTMENT_AMOUNT_MESSAGE, `${field}[${i}].amount`, ADJUSTMENT_CONTRACT_RULE);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function assertDocumentAdjustmentAmounts(input) {
|
|
749
|
+
for (const [i, line] of input.lines.entries()) {
|
|
750
|
+
assertValidAdjustmentAmounts(line.allowances, `lines[${i}].allowances`);
|
|
751
|
+
assertValidAdjustmentAmounts(line.charges, `lines[${i}].charges`);
|
|
752
|
+
}
|
|
753
|
+
assertValidAdjustmentAmounts(input.allowances, "allowances");
|
|
754
|
+
assertValidAdjustmentAmounts(input.charges, "charges");
|
|
755
|
+
}
|
|
756
|
+
function formatBaseQuantity(baseQuantity, field) {
|
|
757
|
+
assertValidBaseQuantity(baseQuantity, field);
|
|
724
758
|
const numeric = String(baseQuantity);
|
|
725
759
|
const exponentMarker = numeric.search(/[eE]/);
|
|
726
760
|
if (exponentMarker === -1)
|
|
@@ -763,8 +797,8 @@ function buildPartyXml(party, role) {
|
|
|
763
797
|
<cac:Party>
|
|
764
798
|
<cbc:EndpointID schemeID="${escapeXml(endpointScheme)}">${escapeXml(endpointId)}</cbc:EndpointID>
|
|
765
799
|
${// ⛔ DEUX listes, pas une. `BR-CL-25` juge l'EndpointID ci-dessus contre la
|
|
766
|
-
// liste EAS (
|
|
767
|
-
// contre la liste ISO 6523 ICD (
|
|
800
|
+
// liste EAS (104 valeurs, jusqu'à `9959`) ; `BR-CL-10` juge CE champ-ci
|
|
801
|
+
// contre la liste ISO 6523 ICD (243 valeurs, `0002`–`0248`). `9932` (GB),
|
|
768
802
|
// `9935` (IE) et `9930` (DE) sont légaux là-haut et FATALS ici.
|
|
769
803
|
//
|
|
770
804
|
// Le champ (BT-29) est optionnel : l'omettre est la seule écriture
|
|
@@ -952,14 +986,21 @@ function buildLineAllowanceChargeXml(reason, amount, isCharge, currency) {
|
|
|
952
986
|
<cbc:Amount currencyID="${escapeXml(currency)}">${formatAmount(amount)}</cbc:Amount>
|
|
953
987
|
</cac:AllowanceCharge>`;
|
|
954
988
|
}
|
|
955
|
-
function calculateLineExtensionAmount(line) {
|
|
956
|
-
|
|
989
|
+
function calculateLineExtensionAmount(line, lineIndex) {
|
|
990
|
+
if (line.baseQuantity !== void 0) {
|
|
991
|
+
assertValidBaseQuantity(line.baseQuantity, `lines[${lineIndex}].baseQuantity`);
|
|
992
|
+
}
|
|
993
|
+
const base = line.quantity * line.unitPrice / (line.baseQuantity ?? 1);
|
|
957
994
|
const lineAllowances = (line.allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
958
995
|
const lineCharges = (line.charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
959
|
-
|
|
996
|
+
const total = base - lineAllowances + lineCharges;
|
|
997
|
+
if (!survivesCents(total)) {
|
|
998
|
+
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, `lines[${lineIndex}]`, DERIVED_AMOUNT_CONTRACT_RULE);
|
|
999
|
+
}
|
|
1000
|
+
return total;
|
|
960
1001
|
}
|
|
961
1002
|
function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
|
|
962
|
-
const lineTotal = calculateLineExtensionAmount(line);
|
|
1003
|
+
const lineTotal = calculateLineExtensionAmount(line, index);
|
|
963
1004
|
const unit = resolveUnitCode(line.unit ?? DEFAULT_UNIT);
|
|
964
1005
|
const vatCategory = line.vatCategory ?? "S";
|
|
965
1006
|
const lineAllowancesXml = (line.allowances ?? []).map((a) => buildLineAllowanceChargeXml(a.reason, a.amount, false, currency)).join("");
|
|
@@ -1035,7 +1076,7 @@ function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
|
|
|
1035
1076
|
}
|
|
1036
1077
|
}
|
|
1037
1078
|
for (const [index, line] of lines.entries()) {
|
|
1038
|
-
addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line), line.taxExemptReason, `lines[${index}]`);
|
|
1079
|
+
addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line, index), line.taxExemptReason, `lines[${index}]`);
|
|
1039
1080
|
}
|
|
1040
1081
|
for (const [index, a] of (allowances ?? []).entries()) {
|
|
1041
1082
|
addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount, a.taxExemptReason, `allowances[${index}]`);
|
|
@@ -1050,19 +1091,29 @@ function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
|
|
|
1050
1091
|
}
|
|
1051
1092
|
}
|
|
1052
1093
|
}
|
|
1053
|
-
return Array.from(groups.values()).map((subtotal) =>
|
|
1054
|
-
|
|
1055
|
-
taxAmount
|
|
1056
|
-
|
|
1094
|
+
return Array.from(groups.values()).map((subtotal) => {
|
|
1095
|
+
const taxableAmount = round2(subtotal.taxableAmount);
|
|
1096
|
+
const taxAmount = round2(taxableAmount * (subtotal.vatRate / 100));
|
|
1097
|
+
if (!survivesCents(taxableAmount) || !survivesCents(taxAmount)) {
|
|
1098
|
+
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, "totals", DERIVED_AMOUNT_CONTRACT_RULE);
|
|
1099
|
+
}
|
|
1100
|
+
return { ...subtotal, taxableAmount, taxAmount };
|
|
1101
|
+
});
|
|
1057
1102
|
}
|
|
1058
1103
|
function calculateDocumentTotals(lines, allowances, charges, options = {}) {
|
|
1059
1104
|
const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges, options);
|
|
1060
|
-
const lineExtensionAmount = lines.reduce((sum, line) => sum + calculateLineExtensionAmount(line), 0);
|
|
1105
|
+
const lineExtensionAmount = lines.reduce((sum, line, index) => sum + calculateLineExtensionAmount(line, index), 0);
|
|
1061
1106
|
const allowanceTotalAmount = (allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
1062
1107
|
const chargeTotalAmount = (charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
1063
1108
|
const taxExclusiveAmount = round2(lineExtensionAmount - allowanceTotalAmount + chargeTotalAmount);
|
|
1109
|
+
if (!survivesCents(allowanceTotalAmount) || !survivesCents(chargeTotalAmount) || !survivesCents(taxExclusiveAmount)) {
|
|
1110
|
+
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, "totals", DERIVED_AMOUNT_CONTRACT_RULE);
|
|
1111
|
+
}
|
|
1064
1112
|
const totalTax = round2(taxSubtotals.reduce((sum, st) => sum + st.taxAmount, 0));
|
|
1065
1113
|
const taxInclusiveAmount = round2(taxExclusiveAmount + totalTax);
|
|
1114
|
+
if (!survivesCents(totalTax) || !survivesCents(taxInclusiveAmount)) {
|
|
1115
|
+
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, "totals", DERIVED_AMOUNT_CONTRACT_RULE);
|
|
1116
|
+
}
|
|
1066
1117
|
return {
|
|
1067
1118
|
lineExtensionAmount,
|
|
1068
1119
|
allowanceTotalAmount,
|
|
@@ -1147,6 +1198,7 @@ function buildOrderReferenceXml(orderReference, salesOrderReference) {
|
|
|
1147
1198
|
return parts.join("");
|
|
1148
1199
|
}
|
|
1149
1200
|
function buildInvoiceXml(input) {
|
|
1201
|
+
assertDocumentAdjustmentAmounts(input);
|
|
1150
1202
|
const currency = input.currency ?? "EUR";
|
|
1151
1203
|
const date = formatDate(input.date);
|
|
1152
1204
|
const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
|
|
@@ -1194,6 +1246,7 @@ function buildInvoiceXml(input) {
|
|
|
1194
1246
|
</Invoice>`;
|
|
1195
1247
|
}
|
|
1196
1248
|
function buildCreditNoteXml(input) {
|
|
1249
|
+
assertDocumentAdjustmentAmounts(input);
|
|
1197
1250
|
const currency = input.currency ?? "EUR";
|
|
1198
1251
|
const date = formatDate(input.date);
|
|
1199
1252
|
const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
|
|
@@ -1437,8 +1490,13 @@ var CURRENCIES = /* @__PURE__ */ new Map([
|
|
|
1437
1490
|
["CZK", { code: "CZK", name: "Czech Koruna", minorUnits: 2 }],
|
|
1438
1491
|
["HUF", { code: "HUF", name: "Hungarian Forint", minorUnits: 2 }],
|
|
1439
1492
|
["RON", { code: "RON", name: "Romanian Leu", minorUnits: 2 }],
|
|
1440
|
-
|
|
1441
|
-
|
|
1493
|
+
// ⛔ BGN and HRK are GONE (GPR-1205). Neither is in BR-CL-04/BR-CL-05 of the
|
|
1494
|
+
// graved 3.0.21 rulebooks — Bulgaria and Croatia both joined the euro — so
|
|
1495
|
+
// accepting them here would wave through a document the network refuses, which
|
|
1496
|
+
// is the exact failure this release closes. XCG replaces ANG (never carried
|
|
1497
|
+
// here) for the Dutch Caribbean. `currency-codes-network-parity.test.ts` reds
|
|
1498
|
+
// if this table ever readmits a code the rulebooks reject.
|
|
1499
|
+
["XCG", { code: "XCG", name: "Caribbean Guilder", minorUnits: 2 }],
|
|
1442
1500
|
["ISK", { code: "ISK", name: "Icelandic Krona", minorUnits: 0 }],
|
|
1443
1501
|
["TRY", { code: "TRY", name: "Turkish Lira", minorUnits: 2 }],
|
|
1444
1502
|
["JPY", { code: "JPY", name: "Japanese Yen", minorUnits: 0 }],
|
|
@@ -1564,6 +1622,11 @@ function assertString(value, fieldPath, errors) {
|
|
|
1564
1622
|
return true;
|
|
1565
1623
|
}
|
|
1566
1624
|
var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
1625
|
+
var ALLOWANCE_CHARGE_CONTRACT_RULE = "GETPEPPR-ALLOWANCE-CHARGE-AMOUNT";
|
|
1626
|
+
var DERIVED_AMOUNT_CONTRACT_RULE2 = "GETPEPPR-DERIVED-AMOUNT";
|
|
1627
|
+
function survivesCents2(value) {
|
|
1628
|
+
return Number.isFinite(value) && Number.isFinite(value * 100);
|
|
1629
|
+
}
|
|
1567
1630
|
function validateParty(party, path) {
|
|
1568
1631
|
const errors = [];
|
|
1569
1632
|
if (party.name === void 0 || party.name === null || party.name === "") {
|
|
@@ -1637,6 +1700,31 @@ function validateLine(line, index, isCreditNote = false) {
|
|
|
1637
1700
|
} else if (line.vatRate < 0 || line.vatRate > 100) {
|
|
1638
1701
|
errors.push(error(`${path}.vatRate`, `VAT rate must be between 0 and 100, got ${line.vatRate}`, void 0, "Use 0 for zero-rated, 21 for standard Belgian VAT, etc."));
|
|
1639
1702
|
}
|
|
1703
|
+
const adjustmentItems = {
|
|
1704
|
+
allowances: Array.isArray(line.allowances) ? line.allowances : [],
|
|
1705
|
+
charges: Array.isArray(line.charges) ? line.charges : []
|
|
1706
|
+
};
|
|
1707
|
+
for (const kind of ["allowances", "charges"]) {
|
|
1708
|
+
const items = line[kind];
|
|
1709
|
+
if (items === void 0)
|
|
1710
|
+
continue;
|
|
1711
|
+
if (!Array.isArray(items)) {
|
|
1712
|
+
errors.push(error(`${path}.${kind}`, `${kind} must be an array \u2014 omit the field instead of sending ${items === null ? "null" : typeof items}`, ALLOWANCE_CHARGE_CONTRACT_RULE));
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
for (const [i, item] of items.entries()) {
|
|
1716
|
+
const amount = item?.amount;
|
|
1717
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || amount < 0) {
|
|
1718
|
+
errors.push(error(`${path}.${kind}[${i}].amount`, `Allowance and charge amounts must be zero or positive finite numbers, got ${String(amount)}`, ALLOWANCE_CHARGE_CONTRACT_RULE, "An allowance reduces the amount and a charge increases it \u2014 encode the direction in the field, never in the sign."));
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const bq = typeof line.baseQuantity === "number" && line.baseQuantity > 0 ? line.baseQuantity : 1;
|
|
1723
|
+
const derivedNet = (line.quantity ?? 0) * (line.unitPrice ?? 0) / bq - adjustmentItems.allowances.reduce((sum, a) => sum + (a?.amount ?? 0), 0) + adjustmentItems.charges.reduce((sum, c) => sum + (c?.amount ?? 0), 0);
|
|
1724
|
+
const derivedVat = derivedNet * ((typeof line.vatRate === "number" ? line.vatRate : 0) / 100);
|
|
1725
|
+
if (!survivesCents2(derivedNet) || !survivesCents2(derivedVat)) {
|
|
1726
|
+
errors.push(error(path, "Derived amount is not finite (overflow). Reduce quantities, prices or adjustment amounts so every total stays representable.", DERIVED_AMOUNT_CONTRACT_RULE2));
|
|
1727
|
+
}
|
|
1640
1728
|
return errors;
|
|
1641
1729
|
}
|
|
1642
1730
|
function validateInvoice(input) {
|
|
@@ -1713,15 +1801,52 @@ function validateInvoice(input) {
|
|
|
1713
1801
|
if (value === void 0)
|
|
1714
1802
|
continue;
|
|
1715
1803
|
if (!Array.isArray(value)) {
|
|
1716
|
-
errors.push(error(field, `${field} must be an array`,
|
|
1804
|
+
errors.push(error(field, `${field} must be an array \u2014 omit the field instead of sending ${value === null ? "null" : typeof value}`, ALLOWANCE_CHARGE_CONTRACT_RULE));
|
|
1717
1805
|
continue;
|
|
1718
1806
|
}
|
|
1719
1807
|
for (const [index, item] of value.entries()) {
|
|
1720
1808
|
if (item === null || typeof item !== "object") {
|
|
1721
1809
|
errors.push(error(`${field}[${index}]`, `${field}[${index}] must be an object`, void 0));
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
const amount = item.amount;
|
|
1813
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || amount < 0) {
|
|
1814
|
+
errors.push(error(`${field}[${index}].amount`, `Allowance and charge amounts must be zero or positive finite numbers, got ${String(amount)}`, ALLOWANCE_CHARGE_CONTRACT_RULE, "An allowance reduces the amount and a charge increases it \u2014 encode the direction in the field, never in the sign."));
|
|
1722
1815
|
}
|
|
1723
1816
|
}
|
|
1724
1817
|
}
|
|
1818
|
+
if (Array.isArray(input.lines)) {
|
|
1819
|
+
const itemsOf = (value) => Array.isArray(value) ? value : [];
|
|
1820
|
+
const amountOf = (item) => typeof item?.amount === "number" ? item.amount : 0;
|
|
1821
|
+
const lineNets = input.lines.reduce((sum, line) => {
|
|
1822
|
+
if (typeof line !== "object" || line === null)
|
|
1823
|
+
return sum;
|
|
1824
|
+
const lbq = typeof line.baseQuantity === "number" && line.baseQuantity > 0 ? line.baseQuantity : 1;
|
|
1825
|
+
return sum + (line.quantity ?? 0) * (line.unitPrice ?? 0) / lbq - itemsOf(line.allowances).reduce((s, a) => s + amountOf(a), 0) + itemsOf(line.charges).reduce((s, c) => s + amountOf(c), 0);
|
|
1826
|
+
}, 0);
|
|
1827
|
+
const allowanceTotal = itemsOf(input.allowances).reduce((s, a) => s + amountOf(a), 0);
|
|
1828
|
+
const chargeTotal = itemsOf(input.charges).reduce((s, c) => s + amountOf(c), 0);
|
|
1829
|
+
const vatOf = (items, sign) => {
|
|
1830
|
+
if (!Array.isArray(items))
|
|
1831
|
+
return 0;
|
|
1832
|
+
return items.reduce((s, it) => {
|
|
1833
|
+
const rec = it;
|
|
1834
|
+
const amount = typeof rec?.amount === "number" ? rec.amount : 0;
|
|
1835
|
+
const rate = typeof rec?.vatRate === "number" ? rec.vatRate : 0;
|
|
1836
|
+
return s + sign * amount * (rate / 100);
|
|
1837
|
+
}, 0);
|
|
1838
|
+
};
|
|
1839
|
+
const docVat = vatOf(input.allowances, -1) + vatOf(input.charges, 1) + input.lines.reduce((s, line) => {
|
|
1840
|
+
if (typeof line !== "object" || line === null)
|
|
1841
|
+
return s;
|
|
1842
|
+
const lbq = typeof line.baseQuantity === "number" && line.baseQuantity > 0 ? line.baseQuantity : 1;
|
|
1843
|
+
const net = (line.quantity ?? 0) * (line.unitPrice ?? 0) / lbq;
|
|
1844
|
+
return s + net * ((typeof line.vatRate === "number" ? line.vatRate : 0) / 100);
|
|
1845
|
+
}, 0);
|
|
1846
|
+
if (!survivesCents2(lineNets - allowanceTotal + chargeTotal) || !survivesCents2(docVat)) {
|
|
1847
|
+
errors.push(error("totals", "Derived amount is not finite (overflow). Reduce quantities, prices or adjustment amounts so every total stays representable.", DERIVED_AMOUNT_CONTRACT_RULE2));
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1725
1850
|
if (input.date) {
|
|
1726
1851
|
if (!ISO_DATE_RE.test(input.date)) {
|
|
1727
1852
|
errors.push(error("date", `Invalid date format: "${input.date}"`, void 0, "Use ISO 8601: YYYY-MM-DD"));
|
|
@@ -2331,18 +2456,171 @@ function statusFamily(status) {
|
|
|
2331
2456
|
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
2332
2457
|
|
|
2333
2458
|
// ../sdk/dist/version.js
|
|
2334
|
-
var SDK_VERSION = "
|
|
2459
|
+
var SDK_VERSION = "5.0.0";
|
|
2460
|
+
|
|
2461
|
+
// ../sdk/dist/core/api-result.js
|
|
2462
|
+
var API_RESULT_HEADER_NAMES = {
|
|
2463
|
+
requestId: "Getpeppr-Request-Id",
|
|
2464
|
+
resultCode: "Getpeppr-Result-Code",
|
|
2465
|
+
resultMessage: "Getpeppr-Result-Message",
|
|
2466
|
+
retryable: "Getpeppr-Retryable",
|
|
2467
|
+
remediation: "Getpeppr-Remediation",
|
|
2468
|
+
docs: "Getpeppr-Result-Docs"
|
|
2469
|
+
};
|
|
2470
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
2471
|
+
function stripControls(value) {
|
|
2472
|
+
return value.replace(CONTROL_CHARACTERS, " ");
|
|
2473
|
+
}
|
|
2474
|
+
function safeDocsUrl(value) {
|
|
2475
|
+
if (typeof value !== "string")
|
|
2476
|
+
return null;
|
|
2477
|
+
let parsed;
|
|
2478
|
+
try {
|
|
2479
|
+
parsed = new URL(value);
|
|
2480
|
+
} catch {
|
|
2481
|
+
return null;
|
|
2482
|
+
}
|
|
2483
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
2484
|
+
return null;
|
|
2485
|
+
return parsed.href;
|
|
2486
|
+
}
|
|
2487
|
+
var MAX_BYTES = {
|
|
2488
|
+
requestId: 256,
|
|
2489
|
+
code: 256,
|
|
2490
|
+
message: 1024,
|
|
2491
|
+
remediation: 64,
|
|
2492
|
+
docs: 2048,
|
|
2493
|
+
// ⚠️ A COST bound, not a correctness one — and the distinction is measured.
|
|
2494
|
+
// When this field was still trimmed, a huge padded value could normalise into
|
|
2495
|
+
// `true`, so the ceiling changed the verdict. Now that the token is compared
|
|
2496
|
+
// verbatim, no string can be both over-long and equal to "true"/"false": a
|
|
2497
|
+
// mutation removing this ceiling SURVIVES the suite, and correctly so. It
|
|
2498
|
+
// stays to bound the whitespace/control scan on an absurd value, and it is
|
|
2499
|
+
// the one entry here with no test — deliberately, since any assertion would
|
|
2500
|
+
// be satisfied by both answers.
|
|
2501
|
+
retryable: 32
|
|
2502
|
+
};
|
|
2503
|
+
var STRICT_HTTPS_URL = /^https:\/\/[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?(?::\d{1,5})?(?:\/[^\s\\]*)?$/i;
|
|
2504
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
2505
|
+
function withinBudget(raw, maxBytes) {
|
|
2506
|
+
if (raw === null)
|
|
2507
|
+
return void 0;
|
|
2508
|
+
return TEXT_ENCODER.encode(raw).length > maxBytes ? void 0 : raw;
|
|
2509
|
+
}
|
|
2510
|
+
function readSentence(raw, maxBytes) {
|
|
2511
|
+
const bounded = withinBudget(raw, maxBytes);
|
|
2512
|
+
if (bounded === void 0)
|
|
2513
|
+
return void 0;
|
|
2514
|
+
const cleaned = stripControls(bounded).trim();
|
|
2515
|
+
return cleaned === "" ? void 0 : cleaned;
|
|
2516
|
+
}
|
|
2517
|
+
function readMachineToken(raw, maxBytes) {
|
|
2518
|
+
const bounded = withinBudget(raw, maxBytes);
|
|
2519
|
+
if (bounded === void 0)
|
|
2520
|
+
return void 0;
|
|
2521
|
+
if (bounded === "")
|
|
2522
|
+
return void 0;
|
|
2523
|
+
if (new RegExp(CONTROL_CHARACTERS.source).test(bounded))
|
|
2524
|
+
return void 0;
|
|
2525
|
+
if (/\s/u.test(bounded))
|
|
2526
|
+
return void 0;
|
|
2527
|
+
return bounded;
|
|
2528
|
+
}
|
|
2529
|
+
function readBoolean(raw) {
|
|
2530
|
+
const token = readMachineToken(raw, MAX_BYTES.retryable);
|
|
2531
|
+
if (token === void 0)
|
|
2532
|
+
return void 0;
|
|
2533
|
+
if (token === "true")
|
|
2534
|
+
return true;
|
|
2535
|
+
if (token === "false")
|
|
2536
|
+
return false;
|
|
2537
|
+
return void 0;
|
|
2538
|
+
}
|
|
2539
|
+
function readDocsUrl(raw) {
|
|
2540
|
+
const token = readMachineToken(raw, MAX_BYTES.docs);
|
|
2541
|
+
if (token === void 0)
|
|
2542
|
+
return void 0;
|
|
2543
|
+
if (!STRICT_HTTPS_URL.test(token))
|
|
2544
|
+
return void 0;
|
|
2545
|
+
const parsed = safeDocsUrl(token);
|
|
2546
|
+
if (parsed === null)
|
|
2547
|
+
return void 0;
|
|
2548
|
+
const url = new URL(parsed);
|
|
2549
|
+
if (url.protocol !== "https:")
|
|
2550
|
+
return void 0;
|
|
2551
|
+
if (url.username !== "" || url.password !== "")
|
|
2552
|
+
return void 0;
|
|
2553
|
+
return url.href;
|
|
2554
|
+
}
|
|
2555
|
+
function parseApiResultHeaders(headers) {
|
|
2556
|
+
const requestId = readMachineToken(headers.get(API_RESULT_HEADER_NAMES.requestId), MAX_BYTES.requestId);
|
|
2557
|
+
const code = readMachineToken(headers.get(API_RESULT_HEADER_NAMES.resultCode), MAX_BYTES.code);
|
|
2558
|
+
const message = readSentence(headers.get(API_RESULT_HEADER_NAMES.resultMessage), MAX_BYTES.message);
|
|
2559
|
+
const retryable = readBoolean(headers.get(API_RESULT_HEADER_NAMES.retryable));
|
|
2560
|
+
const remediation = readMachineToken(headers.get(API_RESULT_HEADER_NAMES.remediation), MAX_BYTES.remediation);
|
|
2561
|
+
const docs = readDocsUrl(headers.get(API_RESULT_HEADER_NAMES.docs));
|
|
2562
|
+
const result = {};
|
|
2563
|
+
if (requestId !== void 0)
|
|
2564
|
+
result.requestId = requestId;
|
|
2565
|
+
if (code !== void 0)
|
|
2566
|
+
result.code = code;
|
|
2567
|
+
if (message !== void 0)
|
|
2568
|
+
result.message = message;
|
|
2569
|
+
if (retryable !== void 0)
|
|
2570
|
+
result.retryable = retryable;
|
|
2571
|
+
if (remediation !== void 0)
|
|
2572
|
+
result.remediation = remediation;
|
|
2573
|
+
if (docs !== void 0)
|
|
2574
|
+
result.docs = docs;
|
|
2575
|
+
return Object.keys(result).length === 0 ? void 0 : result;
|
|
2576
|
+
}
|
|
2335
2577
|
|
|
2336
2578
|
// ../sdk/dist/core/client.js
|
|
2337
|
-
function
|
|
2579
|
+
function normalizeHeaderValue(value) {
|
|
2580
|
+
return value.replace(/^[\t\n\r ]+|[\t\n\r ]+$/g, "");
|
|
2581
|
+
}
|
|
2582
|
+
function isCarriableHeaderValue(value) {
|
|
2583
|
+
for (let i = 0; i < value.length; i++) {
|
|
2584
|
+
const code = value.charCodeAt(i);
|
|
2585
|
+
const carriable = code === 9 || code >= 32 && code <= 126 || code >= 128 && code <= 255;
|
|
2586
|
+
if (!carriable)
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2589
|
+
return true;
|
|
2590
|
+
}
|
|
2591
|
+
function idempotencyKeyRefusal(message) {
|
|
2592
|
+
return new PeppolValidationError(`Invalid idempotency key: ${message}`, {
|
|
2593
|
+
valid: false,
|
|
2594
|
+
errors: [{ field: "idempotencyKey", message }],
|
|
2595
|
+
warnings: []
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
function applyIdempotencyKey(headers, options) {
|
|
2599
|
+
const key = options?.idempotencyKey;
|
|
2600
|
+
if (key === void 0 || key === null)
|
|
2601
|
+
return;
|
|
2602
|
+
if (typeof key !== "string") {
|
|
2603
|
+
throw idempotencyKeyRefusal(`expected a string, received ${Array.isArray(key) ? "an array" : `a ${typeof key}`}.`);
|
|
2604
|
+
}
|
|
2605
|
+
const normalized = normalizeHeaderValue(key);
|
|
2606
|
+
if (normalized === "") {
|
|
2607
|
+
throw idempotencyKeyRefusal("the key is blank once HTTP whitespace is stripped, so it would reach the gateway empty and protect nothing. Pass a non-blank key, or omit the option.");
|
|
2608
|
+
}
|
|
2609
|
+
if (!isCarriableHeaderValue(normalized)) {
|
|
2610
|
+
throw idempotencyKeyRefusal("the key contains a character no HTTP header can carry. A header value may hold only HTAB, space, U+0021-U+007E and U+0080-U+00FF (RFC 9110 field-value) \u2014 so every control character other than the tab, plus DEL and anything above U+00FF, is refused by the transport itself.");
|
|
2611
|
+
}
|
|
2612
|
+
headers["Idempotency-Key"] = normalized;
|
|
2613
|
+
}
|
|
2614
|
+
function carriesUsableIdempotencyKey(headers) {
|
|
2338
2615
|
if (!headers)
|
|
2339
|
-
return
|
|
2340
|
-
const
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2616
|
+
return false;
|
|
2617
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
2618
|
+
if (name.toLowerCase() !== "idempotency-key")
|
|
2619
|
+
continue;
|
|
2620
|
+
if (typeof value === "string" && normalizeHeaderValue(value) !== "")
|
|
2621
|
+
return true;
|
|
2344
2622
|
}
|
|
2345
|
-
return
|
|
2623
|
+
return false;
|
|
2346
2624
|
}
|
|
2347
2625
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2348
2626
|
function sleep(ms) {
|
|
@@ -2369,33 +2647,16 @@ function parseRetryAfter(headerValue) {
|
|
|
2369
2647
|
}
|
|
2370
2648
|
return void 0;
|
|
2371
2649
|
}
|
|
2372
|
-
var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
2373
|
-
function stripControls(value) {
|
|
2374
|
-
return value.replace(CONTROL_CHARACTERS, " ");
|
|
2375
|
-
}
|
|
2376
2650
|
function readOwn(source, key) {
|
|
2377
2651
|
return Object.hasOwn(source, key) ? source[key] : void 0;
|
|
2378
2652
|
}
|
|
2379
|
-
function
|
|
2653
|
+
function readSentence2(source, key) {
|
|
2380
2654
|
const value = readOwn(source, key);
|
|
2381
2655
|
if (typeof value !== "string")
|
|
2382
2656
|
return null;
|
|
2383
2657
|
const cleaned = stripControls(value).trim();
|
|
2384
2658
|
return cleaned === "" ? null : cleaned;
|
|
2385
2659
|
}
|
|
2386
|
-
function safeDocsUrl(value) {
|
|
2387
|
-
if (typeof value !== "string")
|
|
2388
|
-
return null;
|
|
2389
|
-
let parsed;
|
|
2390
|
-
try {
|
|
2391
|
-
parsed = new URL(value);
|
|
2392
|
-
} catch {
|
|
2393
|
-
return null;
|
|
2394
|
-
}
|
|
2395
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
2396
|
-
return null;
|
|
2397
|
-
return parsed.href;
|
|
2398
|
-
}
|
|
2399
2660
|
function formatApiErrorMessage(status, rawBody) {
|
|
2400
2661
|
const verbatim = `getpeppr API error (${status}): ${stripControls(rawBody)}`;
|
|
2401
2662
|
let parsed;
|
|
@@ -2407,7 +2668,7 @@ function formatApiErrorMessage(status, rawBody) {
|
|
|
2407
2668
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2408
2669
|
return verbatim;
|
|
2409
2670
|
}
|
|
2410
|
-
const sentence =
|
|
2671
|
+
const sentence = readSentence2(parsed, "message") ?? readSentence2(parsed, "error");
|
|
2411
2672
|
if (sentence === null)
|
|
2412
2673
|
return verbatim;
|
|
2413
2674
|
const link = safeDocsUrl(readOwn(parsed, "docs"));
|
|
@@ -2415,6 +2676,8 @@ function formatApiErrorMessage(status, rawBody) {
|
|
|
2415
2676
|
}
|
|
2416
2677
|
function isRetryableError(error2) {
|
|
2417
2678
|
if (error2 instanceof PeppolApiError) {
|
|
2679
|
+
if (error2.retryable !== void 0)
|
|
2680
|
+
return error2.retryable;
|
|
2418
2681
|
return RETRYABLE_STATUS_CODES.has(error2.statusCode);
|
|
2419
2682
|
}
|
|
2420
2683
|
if (error2 instanceof Error && error2.name === "AbortError") {
|
|
@@ -2456,7 +2719,7 @@ var GetpepprAdapter = class {
|
|
|
2456
2719
|
lastError = err;
|
|
2457
2720
|
const is429 = err instanceof PeppolApiError && err.statusCode === 429;
|
|
2458
2721
|
const isSafeMethod = /^(GET|DELETE|HEAD)$/i.test(method);
|
|
2459
|
-
const hasIdempotencyKey =
|
|
2722
|
+
const hasIdempotencyKey = carriesUsableIdempotencyKey(extraHeaders);
|
|
2460
2723
|
const canRetry = is429 || isSafeMethod || hasIdempotencyKey;
|
|
2461
2724
|
if (attempt < maxRetries && canRetry && isRetryableError(err)) {
|
|
2462
2725
|
const retryAfterMs = err instanceof PeppolApiError ? err.retryAfterMs : void 0;
|
|
@@ -2499,6 +2762,7 @@ var GetpepprAdapter = class {
|
|
|
2499
2762
|
body: body ? JSON.stringify(body) : void 0,
|
|
2500
2763
|
signal: controller.signal
|
|
2501
2764
|
});
|
|
2765
|
+
const result = parseApiResultHeaders(response.headers);
|
|
2502
2766
|
if (!response.ok) {
|
|
2503
2767
|
const errorBody = await response.text().catch(() => "Unknown error");
|
|
2504
2768
|
const retryAfterMs = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
|
|
@@ -2509,12 +2773,13 @@ var GetpepprAdapter = class {
|
|
|
2509
2773
|
headers: Object.fromEntries(response.headers.entries()),
|
|
2510
2774
|
body: errorBody,
|
|
2511
2775
|
durationMs: Date.now() - startTime,
|
|
2512
|
-
timestamp: Date.now()
|
|
2776
|
+
timestamp: Date.now(),
|
|
2777
|
+
result: cloneResultForHook(result)
|
|
2513
2778
|
});
|
|
2514
2779
|
} catch {
|
|
2515
2780
|
}
|
|
2516
2781
|
}
|
|
2517
|
-
throw new PeppolApiError(formatApiErrorMessage(response.status, errorBody), response.status, errorBody, retryAfterMs);
|
|
2782
|
+
throw new PeppolApiError(formatApiErrorMessage(response.status, errorBody), response.status, errorBody, retryAfterMs, result);
|
|
2518
2783
|
}
|
|
2519
2784
|
if (response.status === 204) {
|
|
2520
2785
|
if (this.onResponse) {
|
|
@@ -2524,7 +2789,8 @@ var GetpepprAdapter = class {
|
|
|
2524
2789
|
headers: Object.fromEntries(response.headers.entries()),
|
|
2525
2790
|
body: void 0,
|
|
2526
2791
|
durationMs: Date.now() - startTime,
|
|
2527
|
-
timestamp: Date.now()
|
|
2792
|
+
timestamp: Date.now(),
|
|
2793
|
+
result: cloneResultForHook(result)
|
|
2528
2794
|
});
|
|
2529
2795
|
} catch {
|
|
2530
2796
|
}
|
|
@@ -2535,7 +2801,20 @@ var GetpepprAdapter = class {
|
|
|
2535
2801
|
try {
|
|
2536
2802
|
responseBody = await response.json();
|
|
2537
2803
|
} catch {
|
|
2538
|
-
|
|
2804
|
+
if (this.onResponse) {
|
|
2805
|
+
try {
|
|
2806
|
+
this.onResponse({
|
|
2807
|
+
status: response.status,
|
|
2808
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
2809
|
+
body: void 0,
|
|
2810
|
+
durationMs: Date.now() - startTime,
|
|
2811
|
+
timestamp: Date.now(),
|
|
2812
|
+
result: cloneResultForHook(result)
|
|
2813
|
+
});
|
|
2814
|
+
} catch {
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
throw new PeppolApiError(`getpeppr API error: unexpected response format (status ${response.status})`, response.status, "Response body is not valid JSON", void 0, result);
|
|
2539
2818
|
}
|
|
2540
2819
|
if (this.onResponse) {
|
|
2541
2820
|
try {
|
|
@@ -2554,7 +2833,8 @@ var GetpepprAdapter = class {
|
|
|
2554
2833
|
// swallows everything — so the hook fires with a marker instead.
|
|
2555
2834
|
body: cloneForHook(responseBody),
|
|
2556
2835
|
durationMs: Date.now() - startTime,
|
|
2557
|
-
timestamp: Date.now()
|
|
2836
|
+
timestamp: Date.now(),
|
|
2837
|
+
result: cloneResultForHook(result)
|
|
2558
2838
|
});
|
|
2559
2839
|
} catch {
|
|
2560
2840
|
}
|
|
@@ -2568,9 +2848,7 @@ var GetpepprAdapter = class {
|
|
|
2568
2848
|
// legacy `_draft` marker, which the current gateway rejects explicitly.
|
|
2569
2849
|
async sendInvoice(input, options) {
|
|
2570
2850
|
const headers = {};
|
|
2571
|
-
|
|
2572
|
-
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
2573
|
-
}
|
|
2851
|
+
applyIdempotencyKey(headers, options);
|
|
2574
2852
|
if (options?.validateRecipient) {
|
|
2575
2853
|
headers["X-Validate-Recipient"] = options.validateRecipient === true ? "warn" : String(options.validateRecipient);
|
|
2576
2854
|
}
|
|
@@ -2579,17 +2857,17 @@ var GetpepprAdapter = class {
|
|
|
2579
2857
|
}
|
|
2580
2858
|
async createInvoice(input, options) {
|
|
2581
2859
|
const headers = {};
|
|
2582
|
-
|
|
2583
|
-
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
2584
|
-
}
|
|
2860
|
+
applyIdempotencyKey(headers, options);
|
|
2585
2861
|
if (options?.validateRecipient) {
|
|
2586
2862
|
headers["X-Validate-Recipient"] = options.validateRecipient === true ? "warn" : String(options.validateRecipient);
|
|
2587
2863
|
}
|
|
2588
2864
|
const result = await this.request("POST", "/invoices", { ...input, _draft: true }, headers);
|
|
2589
2865
|
return parseSendResult(result);
|
|
2590
2866
|
}
|
|
2591
|
-
async sendInvoiceById(id) {
|
|
2592
|
-
|
|
2867
|
+
async sendInvoiceById(id, options) {
|
|
2868
|
+
const headers = {};
|
|
2869
|
+
applyIdempotencyKey(headers, options);
|
|
2870
|
+
await this.request("POST", `/invoices/send/${id}`, void 0, headers);
|
|
2593
2871
|
}
|
|
2594
2872
|
async sendCreditNote(input) {
|
|
2595
2873
|
const result = await this.request("POST", "/credit-notes", input);
|
|
@@ -2695,6 +2973,7 @@ var GetpepprAdapter = class {
|
|
|
2695
2973
|
headers: requestHeaders,
|
|
2696
2974
|
signal: controller.signal
|
|
2697
2975
|
});
|
|
2976
|
+
const result = parseApiResultHeaders(response.headers);
|
|
2698
2977
|
if (!response.ok) {
|
|
2699
2978
|
const errorBody = await response.text().catch(() => "Unknown error");
|
|
2700
2979
|
const retryAfterMs = response.status === 429 ? parseRetryAfter(response.headers.get("Retry-After")) : void 0;
|
|
@@ -2705,12 +2984,13 @@ var GetpepprAdapter = class {
|
|
|
2705
2984
|
headers: Object.fromEntries(response.headers.entries()),
|
|
2706
2985
|
body: errorBody,
|
|
2707
2986
|
durationMs: Date.now() - startTime,
|
|
2708
|
-
timestamp: Date.now()
|
|
2987
|
+
timestamp: Date.now(),
|
|
2988
|
+
result: cloneResultForHook(result)
|
|
2709
2989
|
});
|
|
2710
2990
|
} catch {
|
|
2711
2991
|
}
|
|
2712
2992
|
}
|
|
2713
|
-
throw new PeppolApiError(formatApiErrorMessage(response.status, errorBody), response.status, errorBody, retryAfterMs);
|
|
2993
|
+
throw new PeppolApiError(formatApiErrorMessage(response.status, errorBody), response.status, errorBody, retryAfterMs, result);
|
|
2714
2994
|
}
|
|
2715
2995
|
const responseBody = await response.arrayBuffer();
|
|
2716
2996
|
if (this.onResponse) {
|
|
@@ -2720,7 +3000,8 @@ var GetpepprAdapter = class {
|
|
|
2720
3000
|
headers: Object.fromEntries(response.headers.entries()),
|
|
2721
3001
|
body: `[ArrayBuffer: ${responseBody.byteLength} bytes]`,
|
|
2722
3002
|
durationMs: Date.now() - startTime,
|
|
2723
|
-
timestamp: Date.now()
|
|
3003
|
+
timestamp: Date.now(),
|
|
3004
|
+
result: cloneResultForHook(result)
|
|
2724
3005
|
});
|
|
2725
3006
|
} catch {
|
|
2726
3007
|
}
|
|
@@ -2771,8 +3052,10 @@ var GetpepprAdapter = class {
|
|
|
2771
3052
|
}
|
|
2772
3053
|
};
|
|
2773
3054
|
}
|
|
2774
|
-
async acknowledgeInvoice(id) {
|
|
2775
|
-
const
|
|
3055
|
+
async acknowledgeInvoice(id, options) {
|
|
3056
|
+
const headers = {};
|
|
3057
|
+
applyIdempotencyKey(headers, options);
|
|
3058
|
+
const result = await this.request("POST", `/invoices/${id}/ack`, void 0, headers);
|
|
2776
3059
|
return parseSendResult(result);
|
|
2777
3060
|
}
|
|
2778
3061
|
async updateInvoice(id, input) {
|
|
@@ -2823,8 +3106,10 @@ var GetpepprAdapter = class {
|
|
|
2823
3106
|
const result = await this.request("GET", `/contacts/${id}`);
|
|
2824
3107
|
return parseContact(result);
|
|
2825
3108
|
}
|
|
2826
|
-
async createContact(input) {
|
|
2827
|
-
const
|
|
3109
|
+
async createContact(input, options) {
|
|
3110
|
+
const headers = {};
|
|
3111
|
+
applyIdempotencyKey(headers, options);
|
|
3112
|
+
const result = await this.request("POST", "/contacts", input, headers);
|
|
2828
3113
|
return parseContact(result);
|
|
2829
3114
|
}
|
|
2830
3115
|
async updateContact(id, input) {
|
|
@@ -2836,8 +3121,7 @@ var GetpepprAdapter = class {
|
|
|
2836
3121
|
}
|
|
2837
3122
|
async createLegalEntity(input, options) {
|
|
2838
3123
|
const headers = {};
|
|
2839
|
-
|
|
2840
|
-
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
3124
|
+
applyIdempotencyKey(headers, options);
|
|
2841
3125
|
const result = await this.request("POST", "/legal-entities", input, headers);
|
|
2842
3126
|
return parseLegalEntity(result);
|
|
2843
3127
|
}
|
|
@@ -2876,8 +3160,7 @@ var GetpepprAdapter = class {
|
|
|
2876
3160
|
}
|
|
2877
3161
|
async requestLegalEntityAttestation(id, input, options) {
|
|
2878
3162
|
const headers = {};
|
|
2879
|
-
|
|
2880
|
-
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
3163
|
+
applyIdempotencyKey(headers, options);
|
|
2881
3164
|
const result = await this.request("POST", `/legal-entities/${id}/attestation`, input, headers);
|
|
2882
3165
|
return {
|
|
2883
3166
|
id: String(result.id ?? id),
|
|
@@ -2915,8 +3198,10 @@ var GetpepprAdapter = class {
|
|
|
2915
3198
|
const result = await this.request("GET", `/bank-accounts/${id}`);
|
|
2916
3199
|
return parseBankAccount(result);
|
|
2917
3200
|
}
|
|
2918
|
-
async createBankAccount(input) {
|
|
2919
|
-
const
|
|
3201
|
+
async createBankAccount(input, options) {
|
|
3202
|
+
const headers = {};
|
|
3203
|
+
applyIdempotencyKey(headers, options);
|
|
3204
|
+
const result = await this.request("POST", "/bank-accounts", input, headers);
|
|
2920
3205
|
return parseBankAccount(result);
|
|
2921
3206
|
}
|
|
2922
3207
|
async updateBankAccount(id, input) {
|
|
@@ -2953,7 +3238,9 @@ var GetpepprAdapter = class {
|
|
|
2953
3238
|
// tout en salissant le corps des envois standards.
|
|
2954
3239
|
...options.sender ? { sender: options.sender } : {}
|
|
2955
3240
|
};
|
|
2956
|
-
const
|
|
3241
|
+
const headers = {};
|
|
3242
|
+
applyIdempotencyKey(headers, options);
|
|
3243
|
+
const result = await this.request("POST", "/invoices/import", body, headers);
|
|
2957
3244
|
return parseSendResult(result);
|
|
2958
3245
|
}
|
|
2959
3246
|
async listTransportTypes() {
|
|
@@ -3007,6 +3294,9 @@ function detectMimeType(filename) {
|
|
|
3007
3294
|
}
|
|
3008
3295
|
}
|
|
3009
3296
|
var PROTOCOL_ERROR_BODY_LIMIT = 2e3;
|
|
3297
|
+
function cloneResultForHook(result) {
|
|
3298
|
+
return result === void 0 ? void 0 : { ...result };
|
|
3299
|
+
}
|
|
3010
3300
|
function cloneForHook(body) {
|
|
3011
3301
|
try {
|
|
3012
3302
|
return structuredClone(body);
|
|
@@ -3209,12 +3499,24 @@ function parseLegalEntity(raw) {
|
|
|
3209
3499
|
country: raw.country != null ? String(raw.country) : null,
|
|
3210
3500
|
identifier: idObj && idObj.scheme != null && idObj.value != null ? { scheme: String(idObj.scheme), value: String(idObj.value) } : null,
|
|
3211
3501
|
status: String(raw.status ?? "pending"),
|
|
3502
|
+
networkDiscovery: raw.networkDiscovery && typeof raw.networkDiscovery === "object" ? raw.networkDiscovery : { state: "pending", attempts: 0 },
|
|
3212
3503
|
environment: String(raw.environment ?? ""),
|
|
3213
3504
|
createdAt: String(raw.createdAt ?? "")
|
|
3214
3505
|
};
|
|
3215
3506
|
if (raw.verificationDetail != null) {
|
|
3216
3507
|
le.verificationDetail = raw.verificationDetail;
|
|
3217
3508
|
}
|
|
3509
|
+
if (raw.registrationDetail && typeof raw.registrationDetail === "object") {
|
|
3510
|
+
const reason = raw.registrationDetail.reason;
|
|
3511
|
+
const safeReasons = [
|
|
3512
|
+
"already_registered",
|
|
3513
|
+
"invalid_format",
|
|
3514
|
+
"provider_error"
|
|
3515
|
+
];
|
|
3516
|
+
le.registrationDetail = {
|
|
3517
|
+
reason: safeReasons.includes(reason) ? reason : "provider_error"
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3218
3520
|
return le;
|
|
3219
3521
|
}
|
|
3220
3522
|
function parseAccountIdentity(raw) {
|
|
@@ -3437,14 +3739,95 @@ var PeppolProtocolError = class extends PeppolError {
|
|
|
3437
3739
|
var PeppolApiError = class extends PeppolError {
|
|
3438
3740
|
statusCode;
|
|
3439
3741
|
responseBody;
|
|
3440
|
-
/**
|
|
3742
|
+
/**
|
|
3743
|
+
* Parsed `Retry-After` delay in milliseconds.
|
|
3744
|
+
*
|
|
3745
|
+
* `undefined` unless this response is a **429** AND carried a readable
|
|
3746
|
+
* `Retry-After`. No other status reads that header, whatever its remediation
|
|
3747
|
+
* says — measured, all 22 `retry_after` entries in the catalogue are 429s.
|
|
3748
|
+
* The gateway does not attach the header to every throttled answer either.
|
|
3749
|
+
*/
|
|
3441
3750
|
retryAfterMs;
|
|
3442
|
-
|
|
3751
|
+
/**
|
|
3752
|
+
* The canonical result the gateway declared for this response, read from its
|
|
3753
|
+
* six headers — no body parsing required.
|
|
3754
|
+
*
|
|
3755
|
+
* `undefined` against a gateway that has not activated the result catalogue,
|
|
3756
|
+
* and behind any hop that strips unknown headers. The flattened accessors
|
|
3757
|
+
* below all read from here, so they are `undefined` together.
|
|
3758
|
+
*/
|
|
3759
|
+
result;
|
|
3760
|
+
constructor(message, statusCode, responseBody, retryAfterMs, result) {
|
|
3443
3761
|
super(message);
|
|
3444
3762
|
this.statusCode = statusCode;
|
|
3445
3763
|
this.responseBody = responseBody;
|
|
3446
3764
|
this.name = "PeppolApiError";
|
|
3447
3765
|
this.retryAfterMs = retryAfterMs;
|
|
3766
|
+
this.result = result;
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Stable getpeppr result code for this failure (e.g. `"auth.api_key_invalid"`).
|
|
3770
|
+
*
|
|
3771
|
+
* ⛔ NOT the same field as {@link code}, and they can both be present with
|
|
3772
|
+
* different values: this one is the catalogue's global code, `code` is the
|
|
3773
|
+
* route's own sub-reason from the body.
|
|
3774
|
+
*
|
|
3775
|
+
* `undefined` when the gateway sent no result headers.
|
|
3776
|
+
*/
|
|
3777
|
+
get resultCode() {
|
|
3778
|
+
return this.result?.code;
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* The catalogue's sentence for {@link resultCode}.
|
|
3782
|
+
*
|
|
3783
|
+
* ⚠️ Usually SHORTER on detail than `.message`, which is built from the
|
|
3784
|
+
* response body and can name the offending field or rule. Show `.message` to
|
|
3785
|
+
* a human; use this one when you want the stable phrasing.
|
|
3786
|
+
*
|
|
3787
|
+
* `undefined` when the gateway sent no result headers.
|
|
3788
|
+
*/
|
|
3789
|
+
get resultMessage() {
|
|
3790
|
+
return this.result?.message;
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Server-generated correlation id for this exact request. Quote it to support.
|
|
3794
|
+
*
|
|
3795
|
+
* `undefined` when the gateway sent no result headers — which includes every
|
|
3796
|
+
* response from a deployment predating the catalogue.
|
|
3797
|
+
*/
|
|
3798
|
+
get requestId() {
|
|
3799
|
+
return this.result?.requestId;
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Whether retrying this same request can succeed, per the catalogue.
|
|
3803
|
+
*
|
|
3804
|
+
* ⚠️ `undefined` means "the gateway did not say", NOT "no" — the SDK then
|
|
3805
|
+
* falls back to its historic status policy. A `false` here is an explicit
|
|
3806
|
+
* refusal and the SDK will not retry, whatever the status.
|
|
3807
|
+
*/
|
|
3808
|
+
get retryable() {
|
|
3809
|
+
return this.result?.retryable;
|
|
3810
|
+
}
|
|
3811
|
+
/**
|
|
3812
|
+
* What to do about it: `"none"`, `"fix_request"`, `"authenticate"`,
|
|
3813
|
+
* `"retry"`, `"retry_after"`, `"wait"` or `"contact_support"` today.
|
|
3814
|
+
*
|
|
3815
|
+
* Typed open — a value added server-side reaches you rather than vanishing.
|
|
3816
|
+
* `undefined` when the gateway sent no result headers.
|
|
3817
|
+
*/
|
|
3818
|
+
get remediation() {
|
|
3819
|
+
return this.result?.remediation;
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Documentation link for {@link resultCode}, when the catalogue provides one.
|
|
3823
|
+
*
|
|
3824
|
+
* `undefined` when the gateway sent no result headers, when the catalogue
|
|
3825
|
+
* entry has no docs link, or when the value was not a plain `https://` URL
|
|
3826
|
+
* (`http:`, credentials in the authority, and anything the URL parser would
|
|
3827
|
+
* have to repair are all refused).
|
|
3828
|
+
*/
|
|
3829
|
+
get docs() {
|
|
3830
|
+
return this.result?.docs;
|
|
3448
3831
|
}
|
|
3449
3832
|
/**
|
|
3450
3833
|
* The gateway's machine-readable error code, parsed from the JSON response body
|
|
@@ -3617,8 +4000,8 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
3617
4000
|
* always returns 501. Submit the final document with `invoices.send()`.
|
|
3618
4001
|
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3619
4002
|
*/
|
|
3620
|
-
async sendById(id) {
|
|
3621
|
-
return this.adapter.sendInvoiceById(id);
|
|
4003
|
+
async sendById(id, options) {
|
|
4004
|
+
return this.adapter.sendInvoiceById(id, options);
|
|
3622
4005
|
}
|
|
3623
4006
|
/**
|
|
3624
4007
|
* Send an invoice via Peppol.
|
|
@@ -3792,8 +4175,8 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
3792
4175
|
* acknowledgement and always returns 501.
|
|
3793
4176
|
* @throws {PeppolApiError} 501 with the current gateway provider
|
|
3794
4177
|
*/
|
|
3795
|
-
async acknowledge(id) {
|
|
3796
|
-
return this.adapter.acknowledgeInvoice(id);
|
|
4178
|
+
async acknowledge(id, options) {
|
|
4179
|
+
return this.adapter.acknowledgeInvoice(id, options);
|
|
3797
4180
|
}
|
|
3798
4181
|
/**
|
|
3799
4182
|
* Request an update to an existing invoice.
|
|
@@ -4083,8 +4466,8 @@ var ContactOperations = class {
|
|
|
4083
4466
|
* });
|
|
4084
4467
|
* ```
|
|
4085
4468
|
*/
|
|
4086
|
-
async create(input) {
|
|
4087
|
-
return this.adapter.createContact(input);
|
|
4469
|
+
async create(input, options) {
|
|
4470
|
+
return this.adapter.createContact(input, options);
|
|
4088
4471
|
}
|
|
4089
4472
|
/**
|
|
4090
4473
|
* Update an existing contact.
|
|
@@ -4309,8 +4692,8 @@ var BankAccountOperations = class {
|
|
|
4309
4692
|
* });
|
|
4310
4693
|
* ```
|
|
4311
4694
|
*/
|
|
4312
|
-
async create(input) {
|
|
4313
|
-
return this.adapter.createBankAccount(input);
|
|
4695
|
+
async create(input, options) {
|
|
4696
|
+
return this.adapter.createBankAccount(input, options);
|
|
4314
4697
|
}
|
|
4315
4698
|
/**
|
|
4316
4699
|
* Update an existing bank account.
|
|
@@ -5261,6 +5644,12 @@ async function pollUntilTerminal(client, documentId, options = {}) {
|
|
|
5261
5644
|
return { finalStatus: lastStatus, timedOut: true };
|
|
5262
5645
|
}
|
|
5263
5646
|
|
|
5647
|
+
// src/lib/dashboard-url.ts
|
|
5648
|
+
var DASHBOARD_INVOICES_BASE = "https://console.getpeppr.dev/invoices";
|
|
5649
|
+
function dashboardUrlForSendResult(result) {
|
|
5650
|
+
return `${DASHBOARD_INVOICES_BASE}/${result.id}`;
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5264
5653
|
// src/formatters/send-result.ts
|
|
5265
5654
|
import pc5 from "picocolors";
|
|
5266
5655
|
function formatSendResult(result, mode) {
|
|
@@ -5286,7 +5675,6 @@ function formatSendResult(result, mode) {
|
|
|
5286
5675
|
// src/commands/send.ts
|
|
5287
5676
|
var API_BASE = "https://api.getpeppr.dev/v1";
|
|
5288
5677
|
var LOCAL_BASE = "http://localhost:3001/api/v1";
|
|
5289
|
-
var DASHBOARD_BASE = "https://console.getpeppr.dev/invoices";
|
|
5290
5678
|
function registerSendCommand(program2) {
|
|
5291
5679
|
program2.command("send").description("Send an invoice to the Peppol network via getpeppr API").argument("[file]", "optional path to invoice JSON (mutex with --to/--amount/...)").option("--prod", "target production (live keys + confirmation)").option("--local", "target localhost:3001 dev server").option("--key <key>", "override API key \u2014 for CI/scripted use only; visible in `ps` and shell history. Prefer GETPEPPR_API_KEY env var.").option("--to <peppol-id>", "recipient peppol id (e.g., 9925:BE0314595348)").option("--country <iso>", "recipient ISO 3166-1 alpha-2 country override (e.g., BE)").option("--amount <number>", "line amount in major currency units (decimal allowed)").option("--currency <iso>", "ISO 4217 currency (default EUR)").option("--desc <text>", "line description").option("--attachment", "attach the test PDF").option("--watch", "poll status until a terminal state (60s timeout)").option("-y, --yes", "skip --prod confirmation prompt").option("--no-validate", "skip pre-validation locally").option("--json", "output JSON").option("--quiet", "exit code only, no output").action(async (file, flags) => {
|
|
5292
5680
|
let auth;
|
|
@@ -5390,7 +5778,7 @@ function registerSendCommand(program2) {
|
|
|
5390
5778
|
`);
|
|
5391
5779
|
process.exit(1);
|
|
5392
5780
|
}
|
|
5393
|
-
const dashboardUrl =
|
|
5781
|
+
const dashboardUrl = dashboardUrlForSendResult(result);
|
|
5394
5782
|
let finalStatus = result.status;
|
|
5395
5783
|
let timedOut = false;
|
|
5396
5784
|
let watchFailed = false;
|