@deuriib/dgii-ecf 0.3.2 → 0.4.1
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/index.cjs +69 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +70 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -611,27 +611,11 @@ var P12Reader = class {
|
|
|
611
611
|
// src/signing.ts
|
|
612
612
|
import crypto from "crypto";
|
|
613
613
|
import { DOMParser } from "@xmldom/xmldom";
|
|
614
|
-
import {
|
|
614
|
+
import { SignedXml } from "xml-crypto";
|
|
615
615
|
var C14N_INCLUSIVE = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
|
|
616
616
|
var RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
|
617
617
|
var SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256";
|
|
618
618
|
var ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
|
|
619
|
-
var CUSTOM_DIGEST_URI = "http://dgii.gov.do/digest/sha256-c14n";
|
|
620
|
-
var DgiiDigest = class {
|
|
621
|
-
getHash(xml) {
|
|
622
|
-
const doc = new DOMParser().parseFromString(
|
|
623
|
-
xml
|
|
624
|
-
);
|
|
625
|
-
const canonical = new C14nCanonicalization().process(
|
|
626
|
-
doc,
|
|
627
|
-
{}
|
|
628
|
-
);
|
|
629
|
-
return crypto.createHash("sha256").update(canonical, "utf8").digest("base64");
|
|
630
|
-
}
|
|
631
|
-
getAlgorithmName() {
|
|
632
|
-
return SHA256;
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
619
|
var parseStrict = (xml) => {
|
|
636
620
|
const errors = [];
|
|
637
621
|
const doc = new DOMParser({
|
|
@@ -700,12 +684,10 @@ var Signature = class {
|
|
|
700
684
|
signatureAlgorithm: RSA_SHA256,
|
|
701
685
|
canonicalizationAlgorithm: C14N_INCLUSIVE
|
|
702
686
|
});
|
|
703
|
-
const hashAlgorithms = sig.HashAlgorithms;
|
|
704
|
-
hashAlgorithms[CUSTOM_DIGEST_URI] = DgiiDigest;
|
|
705
687
|
sig.addReference({
|
|
706
688
|
xpath: `//*[local-name(.)='${elementName}']`,
|
|
707
689
|
transforms: [ENVELOPED],
|
|
708
|
-
digestAlgorithm:
|
|
690
|
+
digestAlgorithm: SHA256,
|
|
709
691
|
isEmptyUri: true
|
|
710
692
|
});
|
|
711
693
|
cleanNodes(doc);
|
|
@@ -1034,8 +1016,39 @@ var ECF = class {
|
|
|
1034
1016
|
}
|
|
1035
1017
|
buildIdempotencyKey(signedXml) {
|
|
1036
1018
|
const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
|
|
1037
|
-
const
|
|
1038
|
-
return `${rnc}:${
|
|
1019
|
+
const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
|
|
1020
|
+
return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Run a submission through the persist-before-transmit idempotency flow.
|
|
1024
|
+
*
|
|
1025
|
+
* The pending marker is written first so an interrupted send can be
|
|
1026
|
+
* reconciled instead of re-issued; a successful response replaces it, and
|
|
1027
|
+
* a failure clears it so the caller can retry.
|
|
1028
|
+
*/
|
|
1029
|
+
async submitIdempotent(key, send) {
|
|
1030
|
+
const cached = await this.idempotency.get(key);
|
|
1031
|
+
if (cached !== void 0) {
|
|
1032
|
+
if (isPendingSubmission(cached)) {
|
|
1033
|
+
return err(
|
|
1034
|
+
new DgiiError(
|
|
1035
|
+
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1036
|
+
{ code: "IDEMPOTENCY" }
|
|
1037
|
+
)
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
return ok(cached);
|
|
1041
|
+
}
|
|
1042
|
+
return this.singleFlight(key, async () => {
|
|
1043
|
+
await this.idempotency.set(key, PENDING_SUBMISSION);
|
|
1044
|
+
const result = await send();
|
|
1045
|
+
if (result.isOk()) {
|
|
1046
|
+
await this.idempotency.set(key, result.value);
|
|
1047
|
+
} else {
|
|
1048
|
+
await this.idempotency.delete(key);
|
|
1049
|
+
}
|
|
1050
|
+
return result;
|
|
1051
|
+
});
|
|
1039
1052
|
}
|
|
1040
1053
|
async singleFlight(key, task) {
|
|
1041
1054
|
const existing = this.inFlight.get(key);
|
|
@@ -1108,66 +1121,28 @@ var ECF = class {
|
|
|
1108
1121
|
*/
|
|
1109
1122
|
async sendElectronicDocument(signedXml, fileName, buyerHost) {
|
|
1110
1123
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1111
|
-
|
|
1112
|
-
if (cached !== void 0) {
|
|
1113
|
-
if (isPendingSubmission(cached)) {
|
|
1114
|
-
return err(
|
|
1115
|
-
new DgiiError(
|
|
1116
|
-
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1117
|
-
{ code: "IDEMPOTENCY" }
|
|
1118
|
-
)
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
return ok(cached);
|
|
1122
|
-
}
|
|
1123
|
-
return this.singleFlight(key, async () => {
|
|
1124
|
-
await this.idempotency.set(key, PENDING_SUBMISSION);
|
|
1124
|
+
return this.submitIdempotent(key, () => {
|
|
1125
1125
|
const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
|
|
1126
|
-
|
|
1126
|
+
return this.http.postMultipart(
|
|
1127
1127
|
url,
|
|
1128
1128
|
fileName,
|
|
1129
1129
|
signedXml,
|
|
1130
1130
|
{ absoluteUrl: true, resource: "recepcion/api/FacturasElectronicas" /* SendInvoice */ }
|
|
1131
1131
|
);
|
|
1132
|
-
if (result.isOk()) {
|
|
1133
|
-
await this.idempotency.set(key, result.value);
|
|
1134
|
-
} else {
|
|
1135
|
-
await this.idempotency.delete(key);
|
|
1136
|
-
}
|
|
1137
|
-
return result;
|
|
1138
1132
|
});
|
|
1139
1133
|
}
|
|
1140
1134
|
/** Submit a signed summary invoice (RFCE). Idempotent like submissions. */
|
|
1141
1135
|
async sendSummary(signedXml, fileName) {
|
|
1142
1136
|
const key = this.buildIdempotencyKey(signedXml);
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
new DgiiError(
|
|
1148
|
-
"A submission with this key is already pending; reconcile via a status lookup before retrying",
|
|
1149
|
-
{ code: "IDEMPOTENCY" }
|
|
1150
|
-
)
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
return ok(cached);
|
|
1154
|
-
}
|
|
1155
|
-
return this.singleFlight(key, async () => {
|
|
1156
|
-
await this.idempotency.set(key, PENDING_SUBMISSION);
|
|
1157
|
-
const url = buildUrl(this.environment, "recepcionfc/api/recepcion/ecf" /* SendSummary */, BaseUrl.CF);
|
|
1158
|
-
const result = await this.http.postMultipart(
|
|
1159
|
-
url,
|
|
1137
|
+
return this.submitIdempotent(
|
|
1138
|
+
key,
|
|
1139
|
+
() => this.http.postMultipart(
|
|
1140
|
+
buildUrl(this.environment, "recepcionfc/api/recepcion/ecf" /* SendSummary */, BaseUrl.CF),
|
|
1160
1141
|
fileName,
|
|
1161
1142
|
signedXml,
|
|
1162
1143
|
{ absoluteUrl: true, resource: "recepcionfc/api/recepcion/ecf" /* SendSummary */ }
|
|
1163
|
-
)
|
|
1164
|
-
|
|
1165
|
-
await this.idempotency.set(key, result.value);
|
|
1166
|
-
} else {
|
|
1167
|
-
await this.idempotency.delete(key);
|
|
1168
|
-
}
|
|
1169
|
-
return result;
|
|
1170
|
-
});
|
|
1144
|
+
)
|
|
1145
|
+
);
|
|
1171
1146
|
}
|
|
1172
1147
|
statusTrackId(trackId) {
|
|
1173
1148
|
const url = buildUrl(this.environment, "consultaresultado/api/Consultas/Estado" /* TrackResultStatus */);
|
|
@@ -1227,21 +1202,37 @@ var ECF = class {
|
|
|
1227
1202
|
{ absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
|
|
1228
1203
|
);
|
|
1229
1204
|
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Submit a signed commercial approval (ACECF). Idempotent like submissions,
|
|
1207
|
+
* keyed on `RNC + eNCF + SHA-256(signed XML)`.
|
|
1208
|
+
*/
|
|
1230
1209
|
sendCommercialApproval(signedXml, fileName) {
|
|
1231
|
-
const
|
|
1232
|
-
return this.
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1210
|
+
const key = this.buildIdempotencyKey(signedXml);
|
|
1211
|
+
return this.submitIdempotent(
|
|
1212
|
+
key,
|
|
1213
|
+
() => this.http.postMultipart(
|
|
1214
|
+
buildUrl(this.environment, "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */),
|
|
1215
|
+
fileName,
|
|
1216
|
+
signedXml,
|
|
1217
|
+
{ absoluteUrl: true, resource: "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */ }
|
|
1218
|
+
)
|
|
1237
1219
|
);
|
|
1238
1220
|
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Submit a signed range-voiding request (ANECF). Idempotent like
|
|
1223
|
+
* submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
|
|
1224
|
+
*/
|
|
1239
1225
|
voidENCF(signedXml, fileName) {
|
|
1240
|
-
const
|
|
1241
|
-
return this.
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1226
|
+
const key = this.buildIdempotencyKey(signedXml);
|
|
1227
|
+
return this.submitIdempotent(
|
|
1228
|
+
key,
|
|
1229
|
+
() => this.http.postMultipart(
|
|
1230
|
+
buildUrl(this.environment, "anulacionrangos/api/operaciones/anularrango" /* Void */),
|
|
1231
|
+
fileName,
|
|
1232
|
+
signedXml,
|
|
1233
|
+
{ absoluteUrl: true, resource: "anulacionrangos/api/operaciones/anularrango" /* Void */ }
|
|
1234
|
+
)
|
|
1235
|
+
);
|
|
1245
1236
|
}
|
|
1246
1237
|
};
|
|
1247
1238
|
export {
|