@deuriib/dgii-ecf 0.5.0 → 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 +422 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -5
- package/dist/index.d.ts +180 -5
- package/dist/index.js +407 -43
- 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]);
|
|
@@ -67,9 +84,10 @@ var readMensajes = (value) => {
|
|
|
67
84
|
if (typeof item === "string" && item.trim()) {
|
|
68
85
|
out.push({ valor: item.trim(), codigo: 0 });
|
|
69
86
|
} else if (isObject(item) && typeof item.valor === "string") {
|
|
87
|
+
const codigo = item.codigo;
|
|
70
88
|
out.push({
|
|
71
89
|
valor: item.valor,
|
|
72
|
-
codigo: typeof
|
|
90
|
+
codigo: typeof codigo === "number" || typeof codigo === "string" ? codigo : 0
|
|
73
91
|
});
|
|
74
92
|
}
|
|
75
93
|
}
|
|
@@ -408,9 +426,9 @@ var HttpClient = class {
|
|
|
408
426
|
}
|
|
409
427
|
return url;
|
|
410
428
|
}
|
|
411
|
-
authHeaders(extra) {
|
|
429
|
+
authHeaders(extra, omitAuth = false) {
|
|
412
430
|
const headers = { Accept: "application/json" };
|
|
413
|
-
if (this.token) {
|
|
431
|
+
if (!omitAuth && this.token) {
|
|
414
432
|
headers.Authorization = `Bearer ${this.token}`;
|
|
415
433
|
}
|
|
416
434
|
return { ...headers, ...extra };
|
|
@@ -463,7 +481,7 @@ var HttpClient = class {
|
|
|
463
481
|
async execute(options) {
|
|
464
482
|
const url = this.buildUrl(options.path, options.absoluteUrl, options.query);
|
|
465
483
|
const method = options.method ?? "GET";
|
|
466
|
-
const headers = this.authHeaders(options.headers);
|
|
484
|
+
const headers = this.authHeaders(options.headers, options.omitAuth);
|
|
467
485
|
const ctx = {
|
|
468
486
|
method,
|
|
469
487
|
resource: options.resource ?? options.path,
|
|
@@ -785,18 +803,43 @@ var roundHalfUpToCents = (value) => {
|
|
|
785
803
|
const padded = rounded.padStart(3, "0");
|
|
786
804
|
return `${sign}${padded.slice(0, -2)}.${padded.slice(-2)}`;
|
|
787
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
|
+
};
|
|
788
831
|
var Transformer = class {
|
|
789
832
|
json2xml(dataObject, round = false) {
|
|
790
833
|
try {
|
|
834
|
+
const pruned = pruneEmptyValues(dataObject);
|
|
791
835
|
const data = {
|
|
792
836
|
_declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
|
|
793
|
-
...
|
|
837
|
+
...pruned !== null && pruned !== void 0 && typeof pruned === "object" && !Array.isArray(pruned) ? pruned : {}
|
|
794
838
|
};
|
|
795
839
|
const xml = convert.js2xml(data, {
|
|
796
840
|
compact: true,
|
|
797
841
|
ignoreComment: true,
|
|
798
842
|
spaces: 2,
|
|
799
|
-
fullTagEmptyElement: true,
|
|
800
843
|
textFn: (value, label) => {
|
|
801
844
|
if (round && label.toLowerCase() !== "version") {
|
|
802
845
|
return roundHalfUpToCents(value);
|
|
@@ -947,6 +990,190 @@ var resolveReceptionRoute = (signedXml) => {
|
|
|
947
990
|
)
|
|
948
991
|
) : ok(GENERAL);
|
|
949
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
|
+
};
|
|
950
1177
|
|
|
951
1178
|
// src/utils.ts
|
|
952
1179
|
var getCodeSixDigitfromSignature = (signedXml) => {
|
|
@@ -1006,31 +1233,6 @@ var toArray = (value) => {
|
|
|
1006
1233
|
}
|
|
1007
1234
|
return Array.isArray(value) ? value : [value];
|
|
1008
1235
|
};
|
|
1009
|
-
var isEmptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1010
|
-
var removeEmptyValues = (input) => {
|
|
1011
|
-
if (input === null || input === void 0 || input === "") {
|
|
1012
|
-
return void 0;
|
|
1013
|
-
}
|
|
1014
|
-
if (Array.isArray(input)) {
|
|
1015
|
-
const filtered = input.map(removeEmptyValues).filter(
|
|
1016
|
-
(item) => item !== null && item !== void 0 && item !== "" && !isEmptyObject(item)
|
|
1017
|
-
);
|
|
1018
|
-
return filtered.length ? filtered : void 0;
|
|
1019
|
-
}
|
|
1020
|
-
if (typeof input === "object") {
|
|
1021
|
-
const output = {};
|
|
1022
|
-
for (const [key, value] of Object.entries(
|
|
1023
|
-
input
|
|
1024
|
-
)) {
|
|
1025
|
-
const cleaned = removeEmptyValues(value);
|
|
1026
|
-
if (cleaned !== void 0 && cleaned !== "" && !isEmptyObject(cleaned)) {
|
|
1027
|
-
output[key] = cleaned;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
return Object.keys(output).length ? output : void 0;
|
|
1031
|
-
}
|
|
1032
|
-
return input;
|
|
1033
|
-
};
|
|
1034
1236
|
var convertECF32ToRFCE = (ecf32Xml) => {
|
|
1035
1237
|
const securityCode = getCodeSixDigitfromSignature(ecf32Xml);
|
|
1036
1238
|
if (securityCode.isErr()) {
|
|
@@ -1045,7 +1247,7 @@ var convertECF32ToRFCE = (ecf32Xml) => {
|
|
|
1045
1247
|
const impuestos = toArray(
|
|
1046
1248
|
encabezado?.Totales?.ImpuestosAdicionales?.ImpuestoAdicional
|
|
1047
1249
|
);
|
|
1048
|
-
const rfce =
|
|
1250
|
+
const rfce = pruneEmptyValues({
|
|
1049
1251
|
RFCE: {
|
|
1050
1252
|
Encabezado: {
|
|
1051
1253
|
Version: encabezado?.Version,
|
|
@@ -1125,6 +1327,10 @@ var ECF = class {
|
|
|
1125
1327
|
inFlight = /* @__PURE__ */ new Map();
|
|
1126
1328
|
tokenRefreshSkewMs;
|
|
1127
1329
|
now;
|
|
1330
|
+
statusApiKey;
|
|
1331
|
+
enforceB2BRules;
|
|
1332
|
+
validateBeforeSend;
|
|
1333
|
+
xmllintBin;
|
|
1128
1334
|
tokenExpiresAt;
|
|
1129
1335
|
refreshPromise;
|
|
1130
1336
|
constructor(credentials, environment = "TesteCF" /* Dev */, options = {}) {
|
|
@@ -1132,6 +1338,10 @@ var ECF = class {
|
|
|
1132
1338
|
this.environment = environment;
|
|
1133
1339
|
this.tokenRefreshSkewMs = options.tokenRefreshSkewMs ?? 5 * 60 * 1e3;
|
|
1134
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;
|
|
1135
1345
|
this.idempotency = options.idempotencyStore ?? new InMemoryIdempotencyStore();
|
|
1136
1346
|
this.http = new HttpClient({
|
|
1137
1347
|
baseUrl: BaseUrl.ECF,
|
|
@@ -1146,8 +1356,8 @@ var ECF = class {
|
|
|
1146
1356
|
});
|
|
1147
1357
|
}
|
|
1148
1358
|
buildIdempotencyKey(signedXml) {
|
|
1149
|
-
const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
|
|
1150
|
-
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";
|
|
1151
1361
|
return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
|
|
1152
1362
|
}
|
|
1153
1363
|
/**
|
|
@@ -1162,10 +1372,7 @@ var ECF = class {
|
|
|
1162
1372
|
if (cached !== void 0) {
|
|
1163
1373
|
if (isPendingSubmission(cached)) {
|
|
1164
1374
|
return err(
|
|
1165
|
-
new DgiiError(
|
|
1166
|
-
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1167
|
-
{ code: "IDEMPOTENCY" }
|
|
1168
|
-
)
|
|
1375
|
+
new DgiiError(PENDING_SUBMISSION_MESSAGE, { code: "IDEMPOTENCY" })
|
|
1169
1376
|
);
|
|
1170
1377
|
}
|
|
1171
1378
|
return ok(cached);
|
|
@@ -1257,6 +1464,90 @@ var ECF = class {
|
|
|
1257
1464
|
this.tokenExpiresAt = parseExpiry(token.value.expira);
|
|
1258
1465
|
return ok(token.value);
|
|
1259
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
|
+
}
|
|
1260
1551
|
getSeed(buyerHost) {
|
|
1261
1552
|
if (buyerHost) {
|
|
1262
1553
|
const url2 = buildSenderReceiverUrl(
|
|
@@ -1289,6 +1580,29 @@ var ECF = class {
|
|
|
1289
1580
|
* a repeated call returns the cached response instead of double-issuing.
|
|
1290
1581
|
*/
|
|
1291
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
|
+
}
|
|
1292
1606
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1293
1607
|
return this.submitIdempotent(key, () => {
|
|
1294
1608
|
const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
|
|
@@ -1376,6 +1690,22 @@ var ECF = class {
|
|
|
1376
1690
|
}
|
|
1377
1691
|
return ok(Array.isArray(data) ? data : [data]);
|
|
1378
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
|
+
}
|
|
1379
1709
|
/** RFCE inquiry — DGII production only. */
|
|
1380
1710
|
async getSummaryInvoiceInquiry(rnc, encf, codSeguridad) {
|
|
1381
1711
|
if (this.environment !== "eCF" /* Prod */) {
|
|
@@ -1397,12 +1727,32 @@ var ECF = class {
|
|
|
1397
1727
|
{ absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
|
|
1398
1728
|
);
|
|
1399
1729
|
}
|
|
1400
|
-
|
|
1401
|
-
* Submit a signed commercial approval (ACECF). Idempotent like submissions,
|
|
1402
|
-
* keyed on `RNC + eNCF + SHA-256(signed XML)`.
|
|
1403
|
-
*/
|
|
1404
|
-
sendCommercialApproval(signedXml, fileName) {
|
|
1730
|
+
sendCommercialApproval(signedXml, fileName, buyerHost) {
|
|
1405
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
|
+
}
|
|
1406
1756
|
return this.submitIdempotent(
|
|
1407
1757
|
key,
|
|
1408
1758
|
() => this.http.postMultipart(
|
|
@@ -1443,13 +1793,24 @@ export {
|
|
|
1443
1793
|
InMemoryIdempotencyStore,
|
|
1444
1794
|
Ok,
|
|
1445
1795
|
P12Reader,
|
|
1796
|
+
PENDING_SUBMISSION_MESSAGE,
|
|
1446
1797
|
ResultAsync,
|
|
1798
|
+
SCHEMA_DIR,
|
|
1799
|
+
SUMMARY_ACCEPTED,
|
|
1800
|
+
SUMMARY_CONDITIONAL_ACCEPTED,
|
|
1801
|
+
SUMMARY_REJECTED,
|
|
1447
1802
|
SenderReceiverEndpoint,
|
|
1448
1803
|
Signature,
|
|
1804
|
+
StatusEndpoint,
|
|
1805
|
+
StatusEnvironment,
|
|
1449
1806
|
TrackStatusEnum,
|
|
1450
1807
|
Transformer,
|
|
1808
|
+
UNAVAILABLE_SCHEMAS,
|
|
1451
1809
|
buildSenderReceiverUrl,
|
|
1810
|
+
buildStatusUrl,
|
|
1452
1811
|
buildUrl,
|
|
1812
|
+
canReuseSequence,
|
|
1813
|
+
checkB2BApplicability,
|
|
1453
1814
|
compareDecimal,
|
|
1454
1815
|
convertECF32ToRFCE,
|
|
1455
1816
|
createResiliencePolicies,
|
|
@@ -1457,6 +1818,7 @@ export {
|
|
|
1457
1818
|
err,
|
|
1458
1819
|
errAsync,
|
|
1459
1820
|
extractDgiiErrorMessage,
|
|
1821
|
+
formatViolations,
|
|
1460
1822
|
fromPromise,
|
|
1461
1823
|
fromSafePromise,
|
|
1462
1824
|
fromThrowable,
|
|
@@ -1471,9 +1833,11 @@ export {
|
|
|
1471
1833
|
isRetryableStatus,
|
|
1472
1834
|
ok,
|
|
1473
1835
|
okAsync,
|
|
1836
|
+
pruneEmptyValues,
|
|
1474
1837
|
resolveReceptionRoute,
|
|
1475
1838
|
roundHalfUpToCents,
|
|
1476
1839
|
toDgiiError,
|
|
1840
|
+
validateDocument,
|
|
1477
1841
|
validateXMLCertificate
|
|
1478
1842
|
};
|
|
1479
1843
|
//# sourceMappingURL=index.js.map
|