@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 CHANGED
@@ -668,22 +668,6 @@ var C14N_INCLUSIVE = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
668
668
  var RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
669
669
  var SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256";
670
670
  var ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
671
- var CUSTOM_DIGEST_URI = "http://dgii.gov.do/digest/sha256-c14n";
672
- var DgiiDigest = class {
673
- getHash(xml) {
674
- const doc = new import_xmldom.DOMParser().parseFromString(
675
- xml
676
- );
677
- const canonical = new import_xml_crypto.C14nCanonicalization().process(
678
- doc,
679
- {}
680
- );
681
- return import_node_crypto.default.createHash("sha256").update(canonical, "utf8").digest("base64");
682
- }
683
- getAlgorithmName() {
684
- return SHA256;
685
- }
686
- };
687
671
  var parseStrict = (xml) => {
688
672
  const errors = [];
689
673
  const doc = new import_xmldom.DOMParser({
@@ -752,12 +736,10 @@ var Signature = class {
752
736
  signatureAlgorithm: RSA_SHA256,
753
737
  canonicalizationAlgorithm: C14N_INCLUSIVE
754
738
  });
755
- const hashAlgorithms = sig.HashAlgorithms;
756
- hashAlgorithms[CUSTOM_DIGEST_URI] = DgiiDigest;
757
739
  sig.addReference({
758
740
  xpath: `//*[local-name(.)='${elementName}']`,
759
741
  transforms: [ENVELOPED],
760
- digestAlgorithm: CUSTOM_DIGEST_URI,
742
+ digestAlgorithm: SHA256,
761
743
  isEmptyUri: true
762
744
  });
763
745
  cleanNodes(doc);
@@ -1086,8 +1068,39 @@ var ECF = class {
1086
1068
  }
1087
1069
  buildIdempotencyKey(signedXml) {
1088
1070
  const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
1089
- const encf = extractElementText(signedXml, "eNCF") ?? "unknown-encf";
1090
- return `${rnc}:${encf}:${sha256Hex(signedXml)}`;
1071
+ const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
1072
+ return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
1073
+ }
1074
+ /**
1075
+ * Run a submission through the persist-before-transmit idempotency flow.
1076
+ *
1077
+ * The pending marker is written first so an interrupted send can be
1078
+ * reconciled instead of re-issued; a successful response replaces it, and
1079
+ * a failure clears it so the caller can retry.
1080
+ */
1081
+ async submitIdempotent(key, send) {
1082
+ const cached = await this.idempotency.get(key);
1083
+ if (cached !== void 0) {
1084
+ if (isPendingSubmission(cached)) {
1085
+ return (0, import_neverthrow.err)(
1086
+ new DgiiError(
1087
+ "A submission with this key is already pending; reconcile via a status lookup before retrying",
1088
+ { code: "IDEMPOTENCY" }
1089
+ )
1090
+ );
1091
+ }
1092
+ return (0, import_neverthrow.ok)(cached);
1093
+ }
1094
+ return this.singleFlight(key, async () => {
1095
+ await this.idempotency.set(key, PENDING_SUBMISSION);
1096
+ const result = await send();
1097
+ if (result.isOk()) {
1098
+ await this.idempotency.set(key, result.value);
1099
+ } else {
1100
+ await this.idempotency.delete(key);
1101
+ }
1102
+ return result;
1103
+ });
1091
1104
  }
1092
1105
  async singleFlight(key, task) {
1093
1106
  const existing = this.inFlight.get(key);
@@ -1160,66 +1173,28 @@ var ECF = class {
1160
1173
  */
1161
1174
  async sendElectronicDocument(signedXml, fileName, buyerHost) {
1162
1175
  const key = this.buildIdempotencyKey(signedXml);
1163
- const cached = await this.idempotency.get(key);
1164
- if (cached !== void 0) {
1165
- if (isPendingSubmission(cached)) {
1166
- return (0, import_neverthrow.err)(
1167
- new DgiiError(
1168
- "A submission with this key is already pending; reconcile via a status lookup before retrying",
1169
- { code: "IDEMPOTENCY" }
1170
- )
1171
- );
1172
- }
1173
- return (0, import_neverthrow.ok)(cached);
1174
- }
1175
- return this.singleFlight(key, async () => {
1176
- await this.idempotency.set(key, PENDING_SUBMISSION);
1176
+ return this.submitIdempotent(key, () => {
1177
1177
  const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
1178
- const result = await this.http.postMultipart(
1178
+ return this.http.postMultipart(
1179
1179
  url,
1180
1180
  fileName,
1181
1181
  signedXml,
1182
1182
  { absoluteUrl: true, resource: "recepcion/api/FacturasElectronicas" /* SendInvoice */ }
1183
1183
  );
1184
- if (result.isOk()) {
1185
- await this.idempotency.set(key, result.value);
1186
- } else {
1187
- await this.idempotency.delete(key);
1188
- }
1189
- return result;
1190
1184
  });
1191
1185
  }
1192
1186
  /** Submit a signed summary invoice (RFCE). Idempotent like submissions. */
1193
1187
  async sendSummary(signedXml, fileName) {
1194
1188
  const key = this.buildIdempotencyKey(signedXml);
1195
- const cached = await this.idempotency.get(key);
1196
- if (cached !== void 0) {
1197
- if (isPendingSubmission(cached)) {
1198
- return (0, import_neverthrow.err)(
1199
- new DgiiError(
1200
- "A submission with this key is already pending; reconcile via a status lookup before retrying",
1201
- { code: "IDEMPOTENCY" }
1202
- )
1203
- );
1204
- }
1205
- return (0, import_neverthrow.ok)(cached);
1206
- }
1207
- return this.singleFlight(key, async () => {
1208
- await this.idempotency.set(key, PENDING_SUBMISSION);
1209
- const url = buildUrl(this.environment, "recepcionfc/api/recepcion/ecf" /* SendSummary */, BaseUrl.CF);
1210
- const result = await this.http.postMultipart(
1211
- url,
1189
+ return this.submitIdempotent(
1190
+ key,
1191
+ () => this.http.postMultipart(
1192
+ buildUrl(this.environment, "recepcionfc/api/recepcion/ecf" /* SendSummary */, BaseUrl.CF),
1212
1193
  fileName,
1213
1194
  signedXml,
1214
1195
  { absoluteUrl: true, resource: "recepcionfc/api/recepcion/ecf" /* SendSummary */ }
1215
- );
1216
- if (result.isOk()) {
1217
- await this.idempotency.set(key, result.value);
1218
- } else {
1219
- await this.idempotency.delete(key);
1220
- }
1221
- return result;
1222
- });
1196
+ )
1197
+ );
1223
1198
  }
1224
1199
  statusTrackId(trackId) {
1225
1200
  const url = buildUrl(this.environment, "consultaresultado/api/Consultas/Estado" /* TrackResultStatus */);
@@ -1279,21 +1254,37 @@ var ECF = class {
1279
1254
  { absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
1280
1255
  );
1281
1256
  }
1257
+ /**
1258
+ * Submit a signed commercial approval (ACECF). Idempotent like submissions,
1259
+ * keyed on `RNC + eNCF + SHA-256(signed XML)`.
1260
+ */
1282
1261
  sendCommercialApproval(signedXml, fileName) {
1283
- const url = buildUrl(this.environment, "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */);
1284
- return this.http.postMultipart(
1285
- url,
1286
- fileName,
1287
- signedXml,
1288
- { absoluteUrl: true, resource: "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */ }
1262
+ const key = this.buildIdempotencyKey(signedXml);
1263
+ return this.submitIdempotent(
1264
+ key,
1265
+ () => this.http.postMultipart(
1266
+ buildUrl(this.environment, "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */),
1267
+ fileName,
1268
+ signedXml,
1269
+ { absoluteUrl: true, resource: "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */ }
1270
+ )
1289
1271
  );
1290
1272
  }
1273
+ /**
1274
+ * Submit a signed range-voiding request (ANECF). Idempotent like
1275
+ * submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
1276
+ */
1291
1277
  voidENCF(signedXml, fileName) {
1292
- const url = buildUrl(this.environment, "anulacionrangos/api/operaciones/anularrango" /* Void */);
1293
- return this.http.postMultipart(url, fileName, signedXml, {
1294
- absoluteUrl: true,
1295
- resource: "anulacionrangos/api/operaciones/anularrango" /* Void */
1296
- });
1278
+ const key = this.buildIdempotencyKey(signedXml);
1279
+ return this.submitIdempotent(
1280
+ key,
1281
+ () => this.http.postMultipart(
1282
+ buildUrl(this.environment, "anulacionrangos/api/operaciones/anularrango" /* Void */),
1283
+ fileName,
1284
+ signedXml,
1285
+ { absoluteUrl: true, resource: "anulacionrangos/api/operaciones/anularrango" /* Void */ }
1286
+ )
1287
+ );
1297
1288
  }
1298
1289
  };
1299
1290
  // Annotate the CommonJS export names for ESM import in node: