@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.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]);
|
|
@@ -466,9 +497,9 @@ var HttpClient = class {
|
|
|
466
497
|
}
|
|
467
498
|
return url;
|
|
468
499
|
}
|
|
469
|
-
authHeaders(extra) {
|
|
500
|
+
authHeaders(extra, omitAuth = false) {
|
|
470
501
|
const headers = { Accept: "application/json" };
|
|
471
|
-
if (this.token) {
|
|
502
|
+
if (!omitAuth && this.token) {
|
|
472
503
|
headers.Authorization = `Bearer ${this.token}`;
|
|
473
504
|
}
|
|
474
505
|
return { ...headers, ...extra };
|
|
@@ -521,7 +552,7 @@ var HttpClient = class {
|
|
|
521
552
|
async execute(options) {
|
|
522
553
|
const url = this.buildUrl(options.path, options.absoluteUrl, options.query);
|
|
523
554
|
const method = options.method ?? "GET";
|
|
524
|
-
const headers = this.authHeaders(options.headers);
|
|
555
|
+
const headers = this.authHeaders(options.headers, options.omitAuth);
|
|
525
556
|
const ctx = {
|
|
526
557
|
method,
|
|
527
558
|
resource: options.resource ?? options.path,
|
|
@@ -843,18 +874,43 @@ var roundHalfUpToCents = (value) => {
|
|
|
843
874
|
const padded = rounded.padStart(3, "0");
|
|
844
875
|
return `${sign}${padded.slice(0, -2)}.${padded.slice(-2)}`;
|
|
845
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
|
+
};
|
|
846
902
|
var Transformer = class {
|
|
847
903
|
json2xml(dataObject, round = false) {
|
|
848
904
|
try {
|
|
905
|
+
const pruned = pruneEmptyValues(dataObject);
|
|
849
906
|
const data = {
|
|
850
907
|
_declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
|
|
851
|
-
...
|
|
908
|
+
...pruned !== null && pruned !== void 0 && typeof pruned === "object" && !Array.isArray(pruned) ? pruned : {}
|
|
852
909
|
};
|
|
853
910
|
const xml = import_xml_js.default.js2xml(data, {
|
|
854
911
|
compact: true,
|
|
855
912
|
ignoreComment: true,
|
|
856
913
|
spaces: 2,
|
|
857
|
-
fullTagEmptyElement: true,
|
|
858
914
|
textFn: (value, label) => {
|
|
859
915
|
if (round && label.toLowerCase() !== "version") {
|
|
860
916
|
return roundHalfUpToCents(value);
|
|
@@ -1005,6 +1061,191 @@ var resolveReceptionRoute = (signedXml) => {
|
|
|
1005
1061
|
)
|
|
1006
1062
|
) : (0, import_neverthrow.ok)(GENERAL);
|
|
1007
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
|
+
};
|
|
1008
1249
|
|
|
1009
1250
|
// src/utils.ts
|
|
1010
1251
|
var getCodeSixDigitfromSignature = (signedXml) => {
|
|
@@ -1064,31 +1305,6 @@ var toArray = (value) => {
|
|
|
1064
1305
|
}
|
|
1065
1306
|
return Array.isArray(value) ? value : [value];
|
|
1066
1307
|
};
|
|
1067
|
-
var isEmptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
1068
|
-
var removeEmptyValues = (input) => {
|
|
1069
|
-
if (input === null || input === void 0 || input === "") {
|
|
1070
|
-
return void 0;
|
|
1071
|
-
}
|
|
1072
|
-
if (Array.isArray(input)) {
|
|
1073
|
-
const filtered = input.map(removeEmptyValues).filter(
|
|
1074
|
-
(item) => item !== null && item !== void 0 && item !== "" && !isEmptyObject(item)
|
|
1075
|
-
);
|
|
1076
|
-
return filtered.length ? filtered : void 0;
|
|
1077
|
-
}
|
|
1078
|
-
if (typeof input === "object") {
|
|
1079
|
-
const output = {};
|
|
1080
|
-
for (const [key, value] of Object.entries(
|
|
1081
|
-
input
|
|
1082
|
-
)) {
|
|
1083
|
-
const cleaned = removeEmptyValues(value);
|
|
1084
|
-
if (cleaned !== void 0 && cleaned !== "" && !isEmptyObject(cleaned)) {
|
|
1085
|
-
output[key] = cleaned;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
return Object.keys(output).length ? output : void 0;
|
|
1089
|
-
}
|
|
1090
|
-
return input;
|
|
1091
|
-
};
|
|
1092
1308
|
var convertECF32ToRFCE = (ecf32Xml) => {
|
|
1093
1309
|
const securityCode = getCodeSixDigitfromSignature(ecf32Xml);
|
|
1094
1310
|
if (securityCode.isErr()) {
|
|
@@ -1103,7 +1319,7 @@ var convertECF32ToRFCE = (ecf32Xml) => {
|
|
|
1103
1319
|
const impuestos = toArray(
|
|
1104
1320
|
encabezado?.Totales?.ImpuestosAdicionales?.ImpuestoAdicional
|
|
1105
1321
|
);
|
|
1106
|
-
const rfce =
|
|
1322
|
+
const rfce = pruneEmptyValues({
|
|
1107
1323
|
RFCE: {
|
|
1108
1324
|
Encabezado: {
|
|
1109
1325
|
Version: encabezado?.Version,
|
|
@@ -1183,6 +1399,10 @@ var ECF = class {
|
|
|
1183
1399
|
inFlight = /* @__PURE__ */ new Map();
|
|
1184
1400
|
tokenRefreshSkewMs;
|
|
1185
1401
|
now;
|
|
1402
|
+
statusApiKey;
|
|
1403
|
+
enforceB2BRules;
|
|
1404
|
+
validateBeforeSend;
|
|
1405
|
+
xmllintBin;
|
|
1186
1406
|
tokenExpiresAt;
|
|
1187
1407
|
refreshPromise;
|
|
1188
1408
|
constructor(credentials, environment = "TesteCF" /* Dev */, options = {}) {
|
|
@@ -1190,6 +1410,10 @@ var ECF = class {
|
|
|
1190
1410
|
this.environment = environment;
|
|
1191
1411
|
this.tokenRefreshSkewMs = options.tokenRefreshSkewMs ?? 5 * 60 * 1e3;
|
|
1192
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;
|
|
1193
1417
|
this.idempotency = options.idempotencyStore ?? new InMemoryIdempotencyStore();
|
|
1194
1418
|
this.http = new HttpClient({
|
|
1195
1419
|
baseUrl: BaseUrl.ECF,
|
|
@@ -1204,8 +1428,8 @@ var ECF = class {
|
|
|
1204
1428
|
});
|
|
1205
1429
|
}
|
|
1206
1430
|
buildIdempotencyKey(signedXml) {
|
|
1207
|
-
const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
|
|
1208
|
-
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";
|
|
1209
1433
|
return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
|
|
1210
1434
|
}
|
|
1211
1435
|
/**
|
|
@@ -1220,10 +1444,7 @@ var ECF = class {
|
|
|
1220
1444
|
if (cached !== void 0) {
|
|
1221
1445
|
if (isPendingSubmission(cached)) {
|
|
1222
1446
|
return (0, import_neverthrow.err)(
|
|
1223
|
-
new DgiiError(
|
|
1224
|
-
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1225
|
-
{ code: "IDEMPOTENCY" }
|
|
1226
|
-
)
|
|
1447
|
+
new DgiiError(PENDING_SUBMISSION_MESSAGE, { code: "IDEMPOTENCY" })
|
|
1227
1448
|
);
|
|
1228
1449
|
}
|
|
1229
1450
|
return (0, import_neverthrow.ok)(cached);
|
|
@@ -1315,6 +1536,90 @@ var ECF = class {
|
|
|
1315
1536
|
this.tokenExpiresAt = parseExpiry(token.value.expira);
|
|
1316
1537
|
return (0, import_neverthrow.ok)(token.value);
|
|
1317
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
|
+
}
|
|
1318
1623
|
getSeed(buyerHost) {
|
|
1319
1624
|
if (buyerHost) {
|
|
1320
1625
|
const url2 = buildSenderReceiverUrl(
|
|
@@ -1347,6 +1652,29 @@ var ECF = class {
|
|
|
1347
1652
|
* a repeated call returns the cached response instead of double-issuing.
|
|
1348
1653
|
*/
|
|
1349
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
|
+
}
|
|
1350
1678
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1351
1679
|
return this.submitIdempotent(key, () => {
|
|
1352
1680
|
const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
|
|
@@ -1434,6 +1762,22 @@ var ECF = class {
|
|
|
1434
1762
|
}
|
|
1435
1763
|
return (0, import_neverthrow.ok)(Array.isArray(data) ? data : [data]);
|
|
1436
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
|
+
}
|
|
1437
1781
|
/** RFCE inquiry — DGII production only. */
|
|
1438
1782
|
async getSummaryInvoiceInquiry(rnc, encf, codSeguridad) {
|
|
1439
1783
|
if (this.environment !== "eCF" /* Prod */) {
|
|
@@ -1455,12 +1799,32 @@ var ECF = class {
|
|
|
1455
1799
|
{ absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
|
|
1456
1800
|
);
|
|
1457
1801
|
}
|
|
1458
|
-
|
|
1459
|
-
* Submit a signed commercial approval (ACECF). Idempotent like submissions,
|
|
1460
|
-
* keyed on `RNC + eNCF + SHA-256(signed XML)`.
|
|
1461
|
-
*/
|
|
1462
|
-
sendCommercialApproval(signedXml, fileName) {
|
|
1802
|
+
sendCommercialApproval(signedXml, fileName, buyerHost) {
|
|
1463
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
|
+
}
|
|
1464
1828
|
return this.submitIdempotent(
|
|
1465
1829
|
key,
|
|
1466
1830
|
() => this.http.postMultipart(
|
|
@@ -1502,13 +1866,24 @@ var ECF = class {
|
|
|
1502
1866
|
InMemoryIdempotencyStore,
|
|
1503
1867
|
Ok,
|
|
1504
1868
|
P12Reader,
|
|
1869
|
+
PENDING_SUBMISSION_MESSAGE,
|
|
1505
1870
|
ResultAsync,
|
|
1871
|
+
SCHEMA_DIR,
|
|
1872
|
+
SUMMARY_ACCEPTED,
|
|
1873
|
+
SUMMARY_CONDITIONAL_ACCEPTED,
|
|
1874
|
+
SUMMARY_REJECTED,
|
|
1506
1875
|
SenderReceiverEndpoint,
|
|
1507
1876
|
Signature,
|
|
1877
|
+
StatusEndpoint,
|
|
1878
|
+
StatusEnvironment,
|
|
1508
1879
|
TrackStatusEnum,
|
|
1509
1880
|
Transformer,
|
|
1881
|
+
UNAVAILABLE_SCHEMAS,
|
|
1510
1882
|
buildSenderReceiverUrl,
|
|
1883
|
+
buildStatusUrl,
|
|
1511
1884
|
buildUrl,
|
|
1885
|
+
canReuseSequence,
|
|
1886
|
+
checkB2BApplicability,
|
|
1512
1887
|
compareDecimal,
|
|
1513
1888
|
convertECF32ToRFCE,
|
|
1514
1889
|
createResiliencePolicies,
|
|
@@ -1516,6 +1891,7 @@ var ECF = class {
|
|
|
1516
1891
|
err,
|
|
1517
1892
|
errAsync,
|
|
1518
1893
|
extractDgiiErrorMessage,
|
|
1894
|
+
formatViolations,
|
|
1519
1895
|
fromPromise,
|
|
1520
1896
|
fromSafePromise,
|
|
1521
1897
|
fromThrowable,
|
|
@@ -1530,9 +1906,11 @@ var ECF = class {
|
|
|
1530
1906
|
isRetryableStatus,
|
|
1531
1907
|
ok,
|
|
1532
1908
|
okAsync,
|
|
1909
|
+
pruneEmptyValues,
|
|
1533
1910
|
resolveReceptionRoute,
|
|
1534
1911
|
roundHalfUpToCents,
|
|
1535
1912
|
toDgiiError,
|
|
1913
|
+
validateDocument,
|
|
1536
1914
|
validateXMLCertificate
|
|
1537
1915
|
});
|
|
1538
1916
|
//# sourceMappingURL=index.cjs.map
|