@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.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 { C14nCanonicalization, SignedXml } from "xml-crypto";
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: CUSTOM_DIGEST_URI,
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 encf = extractElementText(signedXml, "eNCF") ?? "unknown-encf";
1038
- return `${rnc}:${encf}:${sha256Hex(signedXml)}`;
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
- const cached = await this.idempotency.get(key);
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
- const result = await this.http.postMultipart(
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
- const cached = await this.idempotency.get(key);
1144
- if (cached !== void 0) {
1145
- if (isPendingSubmission(cached)) {
1146
- return err(
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
- if (result.isOk()) {
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 url = buildUrl(this.environment, "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */);
1232
- return this.http.postMultipart(
1233
- url,
1234
- fileName,
1235
- signedXml,
1236
- { absoluteUrl: true, resource: "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */ }
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 url = buildUrl(this.environment, "anulacionrangos/api/operaciones/anularrango" /* Void */);
1241
- return this.http.postMultipart(url, fileName, signedXml, {
1242
- absoluteUrl: true,
1243
- resource: "anulacionrangos/api/operaciones/anularrango" /* Void */
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 {