@deuriib/dgii-ecf 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MANIFEST.json +122 -0
- package/dist/README.md +44 -0
- package/dist/anecf.xsd +134 -0
- package/dist/arecf.xsd +69 -0
- package/dist/e-cf-32.xsd +1707 -0
- package/dist/e-cf-33.xsd +1724 -0
- package/dist/e-cf-34.xsd +1668 -0
- package/dist/e-cf-41.xsd +1543 -0
- package/dist/e-cf-43.xsd +1345 -0
- package/dist/e-cf-44.xsd +1618 -0
- package/dist/e-cf-45.xsd +1682 -0
- package/dist/e-cf-46.xsd +1642 -0
- package/dist/e-cf-47.xsd +1463 -0
- package/dist/index.cjs +420 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -4
- package/dist/index.d.ts +175 -4
- package/dist/index.js +405 -42
- package/dist/index.js.map +1 -1
- package/dist/semilla.xsd +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ var Endpoint = /* @__PURE__ */ ((Endpoint2) => {
|
|
|
24
24
|
Endpoint2["InquiryStatus"] = "consultaestado/api/Consultas/Estado";
|
|
25
25
|
Endpoint2["AllTrackingEcf"] = "ConsultaTrackIds/api/TrackIds/Consulta";
|
|
26
26
|
Endpoint2["Directory"] = "consultadirectorio/api/consultas/obtenerdirectorioporrnc";
|
|
27
|
+
Endpoint2["DirectoryList"] = "consultadirectorio/api/consultas/listado";
|
|
27
28
|
Endpoint2["Void"] = "anulacionrangos/api/operaciones/anularrango";
|
|
28
29
|
return Endpoint2;
|
|
29
30
|
})(Endpoint || {});
|
|
@@ -34,6 +35,19 @@ var SenderReceiverEndpoint = /* @__PURE__ */ ((SenderReceiverEndpoint2) => {
|
|
|
34
35
|
SenderReceiverEndpoint2["CommercialApproval"] = "fe/aprobacioncomercial/api/ecf";
|
|
35
36
|
return SenderReceiverEndpoint2;
|
|
36
37
|
})(SenderReceiverEndpoint || {});
|
|
38
|
+
var StatusEndpoint = /* @__PURE__ */ ((StatusEndpoint2) => {
|
|
39
|
+
StatusEndpoint2["Services"] = "api/estatusservicios/obtenerestatus";
|
|
40
|
+
StatusEndpoint2["MaintenanceWindows"] = "api/estatusservicios/obtenerventanasmantenimiento";
|
|
41
|
+
StatusEndpoint2["Check"] = "api/estatusservicios/verificarestado";
|
|
42
|
+
return StatusEndpoint2;
|
|
43
|
+
})(StatusEndpoint || {});
|
|
44
|
+
var StatusEnvironment = /* @__PURE__ */ ((StatusEnvironment3) => {
|
|
45
|
+
StatusEnvironment3[StatusEnvironment3["PreCertification"] = 1] = "PreCertification";
|
|
46
|
+
StatusEnvironment3[StatusEnvironment3["Production"] = 2] = "Production";
|
|
47
|
+
StatusEnvironment3[StatusEnvironment3["Certification"] = 3] = "Certification";
|
|
48
|
+
return StatusEnvironment3;
|
|
49
|
+
})(StatusEnvironment || {});
|
|
50
|
+
var buildStatusUrl = (endpoint, baseUrl = BaseUrl.STATUS) => `${baseUrl.replace(/\/+$/, "")}/${endpoint}`;
|
|
37
51
|
var buildUrl = (environment, endpoint, baseUrl = BaseUrl.ECF) => `${baseUrl.replace(/\/+$/, "")}/${environment}/${endpoint}`;
|
|
38
52
|
var buildSenderReceiverUrl = (host, endpoint) => `${host.replace(/\/+$/, "")}/${endpoint}`;
|
|
39
53
|
|
|
@@ -46,6 +60,9 @@ var TrackStatusEnum = /* @__PURE__ */ ((TrackStatusEnum2) => {
|
|
|
46
60
|
TrackStatusEnum2["ConditionalAccepted"] = "Aceptado Condicional";
|
|
47
61
|
return TrackStatusEnum2;
|
|
48
62
|
})(TrackStatusEnum || {});
|
|
63
|
+
var SUMMARY_ACCEPTED = "Aceptado";
|
|
64
|
+
var SUMMARY_CONDITIONAL_ACCEPTED = "Aceptado condicional";
|
|
65
|
+
var SUMMARY_REJECTED = "Rechazado";
|
|
49
66
|
|
|
50
67
|
// src/errors.ts
|
|
51
68
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
@@ -409,9 +426,9 @@ var HttpClient = class {
|
|
|
409
426
|
}
|
|
410
427
|
return url;
|
|
411
428
|
}
|
|
412
|
-
authHeaders(extra) {
|
|
429
|
+
authHeaders(extra, omitAuth = false) {
|
|
413
430
|
const headers = { Accept: "application/json" };
|
|
414
|
-
if (this.token) {
|
|
431
|
+
if (!omitAuth && this.token) {
|
|
415
432
|
headers.Authorization = `Bearer ${this.token}`;
|
|
416
433
|
}
|
|
417
434
|
return { ...headers, ...extra };
|
|
@@ -464,7 +481,7 @@ var HttpClient = class {
|
|
|
464
481
|
async execute(options) {
|
|
465
482
|
const url = this.buildUrl(options.path, options.absoluteUrl, options.query);
|
|
466
483
|
const method = options.method ?? "GET";
|
|
467
|
-
const headers = this.authHeaders(options.headers);
|
|
484
|
+
const headers = this.authHeaders(options.headers, options.omitAuth);
|
|
468
485
|
const ctx = {
|
|
469
486
|
method,
|
|
470
487
|
resource: options.resource ?? options.path,
|
|
@@ -786,18 +803,43 @@ var roundHalfUpToCents = (value) => {
|
|
|
786
803
|
const padded = rounded.padStart(3, "0");
|
|
787
804
|
return `${sign}${padded.slice(0, -2)}.${padded.slice(-2)}`;
|
|
788
805
|
};
|
|
806
|
+
var isEmptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
807
|
+
var pruneEmptyValues = (input) => {
|
|
808
|
+
if (input === null || input === void 0 || input === "") {
|
|
809
|
+
return void 0;
|
|
810
|
+
}
|
|
811
|
+
if (Array.isArray(input)) {
|
|
812
|
+
const filtered = input.map(pruneEmptyValues).filter(
|
|
813
|
+
(item) => item !== null && item !== void 0 && item !== "" && !isEmptyObject(item)
|
|
814
|
+
);
|
|
815
|
+
return filtered.length ? filtered : void 0;
|
|
816
|
+
}
|
|
817
|
+
if (typeof input === "object") {
|
|
818
|
+
const output = {};
|
|
819
|
+
for (const [key, value] of Object.entries(
|
|
820
|
+
input
|
|
821
|
+
)) {
|
|
822
|
+
const cleaned = pruneEmptyValues(value);
|
|
823
|
+
if (cleaned !== void 0 && cleaned !== "" && !isEmptyObject(cleaned)) {
|
|
824
|
+
output[key] = cleaned;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return Object.keys(output).length ? output : void 0;
|
|
828
|
+
}
|
|
829
|
+
return input;
|
|
830
|
+
};
|
|
789
831
|
var Transformer = class {
|
|
790
832
|
json2xml(dataObject, round = false) {
|
|
791
833
|
try {
|
|
834
|
+
const pruned = pruneEmptyValues(dataObject);
|
|
792
835
|
const data = {
|
|
793
836
|
_declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
|
|
794
|
-
...
|
|
837
|
+
...pruned !== null && pruned !== void 0 && typeof pruned === "object" && !Array.isArray(pruned) ? pruned : {}
|
|
795
838
|
};
|
|
796
839
|
const xml = convert.js2xml(data, {
|
|
797
840
|
compact: true,
|
|
798
841
|
ignoreComment: true,
|
|
799
842
|
spaces: 2,
|
|
800
|
-
fullTagEmptyElement: true,
|
|
801
843
|
textFn: (value, label) => {
|
|
802
844
|
if (round && label.toLowerCase() !== "version") {
|
|
803
845
|
return roundHalfUpToCents(value);
|
|
@@ -948,6 +990,190 @@ var resolveReceptionRoute = (signedXml) => {
|
|
|
948
990
|
)
|
|
949
991
|
) : ok(GENERAL);
|
|
950
992
|
};
|
|
993
|
+
var B2B_EXCLUDED_TYPES = [
|
|
994
|
+
"41",
|
|
995
|
+
"43",
|
|
996
|
+
"45",
|
|
997
|
+
"46",
|
|
998
|
+
"47"
|
|
999
|
+
];
|
|
1000
|
+
var checkB2BApplicability = (signedXml) => {
|
|
1001
|
+
const root = extractRootElementName(signedXml);
|
|
1002
|
+
if (root === "RFCE") {
|
|
1003
|
+
return err(
|
|
1004
|
+
validationError(
|
|
1005
|
+
"An RFCE summary cannot be delivered directly to a buyer host"
|
|
1006
|
+
)
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
if (root !== "ECF") {
|
|
1010
|
+
return err(
|
|
1011
|
+
validationError(
|
|
1012
|
+
`Unsupported document root "${root ?? "unknown"}"; expected "ECF"`
|
|
1013
|
+
)
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
const tipo = extractElementText(signedXml, "TipoeCF");
|
|
1017
|
+
if (tipo === void 0) {
|
|
1018
|
+
return err(validationError("TipoeCF is missing from the document"));
|
|
1019
|
+
}
|
|
1020
|
+
if (!isEcfTypeCode(tipo)) {
|
|
1021
|
+
return err(validationError(`Unknown TipoeCF "${tipo}"`));
|
|
1022
|
+
}
|
|
1023
|
+
if (B2B_EXCLUDED_TYPES.includes(tipo)) {
|
|
1024
|
+
return err(
|
|
1025
|
+
validationError(
|
|
1026
|
+
`An e-CF of type ${tipo} cannot be delivered directly to a buyer host`
|
|
1027
|
+
)
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
if (tipo !== CONSUMPTION_TYPE) {
|
|
1031
|
+
return ok(void 0);
|
|
1032
|
+
}
|
|
1033
|
+
const total = extractElementText(signedXml, "MontoTotal");
|
|
1034
|
+
if (total === void 0) {
|
|
1035
|
+
return err(
|
|
1036
|
+
validationError(
|
|
1037
|
+
"MontoTotal is required to check B2B applicability of an E32"
|
|
1038
|
+
)
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
if (!DECIMAL_PATTERN.test(total)) {
|
|
1042
|
+
return err(
|
|
1043
|
+
validationError(`MontoTotal "${total}" is not a decimal amount`)
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
if (compareDecimal(total, CONSUMPTION_SUMMARY_THRESHOLD) < 0) {
|
|
1047
|
+
return err(
|
|
1048
|
+
validationError(
|
|
1049
|
+
"An E32 below RD$250,000 cannot be delivered directly to a buyer host; send the RFCE summary to DGII instead"
|
|
1050
|
+
)
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
return ok(void 0);
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
// src/reconciliation.ts
|
|
1057
|
+
var PENDING_SUBMISSION_MESSAGE = "A submission with this key is already pending; call reconcileSubmission() before retrying";
|
|
1058
|
+
var canReuseSequence = (response) => response.secuenciaUtilizada === false;
|
|
1059
|
+
|
|
1060
|
+
// src/validation.ts
|
|
1061
|
+
import { spawnSync } from "child_process";
|
|
1062
|
+
import { existsSync } from "fs";
|
|
1063
|
+
import { dirname, resolve } from "path";
|
|
1064
|
+
import { fileURLToPath } from "url";
|
|
1065
|
+
var moduleDir = (() => {
|
|
1066
|
+
if (typeof __dirname !== "undefined") {
|
|
1067
|
+
return __dirname;
|
|
1068
|
+
}
|
|
1069
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
1070
|
+
})();
|
|
1071
|
+
var SCHEMA_DIR = [moduleDir, resolve(moduleDir, "..", "schemas")].find(
|
|
1072
|
+
(candidate) => existsSync(resolve(candidate, "e-cf-32.xsd"))
|
|
1073
|
+
) ?? resolve(moduleDir, "..", "schemas");
|
|
1074
|
+
var SCHEMA_FILES = {
|
|
1075
|
+
"ECF|32": "e-cf-32.xsd",
|
|
1076
|
+
"ECF|33": "e-cf-33.xsd",
|
|
1077
|
+
"ECF|34": "e-cf-34.xsd",
|
|
1078
|
+
"ECF|41": "e-cf-41.xsd",
|
|
1079
|
+
"ECF|43": "e-cf-43.xsd",
|
|
1080
|
+
"ECF|44": "e-cf-44.xsd",
|
|
1081
|
+
"ECF|45": "e-cf-45.xsd",
|
|
1082
|
+
"ECF|46": "e-cf-46.xsd",
|
|
1083
|
+
"ECF|47": "e-cf-47.xsd",
|
|
1084
|
+
"ANECF|": "anecf.xsd",
|
|
1085
|
+
"ARECF|": "arecf.xsd",
|
|
1086
|
+
"SemillaModel|": "semilla.xsd"
|
|
1087
|
+
};
|
|
1088
|
+
var UNAVAILABLE_SCHEMAS = {
|
|
1089
|
+
"ECF|31": "the published schema references IndicadorServicioTodoIncluidoType, which does not resolve",
|
|
1090
|
+
"RFCE|": "the published schema has an invalid pattern facet: [$0-9] and the \\- escape are not valid XML Schema regex",
|
|
1091
|
+
"ACECF|": "the published schema has an invalid pattern facet: the \\- escape is not valid XML Schema regex"
|
|
1092
|
+
};
|
|
1093
|
+
var schemaKey = (xml) => {
|
|
1094
|
+
const root = extractRootElementName(xml);
|
|
1095
|
+
if (root === void 0) {
|
|
1096
|
+
return void 0;
|
|
1097
|
+
}
|
|
1098
|
+
if (root !== "ECF") {
|
|
1099
|
+
return `${root}|`;
|
|
1100
|
+
}
|
|
1101
|
+
const tipo = extractElementText(xml, "TipoeCF");
|
|
1102
|
+
return tipo === void 0 ? void 0 : `ECF|${tipo}`;
|
|
1103
|
+
};
|
|
1104
|
+
var validationError2 = (message, cause) => new DgiiError(message, {
|
|
1105
|
+
code: "VALIDATION",
|
|
1106
|
+
...cause !== void 0 ? { cause } : {}
|
|
1107
|
+
});
|
|
1108
|
+
var VIOLATION_LINE = /^.*?:(\d+):\s*element\s+([^:]+):\s*(.*)$/;
|
|
1109
|
+
var parseViolations = (stderr) => {
|
|
1110
|
+
const violations = [];
|
|
1111
|
+
for (const raw of stderr.split("\n")) {
|
|
1112
|
+
const line = raw.trim();
|
|
1113
|
+
if (line === "" || line.endsWith("fails to validate") || line.endsWith("validates")) {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
const match = VIOLATION_LINE.exec(line);
|
|
1117
|
+
if (match !== null) {
|
|
1118
|
+
violations.push({
|
|
1119
|
+
path: (match[2] ?? "").trim(),
|
|
1120
|
+
message: (match[3] ?? "").trim()
|
|
1121
|
+
});
|
|
1122
|
+
} else {
|
|
1123
|
+
violations.push({ path: "", message: line });
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return violations;
|
|
1127
|
+
};
|
|
1128
|
+
var validateDocument = (xml, options = {}) => {
|
|
1129
|
+
const key = schemaKey(xml);
|
|
1130
|
+
if (key === void 0) {
|
|
1131
|
+
return err(
|
|
1132
|
+
validationError2("Cannot determine the document type to validate")
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
const file = SCHEMA_FILES[key];
|
|
1136
|
+
if (file === void 0) {
|
|
1137
|
+
const reason = UNAVAILABLE_SCHEMAS[key] ?? "no bundled XML Schema for this document type";
|
|
1138
|
+
const [root, tipo] = key.split("|");
|
|
1139
|
+
const label = tipo ? `${root} ${tipo}` : root ?? key;
|
|
1140
|
+
return err(validationError2(`Schema unavailable for ${label}: ${reason}`));
|
|
1141
|
+
}
|
|
1142
|
+
const bin = options.bin ?? process.env["DGII_XMLLINT_BIN"] ?? "xmllint";
|
|
1143
|
+
const result = spawnSync(
|
|
1144
|
+
bin,
|
|
1145
|
+
["--noout", "--schema", resolve(SCHEMA_DIR, file), "-"],
|
|
1146
|
+
{ input: xml, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }
|
|
1147
|
+
);
|
|
1148
|
+
if (result.error !== void 0 && result.error !== null) {
|
|
1149
|
+
const code = result.error.code;
|
|
1150
|
+
return err(
|
|
1151
|
+
validationError2(
|
|
1152
|
+
code === "ENOENT" ? `xmllint was not found ("${bin}"). Install libxml2-utils or set DGII_XMLLINT_BIN; schema validation cannot run without it` : `Failed to run xmllint: ${result.error.message}`,
|
|
1153
|
+
result.error
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
if (result.status === 0) {
|
|
1158
|
+
return ok([]);
|
|
1159
|
+
}
|
|
1160
|
+
const violations = parseViolations(result.stderr ?? "");
|
|
1161
|
+
return ok(
|
|
1162
|
+
violations.length > 0 ? violations : [
|
|
1163
|
+
{
|
|
1164
|
+
path: "",
|
|
1165
|
+
message: (result.stderr ?? "").trim() || "Validation failed"
|
|
1166
|
+
}
|
|
1167
|
+
]
|
|
1168
|
+
);
|
|
1169
|
+
};
|
|
1170
|
+
var formatViolations = (violations) => {
|
|
1171
|
+
const shown = violations.slice(0, 5).map(
|
|
1172
|
+
(violation) => violation.path ? `${violation.path}: ${violation.message}` : violation.message
|
|
1173
|
+
).join("; ");
|
|
1174
|
+
const remaining = violations.length - 5;
|
|
1175
|
+
return remaining > 0 ? `${shown} (+${remaining} more)` : shown;
|
|
1176
|
+
};
|
|
951
1177
|
|
|
952
1178
|
// src/utils.ts
|
|
953
1179
|
var getCodeSixDigitfromSignature = (signedXml) => {
|
|
@@ -1007,31 +1233,6 @@ var toArray = (value) => {
|
|
|
1007
1233
|
}
|
|
1008
1234
|
return Array.isArray(value) ? value : [value];
|
|
1009
1235
|
};
|
|
1010
|
-
var isEmptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1011
|
-
var removeEmptyValues = (input) => {
|
|
1012
|
-
if (input === null || input === void 0 || input === "") {
|
|
1013
|
-
return void 0;
|
|
1014
|
-
}
|
|
1015
|
-
if (Array.isArray(input)) {
|
|
1016
|
-
const filtered = input.map(removeEmptyValues).filter(
|
|
1017
|
-
(item) => item !== null && item !== void 0 && item !== "" && !isEmptyObject(item)
|
|
1018
|
-
);
|
|
1019
|
-
return filtered.length ? filtered : void 0;
|
|
1020
|
-
}
|
|
1021
|
-
if (typeof input === "object") {
|
|
1022
|
-
const output = {};
|
|
1023
|
-
for (const [key, value] of Object.entries(
|
|
1024
|
-
input
|
|
1025
|
-
)) {
|
|
1026
|
-
const cleaned = removeEmptyValues(value);
|
|
1027
|
-
if (cleaned !== void 0 && cleaned !== "" && !isEmptyObject(cleaned)) {
|
|
1028
|
-
output[key] = cleaned;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
return Object.keys(output).length ? output : void 0;
|
|
1032
|
-
}
|
|
1033
|
-
return input;
|
|
1034
|
-
};
|
|
1035
1236
|
var convertECF32ToRFCE = (ecf32Xml) => {
|
|
1036
1237
|
const securityCode = getCodeSixDigitfromSignature(ecf32Xml);
|
|
1037
1238
|
if (securityCode.isErr()) {
|
|
@@ -1046,7 +1247,7 @@ var convertECF32ToRFCE = (ecf32Xml) => {
|
|
|
1046
1247
|
const impuestos = toArray(
|
|
1047
1248
|
encabezado?.Totales?.ImpuestosAdicionales?.ImpuestoAdicional
|
|
1048
1249
|
);
|
|
1049
|
-
const rfce =
|
|
1250
|
+
const rfce = pruneEmptyValues({
|
|
1050
1251
|
RFCE: {
|
|
1051
1252
|
Encabezado: {
|
|
1052
1253
|
Version: encabezado?.Version,
|
|
@@ -1126,6 +1327,10 @@ var ECF = class {
|
|
|
1126
1327
|
inFlight = /* @__PURE__ */ new Map();
|
|
1127
1328
|
tokenRefreshSkewMs;
|
|
1128
1329
|
now;
|
|
1330
|
+
statusApiKey;
|
|
1331
|
+
enforceB2BRules;
|
|
1332
|
+
validateBeforeSend;
|
|
1333
|
+
xmllintBin;
|
|
1129
1334
|
tokenExpiresAt;
|
|
1130
1335
|
refreshPromise;
|
|
1131
1336
|
constructor(credentials, environment = "TesteCF" /* Dev */, options = {}) {
|
|
@@ -1133,6 +1338,10 @@ var ECF = class {
|
|
|
1133
1338
|
this.environment = environment;
|
|
1134
1339
|
this.tokenRefreshSkewMs = options.tokenRefreshSkewMs ?? 5 * 60 * 1e3;
|
|
1135
1340
|
this.now = options.now ?? Date.now;
|
|
1341
|
+
this.statusApiKey = options.statusApiKey;
|
|
1342
|
+
this.enforceB2BRules = options.enforceB2BRules ?? true;
|
|
1343
|
+
this.validateBeforeSend = options.validateBeforeSend ?? false;
|
|
1344
|
+
this.xmllintBin = options.xmllintBin;
|
|
1136
1345
|
this.idempotency = options.idempotencyStore ?? new InMemoryIdempotencyStore();
|
|
1137
1346
|
this.http = new HttpClient({
|
|
1138
1347
|
baseUrl: BaseUrl.ECF,
|
|
@@ -1147,8 +1356,8 @@ var ECF = class {
|
|
|
1147
1356
|
});
|
|
1148
1357
|
}
|
|
1149
1358
|
buildIdempotencyKey(signedXml) {
|
|
1150
|
-
const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
|
|
1151
|
-
const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
|
|
1359
|
+
const rnc = extractElementText(signedXml, "RNCEmisor") ?? extractElementText(signedXml, "RncEmisor") ?? "unknown-rnc";
|
|
1360
|
+
const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "SecuenciaeNCFDesde") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
|
|
1152
1361
|
return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
|
|
1153
1362
|
}
|
|
1154
1363
|
/**
|
|
@@ -1163,10 +1372,7 @@ var ECF = class {
|
|
|
1163
1372
|
if (cached !== void 0) {
|
|
1164
1373
|
if (isPendingSubmission(cached)) {
|
|
1165
1374
|
return err(
|
|
1166
|
-
new DgiiError(
|
|
1167
|
-
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1168
|
-
{ code: "IDEMPOTENCY" }
|
|
1169
|
-
)
|
|
1375
|
+
new DgiiError(PENDING_SUBMISSION_MESSAGE, { code: "IDEMPOTENCY" })
|
|
1170
1376
|
);
|
|
1171
1377
|
}
|
|
1172
1378
|
return ok(cached);
|
|
@@ -1258,6 +1464,90 @@ var ECF = class {
|
|
|
1258
1464
|
this.tokenExpiresAt = parseExpiry(token.value.expira);
|
|
1259
1465
|
return ok(token.value);
|
|
1260
1466
|
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Query the operational status of every DGII service.
|
|
1469
|
+
*
|
|
1470
|
+
* The status host takes an API key instead of a bearer token, so this call
|
|
1471
|
+
* neither needs `authenticate()` nor triggers a token refresh.
|
|
1472
|
+
*/
|
|
1473
|
+
async getServiceStatus() {
|
|
1474
|
+
const result = await this.statusRequest("api/estatusservicios/obtenerestatus" /* Services */);
|
|
1475
|
+
if (result.isErr()) {
|
|
1476
|
+
return err(result.error);
|
|
1477
|
+
}
|
|
1478
|
+
const data = result.value;
|
|
1479
|
+
if (data === void 0 || data === null) {
|
|
1480
|
+
return ok([]);
|
|
1481
|
+
}
|
|
1482
|
+
return ok(Array.isArray(data) ? data : [data]);
|
|
1483
|
+
}
|
|
1484
|
+
/** Scheduled maintenance windows, one entry per environment. */
|
|
1485
|
+
async getMaintenanceWindows() {
|
|
1486
|
+
const result = await this.statusRequest("api/estatusservicios/obtenerventanasmantenimiento" /* MaintenanceWindows */);
|
|
1487
|
+
if (result.isErr()) {
|
|
1488
|
+
return err(result.error);
|
|
1489
|
+
}
|
|
1490
|
+
const data = result.value;
|
|
1491
|
+
if (Array.isArray(data)) {
|
|
1492
|
+
return ok(data);
|
|
1493
|
+
}
|
|
1494
|
+
return ok(data?.ventanaMantenimientos ?? []);
|
|
1495
|
+
}
|
|
1496
|
+
/** Whether an environment is currently inside a maintenance window. */
|
|
1497
|
+
checkServiceAvailability(ambiente) {
|
|
1498
|
+
return this.statusRequest(
|
|
1499
|
+
"api/estatusservicios/verificarestado" /* Check */,
|
|
1500
|
+
{ ambiente }
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
statusRequest(endpoint, query) {
|
|
1504
|
+
const headers = this.statusApiKey === void 0 ? {} : { Authorization: `Apikey ${this.statusApiKey}` };
|
|
1505
|
+
return this.http.request({
|
|
1506
|
+
method: "GET",
|
|
1507
|
+
path: buildStatusUrl(endpoint),
|
|
1508
|
+
absoluteUrl: true,
|
|
1509
|
+
resource: `status:${endpoint}`,
|
|
1510
|
+
headers,
|
|
1511
|
+
...query !== void 0 ? { query } : {},
|
|
1512
|
+
skipAuthHooks: true,
|
|
1513
|
+
omitAuth: true
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Resolve what happened to a submission whose idempotency marker is pending.
|
|
1518
|
+
*
|
|
1519
|
+
* Queries DGII by e-NCF. When DGII has no record of the document the marker
|
|
1520
|
+
* is cleared, because re-submitting cannot double-issue. When DGII does have
|
|
1521
|
+
* a record the marker stays in place — it is the lock that prevents a second
|
|
1522
|
+
* submission of a document that already exists — and `delivered` tells the
|
|
1523
|
+
* caller to stop retrying.
|
|
1524
|
+
*/
|
|
1525
|
+
async reconcileSubmission(signedXml) {
|
|
1526
|
+
const rnc = extractElementText(signedXml, "RNCEmisor") ?? extractElementText(signedXml, "RncEmisor");
|
|
1527
|
+
const encf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "SecuenciaeNCFDesde");
|
|
1528
|
+
if (rnc === void 0 || encf === void 0) {
|
|
1529
|
+
return err(
|
|
1530
|
+
new DgiiError("The document has no emitter RNC or e-NCF to reconcile", {
|
|
1531
|
+
code: "VALIDATION"
|
|
1532
|
+
})
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
const inquiry = await this.inquiryStatus(rnc, encf);
|
|
1536
|
+
if (inquiry.isErr()) {
|
|
1537
|
+
return err(inquiry.error);
|
|
1538
|
+
}
|
|
1539
|
+
const delivered = inquiry.value.estado !== "No encontrado";
|
|
1540
|
+
const cleared = !delivered;
|
|
1541
|
+
if (cleared) {
|
|
1542
|
+
await this.idempotency.delete(this.buildIdempotencyKey(signedXml));
|
|
1543
|
+
}
|
|
1544
|
+
return ok({
|
|
1545
|
+
inquiry: inquiry.value,
|
|
1546
|
+
delivered,
|
|
1547
|
+
canReuseSequence: canReuseSequence(inquiry.value),
|
|
1548
|
+
cleared
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1261
1551
|
getSeed(buyerHost) {
|
|
1262
1552
|
if (buyerHost) {
|
|
1263
1553
|
const url2 = buildSenderReceiverUrl(
|
|
@@ -1290,6 +1580,29 @@ var ECF = class {
|
|
|
1290
1580
|
* a repeated call returns the cached response instead of double-issuing.
|
|
1291
1581
|
*/
|
|
1292
1582
|
async sendElectronicDocument(signedXml, fileName, buyerHost) {
|
|
1583
|
+
if (buyerHost !== void 0 && this.enforceB2BRules) {
|
|
1584
|
+
const applicable = checkB2BApplicability(signedXml);
|
|
1585
|
+
if (applicable.isErr()) {
|
|
1586
|
+
return err(applicable.error);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
if (this.validateBeforeSend) {
|
|
1590
|
+
const validation = validateDocument(
|
|
1591
|
+
signedXml,
|
|
1592
|
+
this.xmllintBin !== void 0 ? { bin: this.xmllintBin } : {}
|
|
1593
|
+
);
|
|
1594
|
+
if (validation.isErr()) {
|
|
1595
|
+
return err(validation.error);
|
|
1596
|
+
}
|
|
1597
|
+
if (validation.value.length > 0) {
|
|
1598
|
+
return err(
|
|
1599
|
+
new DgiiError(
|
|
1600
|
+
`Document does not match the DGII XML Schema: ${formatViolations(validation.value)}`,
|
|
1601
|
+
{ code: "VALIDATION", data: validation.value }
|
|
1602
|
+
)
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1293
1606
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1294
1607
|
return this.submitIdempotent(key, () => {
|
|
1295
1608
|
const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
|
|
@@ -1377,6 +1690,22 @@ var ECF = class {
|
|
|
1377
1690
|
}
|
|
1378
1691
|
return ok(Array.isArray(data) ? data : [data]);
|
|
1379
1692
|
}
|
|
1693
|
+
/** General directory listing — every receiver that publishes service URLs. */
|
|
1694
|
+
async getDirectoryListing() {
|
|
1695
|
+
const url = buildUrl(this.environment, "consultadirectorio/api/consultas/listado" /* DirectoryList */);
|
|
1696
|
+
const result = await this.http.get(url, void 0, {
|
|
1697
|
+
absoluteUrl: true,
|
|
1698
|
+
resource: "consultadirectorio/api/consultas/listado" /* DirectoryList */
|
|
1699
|
+
});
|
|
1700
|
+
if (result.isErr()) {
|
|
1701
|
+
return err(result.error);
|
|
1702
|
+
}
|
|
1703
|
+
const data = result.value;
|
|
1704
|
+
if (data === void 0 || data === null) {
|
|
1705
|
+
return ok([]);
|
|
1706
|
+
}
|
|
1707
|
+
return ok(Array.isArray(data) ? data : [data]);
|
|
1708
|
+
}
|
|
1380
1709
|
/** RFCE inquiry — DGII production only. */
|
|
1381
1710
|
async getSummaryInvoiceInquiry(rnc, encf, codSeguridad) {
|
|
1382
1711
|
if (this.environment !== "eCF" /* Prod */) {
|
|
@@ -1398,12 +1727,32 @@ var ECF = class {
|
|
|
1398
1727
|
{ absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
|
|
1399
1728
|
);
|
|
1400
1729
|
}
|
|
1401
|
-
|
|
1402
|
-
* Submit a signed commercial approval (ACECF). Idempotent like submissions,
|
|
1403
|
-
* keyed on `RNC + eNCF + SHA-256(signed XML)`.
|
|
1404
|
-
*/
|
|
1405
|
-
sendCommercialApproval(signedXml, fileName) {
|
|
1730
|
+
sendCommercialApproval(signedXml, fileName, buyerHost) {
|
|
1406
1731
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1732
|
+
if (buyerHost !== void 0) {
|
|
1733
|
+
return this.submitIdempotent(
|
|
1734
|
+
key,
|
|
1735
|
+
async () => {
|
|
1736
|
+
const url = buildSenderReceiverUrl(
|
|
1737
|
+
buyerHost,
|
|
1738
|
+
"fe/aprobacioncomercial/api/ecf" /* CommercialApproval */
|
|
1739
|
+
);
|
|
1740
|
+
const result = await this.http.postMultipart(
|
|
1741
|
+
url,
|
|
1742
|
+
fileName,
|
|
1743
|
+
signedXml,
|
|
1744
|
+
{
|
|
1745
|
+
absoluteUrl: true,
|
|
1746
|
+
resource: "fe/aprobacioncomercial/api/ecf" /* CommercialApproval */
|
|
1747
|
+
}
|
|
1748
|
+
);
|
|
1749
|
+
if (result.isErr()) {
|
|
1750
|
+
return result.error.status === 400 ? ok({ status: 400, accepted: false }) : err(result.error);
|
|
1751
|
+
}
|
|
1752
|
+
return ok({ status: 200, accepted: true });
|
|
1753
|
+
}
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1407
1756
|
return this.submitIdempotent(
|
|
1408
1757
|
key,
|
|
1409
1758
|
() => this.http.postMultipart(
|
|
@@ -1444,13 +1793,24 @@ export {
|
|
|
1444
1793
|
InMemoryIdempotencyStore,
|
|
1445
1794
|
Ok,
|
|
1446
1795
|
P12Reader,
|
|
1796
|
+
PENDING_SUBMISSION_MESSAGE,
|
|
1447
1797
|
ResultAsync,
|
|
1798
|
+
SCHEMA_DIR,
|
|
1799
|
+
SUMMARY_ACCEPTED,
|
|
1800
|
+
SUMMARY_CONDITIONAL_ACCEPTED,
|
|
1801
|
+
SUMMARY_REJECTED,
|
|
1448
1802
|
SenderReceiverEndpoint,
|
|
1449
1803
|
Signature,
|
|
1804
|
+
StatusEndpoint,
|
|
1805
|
+
StatusEnvironment,
|
|
1450
1806
|
TrackStatusEnum,
|
|
1451
1807
|
Transformer,
|
|
1808
|
+
UNAVAILABLE_SCHEMAS,
|
|
1452
1809
|
buildSenderReceiverUrl,
|
|
1810
|
+
buildStatusUrl,
|
|
1453
1811
|
buildUrl,
|
|
1812
|
+
canReuseSequence,
|
|
1813
|
+
checkB2BApplicability,
|
|
1454
1814
|
compareDecimal,
|
|
1455
1815
|
convertECF32ToRFCE,
|
|
1456
1816
|
createResiliencePolicies,
|
|
@@ -1458,6 +1818,7 @@ export {
|
|
|
1458
1818
|
err,
|
|
1459
1819
|
errAsync,
|
|
1460
1820
|
extractDgiiErrorMessage,
|
|
1821
|
+
formatViolations,
|
|
1461
1822
|
fromPromise,
|
|
1462
1823
|
fromSafePromise,
|
|
1463
1824
|
fromThrowable,
|
|
@@ -1472,9 +1833,11 @@ export {
|
|
|
1472
1833
|
isRetryableStatus,
|
|
1473
1834
|
ok,
|
|
1474
1835
|
okAsync,
|
|
1836
|
+
pruneEmptyValues,
|
|
1475
1837
|
resolveReceptionRoute,
|
|
1476
1838
|
roundHalfUpToCents,
|
|
1477
1839
|
toDgiiError,
|
|
1840
|
+
validateDocument,
|
|
1478
1841
|
validateXMLCertificate
|
|
1479
1842
|
};
|
|
1480
1843
|
//# sourceMappingURL=index.js.map
|