@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.cjs
CHANGED
|
@@ -42,13 +42,24 @@ __export(index_exports, {
|
|
|
42
42
|
InMemoryIdempotencyStore: () => InMemoryIdempotencyStore,
|
|
43
43
|
Ok: () => import_neverthrow.Ok,
|
|
44
44
|
P12Reader: () => P12Reader,
|
|
45
|
+
PENDING_SUBMISSION_MESSAGE: () => PENDING_SUBMISSION_MESSAGE,
|
|
45
46
|
ResultAsync: () => import_neverthrow.ResultAsync,
|
|
47
|
+
SCHEMA_DIR: () => SCHEMA_DIR,
|
|
48
|
+
SUMMARY_ACCEPTED: () => SUMMARY_ACCEPTED,
|
|
49
|
+
SUMMARY_CONDITIONAL_ACCEPTED: () => SUMMARY_CONDITIONAL_ACCEPTED,
|
|
50
|
+
SUMMARY_REJECTED: () => SUMMARY_REJECTED,
|
|
46
51
|
SenderReceiverEndpoint: () => SenderReceiverEndpoint,
|
|
47
52
|
Signature: () => Signature,
|
|
53
|
+
StatusEndpoint: () => StatusEndpoint,
|
|
54
|
+
StatusEnvironment: () => StatusEnvironment,
|
|
48
55
|
TrackStatusEnum: () => TrackStatusEnum,
|
|
49
56
|
Transformer: () => Transformer,
|
|
57
|
+
UNAVAILABLE_SCHEMAS: () => UNAVAILABLE_SCHEMAS,
|
|
50
58
|
buildSenderReceiverUrl: () => buildSenderReceiverUrl,
|
|
59
|
+
buildStatusUrl: () => buildStatusUrl,
|
|
51
60
|
buildUrl: () => buildUrl,
|
|
61
|
+
canReuseSequence: () => canReuseSequence,
|
|
62
|
+
checkB2BApplicability: () => checkB2BApplicability,
|
|
52
63
|
compareDecimal: () => compareDecimal,
|
|
53
64
|
convertECF32ToRFCE: () => convertECF32ToRFCE,
|
|
54
65
|
createResiliencePolicies: () => createResiliencePolicies,
|
|
@@ -56,6 +67,7 @@ __export(index_exports, {
|
|
|
56
67
|
err: () => import_neverthrow.err,
|
|
57
68
|
errAsync: () => import_neverthrow.errAsync,
|
|
58
69
|
extractDgiiErrorMessage: () => extractDgiiErrorMessage,
|
|
70
|
+
formatViolations: () => formatViolations,
|
|
59
71
|
fromPromise: () => import_neverthrow.fromPromise,
|
|
60
72
|
fromSafePromise: () => import_neverthrow.fromSafePromise,
|
|
61
73
|
fromThrowable: () => import_neverthrow.fromThrowable,
|
|
@@ -70,9 +82,11 @@ __export(index_exports, {
|
|
|
70
82
|
isRetryableStatus: () => isRetryableStatus,
|
|
71
83
|
ok: () => import_neverthrow.ok,
|
|
72
84
|
okAsync: () => import_neverthrow.okAsync,
|
|
85
|
+
pruneEmptyValues: () => pruneEmptyValues,
|
|
73
86
|
resolveReceptionRoute: () => resolveReceptionRoute,
|
|
74
87
|
roundHalfUpToCents: () => roundHalfUpToCents,
|
|
75
88
|
toDgiiError: () => toDgiiError,
|
|
89
|
+
validateDocument: () => validateDocument,
|
|
76
90
|
validateXMLCertificate: () => validateXMLCertificate
|
|
77
91
|
});
|
|
78
92
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -103,6 +117,7 @@ var Endpoint = /* @__PURE__ */ ((Endpoint2) => {
|
|
|
103
117
|
Endpoint2["InquiryStatus"] = "consultaestado/api/Consultas/Estado";
|
|
104
118
|
Endpoint2["AllTrackingEcf"] = "ConsultaTrackIds/api/TrackIds/Consulta";
|
|
105
119
|
Endpoint2["Directory"] = "consultadirectorio/api/consultas/obtenerdirectorioporrnc";
|
|
120
|
+
Endpoint2["DirectoryList"] = "consultadirectorio/api/consultas/listado";
|
|
106
121
|
Endpoint2["Void"] = "anulacionrangos/api/operaciones/anularrango";
|
|
107
122
|
return Endpoint2;
|
|
108
123
|
})(Endpoint || {});
|
|
@@ -113,6 +128,19 @@ var SenderReceiverEndpoint = /* @__PURE__ */ ((SenderReceiverEndpoint2) => {
|
|
|
113
128
|
SenderReceiverEndpoint2["CommercialApproval"] = "fe/aprobacioncomercial/api/ecf";
|
|
114
129
|
return SenderReceiverEndpoint2;
|
|
115
130
|
})(SenderReceiverEndpoint || {});
|
|
131
|
+
var StatusEndpoint = /* @__PURE__ */ ((StatusEndpoint2) => {
|
|
132
|
+
StatusEndpoint2["Services"] = "api/estatusservicios/obtenerestatus";
|
|
133
|
+
StatusEndpoint2["MaintenanceWindows"] = "api/estatusservicios/obtenerventanasmantenimiento";
|
|
134
|
+
StatusEndpoint2["Check"] = "api/estatusservicios/verificarestado";
|
|
135
|
+
return StatusEndpoint2;
|
|
136
|
+
})(StatusEndpoint || {});
|
|
137
|
+
var StatusEnvironment = /* @__PURE__ */ ((StatusEnvironment3) => {
|
|
138
|
+
StatusEnvironment3[StatusEnvironment3["PreCertification"] = 1] = "PreCertification";
|
|
139
|
+
StatusEnvironment3[StatusEnvironment3["Production"] = 2] = "Production";
|
|
140
|
+
StatusEnvironment3[StatusEnvironment3["Certification"] = 3] = "Certification";
|
|
141
|
+
return StatusEnvironment3;
|
|
142
|
+
})(StatusEnvironment || {});
|
|
143
|
+
var buildStatusUrl = (endpoint, baseUrl = BaseUrl.STATUS) => `${baseUrl.replace(/\/+$/, "")}/${endpoint}`;
|
|
116
144
|
var buildUrl = (environment, endpoint, baseUrl = BaseUrl.ECF) => `${baseUrl.replace(/\/+$/, "")}/${environment}/${endpoint}`;
|
|
117
145
|
var buildSenderReceiverUrl = (host, endpoint) => `${host.replace(/\/+$/, "")}/${endpoint}`;
|
|
118
146
|
|
|
@@ -125,6 +153,9 @@ var TrackStatusEnum = /* @__PURE__ */ ((TrackStatusEnum2) => {
|
|
|
125
153
|
TrackStatusEnum2["ConditionalAccepted"] = "Aceptado Condicional";
|
|
126
154
|
return TrackStatusEnum2;
|
|
127
155
|
})(TrackStatusEnum || {});
|
|
156
|
+
var SUMMARY_ACCEPTED = "Aceptado";
|
|
157
|
+
var SUMMARY_CONDITIONAL_ACCEPTED = "Aceptado condicional";
|
|
158
|
+
var SUMMARY_REJECTED = "Rechazado";
|
|
128
159
|
|
|
129
160
|
// src/errors.ts
|
|
130
161
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
@@ -146,9 +177,10 @@ var readMensajes = (value) => {
|
|
|
146
177
|
if (typeof item === "string" && item.trim()) {
|
|
147
178
|
out.push({ valor: item.trim(), codigo: 0 });
|
|
148
179
|
} else if (isObject(item) && typeof item.valor === "string") {
|
|
180
|
+
const codigo = item.codigo;
|
|
149
181
|
out.push({
|
|
150
182
|
valor: item.valor,
|
|
151
|
-
codigo: typeof
|
|
183
|
+
codigo: typeof codigo === "number" || typeof codigo === "string" ? codigo : 0
|
|
152
184
|
});
|
|
153
185
|
}
|
|
154
186
|
}
|
|
@@ -465,9 +497,9 @@ var HttpClient = class {
|
|
|
465
497
|
}
|
|
466
498
|
return url;
|
|
467
499
|
}
|
|
468
|
-
authHeaders(extra) {
|
|
500
|
+
authHeaders(extra, omitAuth = false) {
|
|
469
501
|
const headers = { Accept: "application/json" };
|
|
470
|
-
if (this.token) {
|
|
502
|
+
if (!omitAuth && this.token) {
|
|
471
503
|
headers.Authorization = `Bearer ${this.token}`;
|
|
472
504
|
}
|
|
473
505
|
return { ...headers, ...extra };
|
|
@@ -520,7 +552,7 @@ var HttpClient = class {
|
|
|
520
552
|
async execute(options) {
|
|
521
553
|
const url = this.buildUrl(options.path, options.absoluteUrl, options.query);
|
|
522
554
|
const method = options.method ?? "GET";
|
|
523
|
-
const headers = this.authHeaders(options.headers);
|
|
555
|
+
const headers = this.authHeaders(options.headers, options.omitAuth);
|
|
524
556
|
const ctx = {
|
|
525
557
|
method,
|
|
526
558
|
resource: options.resource ?? options.path,
|
|
@@ -842,18 +874,43 @@ var roundHalfUpToCents = (value) => {
|
|
|
842
874
|
const padded = rounded.padStart(3, "0");
|
|
843
875
|
return `${sign}${padded.slice(0, -2)}.${padded.slice(-2)}`;
|
|
844
876
|
};
|
|
877
|
+
var isEmptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
878
|
+
var pruneEmptyValues = (input) => {
|
|
879
|
+
if (input === null || input === void 0 || input === "") {
|
|
880
|
+
return void 0;
|
|
881
|
+
}
|
|
882
|
+
if (Array.isArray(input)) {
|
|
883
|
+
const filtered = input.map(pruneEmptyValues).filter(
|
|
884
|
+
(item) => item !== null && item !== void 0 && item !== "" && !isEmptyObject(item)
|
|
885
|
+
);
|
|
886
|
+
return filtered.length ? filtered : void 0;
|
|
887
|
+
}
|
|
888
|
+
if (typeof input === "object") {
|
|
889
|
+
const output = {};
|
|
890
|
+
for (const [key, value] of Object.entries(
|
|
891
|
+
input
|
|
892
|
+
)) {
|
|
893
|
+
const cleaned = pruneEmptyValues(value);
|
|
894
|
+
if (cleaned !== void 0 && cleaned !== "" && !isEmptyObject(cleaned)) {
|
|
895
|
+
output[key] = cleaned;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return Object.keys(output).length ? output : void 0;
|
|
899
|
+
}
|
|
900
|
+
return input;
|
|
901
|
+
};
|
|
845
902
|
var Transformer = class {
|
|
846
903
|
json2xml(dataObject, round = false) {
|
|
847
904
|
try {
|
|
905
|
+
const pruned = pruneEmptyValues(dataObject);
|
|
848
906
|
const data = {
|
|
849
907
|
_declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
|
|
850
|
-
...
|
|
908
|
+
...pruned !== null && pruned !== void 0 && typeof pruned === "object" && !Array.isArray(pruned) ? pruned : {}
|
|
851
909
|
};
|
|
852
910
|
const xml = import_xml_js.default.js2xml(data, {
|
|
853
911
|
compact: true,
|
|
854
912
|
ignoreComment: true,
|
|
855
913
|
spaces: 2,
|
|
856
|
-
fullTagEmptyElement: true,
|
|
857
914
|
textFn: (value, label) => {
|
|
858
915
|
if (round && label.toLowerCase() !== "version") {
|
|
859
916
|
return roundHalfUpToCents(value);
|
|
@@ -1004,6 +1061,191 @@ var resolveReceptionRoute = (signedXml) => {
|
|
|
1004
1061
|
)
|
|
1005
1062
|
) : (0, import_neverthrow.ok)(GENERAL);
|
|
1006
1063
|
};
|
|
1064
|
+
var B2B_EXCLUDED_TYPES = [
|
|
1065
|
+
"41",
|
|
1066
|
+
"43",
|
|
1067
|
+
"45",
|
|
1068
|
+
"46",
|
|
1069
|
+
"47"
|
|
1070
|
+
];
|
|
1071
|
+
var checkB2BApplicability = (signedXml) => {
|
|
1072
|
+
const root = extractRootElementName(signedXml);
|
|
1073
|
+
if (root === "RFCE") {
|
|
1074
|
+
return (0, import_neverthrow.err)(
|
|
1075
|
+
validationError(
|
|
1076
|
+
"An RFCE summary cannot be delivered directly to a buyer host"
|
|
1077
|
+
)
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (root !== "ECF") {
|
|
1081
|
+
return (0, import_neverthrow.err)(
|
|
1082
|
+
validationError(
|
|
1083
|
+
`Unsupported document root "${root ?? "unknown"}"; expected "ECF"`
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
const tipo = extractElementText(signedXml, "TipoeCF");
|
|
1088
|
+
if (tipo === void 0) {
|
|
1089
|
+
return (0, import_neverthrow.err)(validationError("TipoeCF is missing from the document"));
|
|
1090
|
+
}
|
|
1091
|
+
if (!isEcfTypeCode(tipo)) {
|
|
1092
|
+
return (0, import_neverthrow.err)(validationError(`Unknown TipoeCF "${tipo}"`));
|
|
1093
|
+
}
|
|
1094
|
+
if (B2B_EXCLUDED_TYPES.includes(tipo)) {
|
|
1095
|
+
return (0, import_neverthrow.err)(
|
|
1096
|
+
validationError(
|
|
1097
|
+
`An e-CF of type ${tipo} cannot be delivered directly to a buyer host`
|
|
1098
|
+
)
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
if (tipo !== CONSUMPTION_TYPE) {
|
|
1102
|
+
return (0, import_neverthrow.ok)(void 0);
|
|
1103
|
+
}
|
|
1104
|
+
const total = extractElementText(signedXml, "MontoTotal");
|
|
1105
|
+
if (total === void 0) {
|
|
1106
|
+
return (0, import_neverthrow.err)(
|
|
1107
|
+
validationError(
|
|
1108
|
+
"MontoTotal is required to check B2B applicability of an E32"
|
|
1109
|
+
)
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
if (!DECIMAL_PATTERN.test(total)) {
|
|
1113
|
+
return (0, import_neverthrow.err)(
|
|
1114
|
+
validationError(`MontoTotal "${total}" is not a decimal amount`)
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
if (compareDecimal(total, CONSUMPTION_SUMMARY_THRESHOLD) < 0) {
|
|
1118
|
+
return (0, import_neverthrow.err)(
|
|
1119
|
+
validationError(
|
|
1120
|
+
"An E32 below RD$250,000 cannot be delivered directly to a buyer host; send the RFCE summary to DGII instead"
|
|
1121
|
+
)
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
return (0, import_neverthrow.ok)(void 0);
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
// src/reconciliation.ts
|
|
1128
|
+
var PENDING_SUBMISSION_MESSAGE = "A submission with this key is already pending; call reconcileSubmission() before retrying";
|
|
1129
|
+
var canReuseSequence = (response) => response.secuenciaUtilizada === false;
|
|
1130
|
+
|
|
1131
|
+
// src/validation.ts
|
|
1132
|
+
var import_node_child_process = require("child_process");
|
|
1133
|
+
var import_node_fs2 = require("fs");
|
|
1134
|
+
var import_node_path = require("path");
|
|
1135
|
+
var import_node_url = require("url");
|
|
1136
|
+
var import_meta = {};
|
|
1137
|
+
var moduleDir = (() => {
|
|
1138
|
+
if (typeof __dirname !== "undefined") {
|
|
1139
|
+
return __dirname;
|
|
1140
|
+
}
|
|
1141
|
+
return (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
1142
|
+
})();
|
|
1143
|
+
var SCHEMA_DIR = [moduleDir, (0, import_node_path.resolve)(moduleDir, "..", "schemas")].find(
|
|
1144
|
+
(candidate) => (0, import_node_fs2.existsSync)((0, import_node_path.resolve)(candidate, "e-cf-32.xsd"))
|
|
1145
|
+
) ?? (0, import_node_path.resolve)(moduleDir, "..", "schemas");
|
|
1146
|
+
var SCHEMA_FILES = {
|
|
1147
|
+
"ECF|32": "e-cf-32.xsd",
|
|
1148
|
+
"ECF|33": "e-cf-33.xsd",
|
|
1149
|
+
"ECF|34": "e-cf-34.xsd",
|
|
1150
|
+
"ECF|41": "e-cf-41.xsd",
|
|
1151
|
+
"ECF|43": "e-cf-43.xsd",
|
|
1152
|
+
"ECF|44": "e-cf-44.xsd",
|
|
1153
|
+
"ECF|45": "e-cf-45.xsd",
|
|
1154
|
+
"ECF|46": "e-cf-46.xsd",
|
|
1155
|
+
"ECF|47": "e-cf-47.xsd",
|
|
1156
|
+
"ANECF|": "anecf.xsd",
|
|
1157
|
+
"ARECF|": "arecf.xsd",
|
|
1158
|
+
"SemillaModel|": "semilla.xsd"
|
|
1159
|
+
};
|
|
1160
|
+
var UNAVAILABLE_SCHEMAS = {
|
|
1161
|
+
"ECF|31": "the published schema references IndicadorServicioTodoIncluidoType, which does not resolve",
|
|
1162
|
+
"RFCE|": "the published schema has an invalid pattern facet: [$0-9] and the \\- escape are not valid XML Schema regex",
|
|
1163
|
+
"ACECF|": "the published schema has an invalid pattern facet: the \\- escape is not valid XML Schema regex"
|
|
1164
|
+
};
|
|
1165
|
+
var schemaKey = (xml) => {
|
|
1166
|
+
const root = extractRootElementName(xml);
|
|
1167
|
+
if (root === void 0) {
|
|
1168
|
+
return void 0;
|
|
1169
|
+
}
|
|
1170
|
+
if (root !== "ECF") {
|
|
1171
|
+
return `${root}|`;
|
|
1172
|
+
}
|
|
1173
|
+
const tipo = extractElementText(xml, "TipoeCF");
|
|
1174
|
+
return tipo === void 0 ? void 0 : `ECF|${tipo}`;
|
|
1175
|
+
};
|
|
1176
|
+
var validationError2 = (message, cause) => new DgiiError(message, {
|
|
1177
|
+
code: "VALIDATION",
|
|
1178
|
+
...cause !== void 0 ? { cause } : {}
|
|
1179
|
+
});
|
|
1180
|
+
var VIOLATION_LINE = /^.*?:(\d+):\s*element\s+([^:]+):\s*(.*)$/;
|
|
1181
|
+
var parseViolations = (stderr) => {
|
|
1182
|
+
const violations = [];
|
|
1183
|
+
for (const raw of stderr.split("\n")) {
|
|
1184
|
+
const line = raw.trim();
|
|
1185
|
+
if (line === "" || line.endsWith("fails to validate") || line.endsWith("validates")) {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
const match = VIOLATION_LINE.exec(line);
|
|
1189
|
+
if (match !== null) {
|
|
1190
|
+
violations.push({
|
|
1191
|
+
path: (match[2] ?? "").trim(),
|
|
1192
|
+
message: (match[3] ?? "").trim()
|
|
1193
|
+
});
|
|
1194
|
+
} else {
|
|
1195
|
+
violations.push({ path: "", message: line });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return violations;
|
|
1199
|
+
};
|
|
1200
|
+
var validateDocument = (xml, options = {}) => {
|
|
1201
|
+
const key = schemaKey(xml);
|
|
1202
|
+
if (key === void 0) {
|
|
1203
|
+
return (0, import_neverthrow.err)(
|
|
1204
|
+
validationError2("Cannot determine the document type to validate")
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
const file = SCHEMA_FILES[key];
|
|
1208
|
+
if (file === void 0) {
|
|
1209
|
+
const reason = UNAVAILABLE_SCHEMAS[key] ?? "no bundled XML Schema for this document type";
|
|
1210
|
+
const [root, tipo] = key.split("|");
|
|
1211
|
+
const label = tipo ? `${root} ${tipo}` : root ?? key;
|
|
1212
|
+
return (0, import_neverthrow.err)(validationError2(`Schema unavailable for ${label}: ${reason}`));
|
|
1213
|
+
}
|
|
1214
|
+
const bin = options.bin ?? process.env["DGII_XMLLINT_BIN"] ?? "xmllint";
|
|
1215
|
+
const result = (0, import_node_child_process.spawnSync)(
|
|
1216
|
+
bin,
|
|
1217
|
+
["--noout", "--schema", (0, import_node_path.resolve)(SCHEMA_DIR, file), "-"],
|
|
1218
|
+
{ input: xml, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }
|
|
1219
|
+
);
|
|
1220
|
+
if (result.error !== void 0 && result.error !== null) {
|
|
1221
|
+
const code = result.error.code;
|
|
1222
|
+
return (0, import_neverthrow.err)(
|
|
1223
|
+
validationError2(
|
|
1224
|
+
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}`,
|
|
1225
|
+
result.error
|
|
1226
|
+
)
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
if (result.status === 0) {
|
|
1230
|
+
return (0, import_neverthrow.ok)([]);
|
|
1231
|
+
}
|
|
1232
|
+
const violations = parseViolations(result.stderr ?? "");
|
|
1233
|
+
return (0, import_neverthrow.ok)(
|
|
1234
|
+
violations.length > 0 ? violations : [
|
|
1235
|
+
{
|
|
1236
|
+
path: "",
|
|
1237
|
+
message: (result.stderr ?? "").trim() || "Validation failed"
|
|
1238
|
+
}
|
|
1239
|
+
]
|
|
1240
|
+
);
|
|
1241
|
+
};
|
|
1242
|
+
var formatViolations = (violations) => {
|
|
1243
|
+
const shown = violations.slice(0, 5).map(
|
|
1244
|
+
(violation) => violation.path ? `${violation.path}: ${violation.message}` : violation.message
|
|
1245
|
+
).join("; ");
|
|
1246
|
+
const remaining = violations.length - 5;
|
|
1247
|
+
return remaining > 0 ? `${shown} (+${remaining} more)` : shown;
|
|
1248
|
+
};
|
|
1007
1249
|
|
|
1008
1250
|
// src/utils.ts
|
|
1009
1251
|
var getCodeSixDigitfromSignature = (signedXml) => {
|
|
@@ -1063,31 +1305,6 @@ var toArray = (value) => {
|
|
|
1063
1305
|
}
|
|
1064
1306
|
return Array.isArray(value) ? value : [value];
|
|
1065
1307
|
};
|
|
1066
|
-
var isEmptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1067
|
-
var removeEmptyValues = (input) => {
|
|
1068
|
-
if (input === null || input === void 0 || input === "") {
|
|
1069
|
-
return void 0;
|
|
1070
|
-
}
|
|
1071
|
-
if (Array.isArray(input)) {
|
|
1072
|
-
const filtered = input.map(removeEmptyValues).filter(
|
|
1073
|
-
(item) => item !== null && item !== void 0 && item !== "" && !isEmptyObject(item)
|
|
1074
|
-
);
|
|
1075
|
-
return filtered.length ? filtered : void 0;
|
|
1076
|
-
}
|
|
1077
|
-
if (typeof input === "object") {
|
|
1078
|
-
const output = {};
|
|
1079
|
-
for (const [key, value] of Object.entries(
|
|
1080
|
-
input
|
|
1081
|
-
)) {
|
|
1082
|
-
const cleaned = removeEmptyValues(value);
|
|
1083
|
-
if (cleaned !== void 0 && cleaned !== "" && !isEmptyObject(cleaned)) {
|
|
1084
|
-
output[key] = cleaned;
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
return Object.keys(output).length ? output : void 0;
|
|
1088
|
-
}
|
|
1089
|
-
return input;
|
|
1090
|
-
};
|
|
1091
1308
|
var convertECF32ToRFCE = (ecf32Xml) => {
|
|
1092
1309
|
const securityCode = getCodeSixDigitfromSignature(ecf32Xml);
|
|
1093
1310
|
if (securityCode.isErr()) {
|
|
@@ -1102,7 +1319,7 @@ var convertECF32ToRFCE = (ecf32Xml) => {
|
|
|
1102
1319
|
const impuestos = toArray(
|
|
1103
1320
|
encabezado?.Totales?.ImpuestosAdicionales?.ImpuestoAdicional
|
|
1104
1321
|
);
|
|
1105
|
-
const rfce =
|
|
1322
|
+
const rfce = pruneEmptyValues({
|
|
1106
1323
|
RFCE: {
|
|
1107
1324
|
Encabezado: {
|
|
1108
1325
|
Version: encabezado?.Version,
|
|
@@ -1182,6 +1399,10 @@ var ECF = class {
|
|
|
1182
1399
|
inFlight = /* @__PURE__ */ new Map();
|
|
1183
1400
|
tokenRefreshSkewMs;
|
|
1184
1401
|
now;
|
|
1402
|
+
statusApiKey;
|
|
1403
|
+
enforceB2BRules;
|
|
1404
|
+
validateBeforeSend;
|
|
1405
|
+
xmllintBin;
|
|
1185
1406
|
tokenExpiresAt;
|
|
1186
1407
|
refreshPromise;
|
|
1187
1408
|
constructor(credentials, environment = "TesteCF" /* Dev */, options = {}) {
|
|
@@ -1189,6 +1410,10 @@ var ECF = class {
|
|
|
1189
1410
|
this.environment = environment;
|
|
1190
1411
|
this.tokenRefreshSkewMs = options.tokenRefreshSkewMs ?? 5 * 60 * 1e3;
|
|
1191
1412
|
this.now = options.now ?? Date.now;
|
|
1413
|
+
this.statusApiKey = options.statusApiKey;
|
|
1414
|
+
this.enforceB2BRules = options.enforceB2BRules ?? true;
|
|
1415
|
+
this.validateBeforeSend = options.validateBeforeSend ?? false;
|
|
1416
|
+
this.xmllintBin = options.xmllintBin;
|
|
1192
1417
|
this.idempotency = options.idempotencyStore ?? new InMemoryIdempotencyStore();
|
|
1193
1418
|
this.http = new HttpClient({
|
|
1194
1419
|
baseUrl: BaseUrl.ECF,
|
|
@@ -1203,8 +1428,8 @@ var ECF = class {
|
|
|
1203
1428
|
});
|
|
1204
1429
|
}
|
|
1205
1430
|
buildIdempotencyKey(signedXml) {
|
|
1206
|
-
const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
|
|
1207
|
-
const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
|
|
1431
|
+
const rnc = extractElementText(signedXml, "RNCEmisor") ?? extractElementText(signedXml, "RncEmisor") ?? "unknown-rnc";
|
|
1432
|
+
const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "SecuenciaeNCFDesde") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
|
|
1208
1433
|
return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
|
|
1209
1434
|
}
|
|
1210
1435
|
/**
|
|
@@ -1219,10 +1444,7 @@ var ECF = class {
|
|
|
1219
1444
|
if (cached !== void 0) {
|
|
1220
1445
|
if (isPendingSubmission(cached)) {
|
|
1221
1446
|
return (0, import_neverthrow.err)(
|
|
1222
|
-
new DgiiError(
|
|
1223
|
-
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1224
|
-
{ code: "IDEMPOTENCY" }
|
|
1225
|
-
)
|
|
1447
|
+
new DgiiError(PENDING_SUBMISSION_MESSAGE, { code: "IDEMPOTENCY" })
|
|
1226
1448
|
);
|
|
1227
1449
|
}
|
|
1228
1450
|
return (0, import_neverthrow.ok)(cached);
|
|
@@ -1314,6 +1536,90 @@ var ECF = class {
|
|
|
1314
1536
|
this.tokenExpiresAt = parseExpiry(token.value.expira);
|
|
1315
1537
|
return (0, import_neverthrow.ok)(token.value);
|
|
1316
1538
|
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Query the operational status of every DGII service.
|
|
1541
|
+
*
|
|
1542
|
+
* The status host takes an API key instead of a bearer token, so this call
|
|
1543
|
+
* neither needs `authenticate()` nor triggers a token refresh.
|
|
1544
|
+
*/
|
|
1545
|
+
async getServiceStatus() {
|
|
1546
|
+
const result = await this.statusRequest("api/estatusservicios/obtenerestatus" /* Services */);
|
|
1547
|
+
if (result.isErr()) {
|
|
1548
|
+
return (0, import_neverthrow.err)(result.error);
|
|
1549
|
+
}
|
|
1550
|
+
const data = result.value;
|
|
1551
|
+
if (data === void 0 || data === null) {
|
|
1552
|
+
return (0, import_neverthrow.ok)([]);
|
|
1553
|
+
}
|
|
1554
|
+
return (0, import_neverthrow.ok)(Array.isArray(data) ? data : [data]);
|
|
1555
|
+
}
|
|
1556
|
+
/** Scheduled maintenance windows, one entry per environment. */
|
|
1557
|
+
async getMaintenanceWindows() {
|
|
1558
|
+
const result = await this.statusRequest("api/estatusservicios/obtenerventanasmantenimiento" /* MaintenanceWindows */);
|
|
1559
|
+
if (result.isErr()) {
|
|
1560
|
+
return (0, import_neverthrow.err)(result.error);
|
|
1561
|
+
}
|
|
1562
|
+
const data = result.value;
|
|
1563
|
+
if (Array.isArray(data)) {
|
|
1564
|
+
return (0, import_neverthrow.ok)(data);
|
|
1565
|
+
}
|
|
1566
|
+
return (0, import_neverthrow.ok)(data?.ventanaMantenimientos ?? []);
|
|
1567
|
+
}
|
|
1568
|
+
/** Whether an environment is currently inside a maintenance window. */
|
|
1569
|
+
checkServiceAvailability(ambiente) {
|
|
1570
|
+
return this.statusRequest(
|
|
1571
|
+
"api/estatusservicios/verificarestado" /* Check */,
|
|
1572
|
+
{ ambiente }
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
statusRequest(endpoint, query) {
|
|
1576
|
+
const headers = this.statusApiKey === void 0 ? {} : { Authorization: `Apikey ${this.statusApiKey}` };
|
|
1577
|
+
return this.http.request({
|
|
1578
|
+
method: "GET",
|
|
1579
|
+
path: buildStatusUrl(endpoint),
|
|
1580
|
+
absoluteUrl: true,
|
|
1581
|
+
resource: `status:${endpoint}`,
|
|
1582
|
+
headers,
|
|
1583
|
+
...query !== void 0 ? { query } : {},
|
|
1584
|
+
skipAuthHooks: true,
|
|
1585
|
+
omitAuth: true
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Resolve what happened to a submission whose idempotency marker is pending.
|
|
1590
|
+
*
|
|
1591
|
+
* Queries DGII by e-NCF. When DGII has no record of the document the marker
|
|
1592
|
+
* is cleared, because re-submitting cannot double-issue. When DGII does have
|
|
1593
|
+
* a record the marker stays in place — it is the lock that prevents a second
|
|
1594
|
+
* submission of a document that already exists — and `delivered` tells the
|
|
1595
|
+
* caller to stop retrying.
|
|
1596
|
+
*/
|
|
1597
|
+
async reconcileSubmission(signedXml) {
|
|
1598
|
+
const rnc = extractElementText(signedXml, "RNCEmisor") ?? extractElementText(signedXml, "RncEmisor");
|
|
1599
|
+
const encf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "SecuenciaeNCFDesde");
|
|
1600
|
+
if (rnc === void 0 || encf === void 0) {
|
|
1601
|
+
return (0, import_neverthrow.err)(
|
|
1602
|
+
new DgiiError("The document has no emitter RNC or e-NCF to reconcile", {
|
|
1603
|
+
code: "VALIDATION"
|
|
1604
|
+
})
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
const inquiry = await this.inquiryStatus(rnc, encf);
|
|
1608
|
+
if (inquiry.isErr()) {
|
|
1609
|
+
return (0, import_neverthrow.err)(inquiry.error);
|
|
1610
|
+
}
|
|
1611
|
+
const delivered = inquiry.value.estado !== "No encontrado";
|
|
1612
|
+
const cleared = !delivered;
|
|
1613
|
+
if (cleared) {
|
|
1614
|
+
await this.idempotency.delete(this.buildIdempotencyKey(signedXml));
|
|
1615
|
+
}
|
|
1616
|
+
return (0, import_neverthrow.ok)({
|
|
1617
|
+
inquiry: inquiry.value,
|
|
1618
|
+
delivered,
|
|
1619
|
+
canReuseSequence: canReuseSequence(inquiry.value),
|
|
1620
|
+
cleared
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1317
1623
|
getSeed(buyerHost) {
|
|
1318
1624
|
if (buyerHost) {
|
|
1319
1625
|
const url2 = buildSenderReceiverUrl(
|
|
@@ -1346,6 +1652,29 @@ var ECF = class {
|
|
|
1346
1652
|
* a repeated call returns the cached response instead of double-issuing.
|
|
1347
1653
|
*/
|
|
1348
1654
|
async sendElectronicDocument(signedXml, fileName, buyerHost) {
|
|
1655
|
+
if (buyerHost !== void 0 && this.enforceB2BRules) {
|
|
1656
|
+
const applicable = checkB2BApplicability(signedXml);
|
|
1657
|
+
if (applicable.isErr()) {
|
|
1658
|
+
return (0, import_neverthrow.err)(applicable.error);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (this.validateBeforeSend) {
|
|
1662
|
+
const validation = validateDocument(
|
|
1663
|
+
signedXml,
|
|
1664
|
+
this.xmllintBin !== void 0 ? { bin: this.xmllintBin } : {}
|
|
1665
|
+
);
|
|
1666
|
+
if (validation.isErr()) {
|
|
1667
|
+
return (0, import_neverthrow.err)(validation.error);
|
|
1668
|
+
}
|
|
1669
|
+
if (validation.value.length > 0) {
|
|
1670
|
+
return (0, import_neverthrow.err)(
|
|
1671
|
+
new DgiiError(
|
|
1672
|
+
`Document does not match the DGII XML Schema: ${formatViolations(validation.value)}`,
|
|
1673
|
+
{ code: "VALIDATION", data: validation.value }
|
|
1674
|
+
)
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1349
1678
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1350
1679
|
return this.submitIdempotent(key, () => {
|
|
1351
1680
|
const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
|
|
@@ -1433,6 +1762,22 @@ var ECF = class {
|
|
|
1433
1762
|
}
|
|
1434
1763
|
return (0, import_neverthrow.ok)(Array.isArray(data) ? data : [data]);
|
|
1435
1764
|
}
|
|
1765
|
+
/** General directory listing — every receiver that publishes service URLs. */
|
|
1766
|
+
async getDirectoryListing() {
|
|
1767
|
+
const url = buildUrl(this.environment, "consultadirectorio/api/consultas/listado" /* DirectoryList */);
|
|
1768
|
+
const result = await this.http.get(url, void 0, {
|
|
1769
|
+
absoluteUrl: true,
|
|
1770
|
+
resource: "consultadirectorio/api/consultas/listado" /* DirectoryList */
|
|
1771
|
+
});
|
|
1772
|
+
if (result.isErr()) {
|
|
1773
|
+
return (0, import_neverthrow.err)(result.error);
|
|
1774
|
+
}
|
|
1775
|
+
const data = result.value;
|
|
1776
|
+
if (data === void 0 || data === null) {
|
|
1777
|
+
return (0, import_neverthrow.ok)([]);
|
|
1778
|
+
}
|
|
1779
|
+
return (0, import_neverthrow.ok)(Array.isArray(data) ? data : [data]);
|
|
1780
|
+
}
|
|
1436
1781
|
/** RFCE inquiry — DGII production only. */
|
|
1437
1782
|
async getSummaryInvoiceInquiry(rnc, encf, codSeguridad) {
|
|
1438
1783
|
if (this.environment !== "eCF" /* Prod */) {
|
|
@@ -1454,12 +1799,32 @@ var ECF = class {
|
|
|
1454
1799
|
{ absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
|
|
1455
1800
|
);
|
|
1456
1801
|
}
|
|
1457
|
-
|
|
1458
|
-
* Submit a signed commercial approval (ACECF). Idempotent like submissions,
|
|
1459
|
-
* keyed on `RNC + eNCF + SHA-256(signed XML)`.
|
|
1460
|
-
*/
|
|
1461
|
-
sendCommercialApproval(signedXml, fileName) {
|
|
1802
|
+
sendCommercialApproval(signedXml, fileName, buyerHost) {
|
|
1462
1803
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1804
|
+
if (buyerHost !== void 0) {
|
|
1805
|
+
return this.submitIdempotent(
|
|
1806
|
+
key,
|
|
1807
|
+
async () => {
|
|
1808
|
+
const url = buildSenderReceiverUrl(
|
|
1809
|
+
buyerHost,
|
|
1810
|
+
"fe/aprobacioncomercial/api/ecf" /* CommercialApproval */
|
|
1811
|
+
);
|
|
1812
|
+
const result = await this.http.postMultipart(
|
|
1813
|
+
url,
|
|
1814
|
+
fileName,
|
|
1815
|
+
signedXml,
|
|
1816
|
+
{
|
|
1817
|
+
absoluteUrl: true,
|
|
1818
|
+
resource: "fe/aprobacioncomercial/api/ecf" /* CommercialApproval */
|
|
1819
|
+
}
|
|
1820
|
+
);
|
|
1821
|
+
if (result.isErr()) {
|
|
1822
|
+
return result.error.status === 400 ? (0, import_neverthrow.ok)({ status: 400, accepted: false }) : (0, import_neverthrow.err)(result.error);
|
|
1823
|
+
}
|
|
1824
|
+
return (0, import_neverthrow.ok)({ status: 200, accepted: true });
|
|
1825
|
+
}
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1463
1828
|
return this.submitIdempotent(
|
|
1464
1829
|
key,
|
|
1465
1830
|
() => this.http.postMultipart(
|
|
@@ -1501,13 +1866,24 @@ var ECF = class {
|
|
|
1501
1866
|
InMemoryIdempotencyStore,
|
|
1502
1867
|
Ok,
|
|
1503
1868
|
P12Reader,
|
|
1869
|
+
PENDING_SUBMISSION_MESSAGE,
|
|
1504
1870
|
ResultAsync,
|
|
1871
|
+
SCHEMA_DIR,
|
|
1872
|
+
SUMMARY_ACCEPTED,
|
|
1873
|
+
SUMMARY_CONDITIONAL_ACCEPTED,
|
|
1874
|
+
SUMMARY_REJECTED,
|
|
1505
1875
|
SenderReceiverEndpoint,
|
|
1506
1876
|
Signature,
|
|
1877
|
+
StatusEndpoint,
|
|
1878
|
+
StatusEnvironment,
|
|
1507
1879
|
TrackStatusEnum,
|
|
1508
1880
|
Transformer,
|
|
1881
|
+
UNAVAILABLE_SCHEMAS,
|
|
1509
1882
|
buildSenderReceiverUrl,
|
|
1883
|
+
buildStatusUrl,
|
|
1510
1884
|
buildUrl,
|
|
1885
|
+
canReuseSequence,
|
|
1886
|
+
checkB2BApplicability,
|
|
1511
1887
|
compareDecimal,
|
|
1512
1888
|
convertECF32ToRFCE,
|
|
1513
1889
|
createResiliencePolicies,
|
|
@@ -1515,6 +1891,7 @@ var ECF = class {
|
|
|
1515
1891
|
err,
|
|
1516
1892
|
errAsync,
|
|
1517
1893
|
extractDgiiErrorMessage,
|
|
1894
|
+
formatViolations,
|
|
1518
1895
|
fromPromise,
|
|
1519
1896
|
fromSafePromise,
|
|
1520
1897
|
fromThrowable,
|
|
@@ -1529,9 +1906,11 @@ var ECF = class {
|
|
|
1529
1906
|
isRetryableStatus,
|
|
1530
1907
|
ok,
|
|
1531
1908
|
okAsync,
|
|
1909
|
+
pruneEmptyValues,
|
|
1532
1910
|
resolveReceptionRoute,
|
|
1533
1911
|
roundHalfUpToCents,
|
|
1534
1912
|
toDgiiError,
|
|
1913
|
+
validateDocument,
|
|
1535
1914
|
validateXMLCertificate
|
|
1536
1915
|
});
|
|
1537
1916
|
//# sourceMappingURL=index.cjs.map
|