@deuriib/dgii-ecf 0.3.2 → 0.4.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/index.d.cts CHANGED
@@ -399,6 +399,14 @@ declare class ECF {
399
399
  private readonly inFlight;
400
400
  constructor(credentials: P12ReaderData, environment?: Environment, options?: EcfOptions);
401
401
  private buildIdempotencyKey;
402
+ /**
403
+ * Run a submission through the persist-before-transmit idempotency flow.
404
+ *
405
+ * The pending marker is written first so an interrupted send can be
406
+ * reconciled instead of re-issued; a successful response replaces it, and
407
+ * a failure clears it so the caller can retry.
408
+ */
409
+ private submitIdempotent;
402
410
  private singleFlight;
403
411
  /** Authenticate against DGII (or a buyer host) and store the bearer token. */
404
412
  authenticate(buyerHost?: string): Promise<Result<AuthToken, DgiiError>>;
@@ -418,7 +426,15 @@ declare class ECF {
418
426
  getCustomerDirectory(rnc: string): Promise<Result<ServiceDirectoryResponse[] | undefined, DgiiError>>;
419
427
  /** RFCE inquiry — DGII production only. */
420
428
  getSummaryInvoiceInquiry(rnc: string, encf: string, codSeguridad: string): Promise<Result<InquiryInvoiceSummary, DgiiError>>;
429
+ /**
430
+ * Submit a signed commercial approval (ACECF). Idempotent like submissions,
431
+ * keyed on `RNC + eNCF + SHA-256(signed XML)`.
432
+ */
421
433
  sendCommercialApproval(signedXml: string, fileName: string): Promise<Result<CommercialApprovalResponse, DgiiError>>;
434
+ /**
435
+ * Submit a signed range-voiding request (ANECF). Idempotent like
436
+ * submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
437
+ */
422
438
  voidENCF(signedXml: string, fileName: string): Promise<Result<VoidNCFResponse, DgiiError>>;
423
439
  }
424
440
 
package/dist/index.d.ts CHANGED
@@ -399,6 +399,14 @@ declare class ECF {
399
399
  private readonly inFlight;
400
400
  constructor(credentials: P12ReaderData, environment?: Environment, options?: EcfOptions);
401
401
  private buildIdempotencyKey;
402
+ /**
403
+ * Run a submission through the persist-before-transmit idempotency flow.
404
+ *
405
+ * The pending marker is written first so an interrupted send can be
406
+ * reconciled instead of re-issued; a successful response replaces it, and
407
+ * a failure clears it so the caller can retry.
408
+ */
409
+ private submitIdempotent;
402
410
  private singleFlight;
403
411
  /** Authenticate against DGII (or a buyer host) and store the bearer token. */
404
412
  authenticate(buyerHost?: string): Promise<Result<AuthToken, DgiiError>>;
@@ -418,7 +426,15 @@ declare class ECF {
418
426
  getCustomerDirectory(rnc: string): Promise<Result<ServiceDirectoryResponse[] | undefined, DgiiError>>;
419
427
  /** RFCE inquiry — DGII production only. */
420
428
  getSummaryInvoiceInquiry(rnc: string, encf: string, codSeguridad: string): Promise<Result<InquiryInvoiceSummary, DgiiError>>;
429
+ /**
430
+ * Submit a signed commercial approval (ACECF). Idempotent like submissions,
431
+ * keyed on `RNC + eNCF + SHA-256(signed XML)`.
432
+ */
421
433
  sendCommercialApproval(signedXml: string, fileName: string): Promise<Result<CommercialApprovalResponse, DgiiError>>;
434
+ /**
435
+ * Submit a signed range-voiding request (ANECF). Idempotent like
436
+ * submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
437
+ */
422
438
  voidENCF(signedXml: string, fileName: string): Promise<Result<VoidNCFResponse, DgiiError>>;
423
439
  }
424
440
 
package/dist/index.js CHANGED
@@ -1034,8 +1034,39 @@ var ECF = class {
1034
1034
  }
1035
1035
  buildIdempotencyKey(signedXml) {
1036
1036
  const rnc = extractElementText(signedXml, "RNCEmisor") ?? "unknown-rnc";
1037
- const encf = extractElementText(signedXml, "eNCF") ?? "unknown-encf";
1038
- return `${rnc}:${encf}:${sha256Hex(signedXml)}`;
1037
+ const ncf = extractElementText(signedXml, "eNCF") ?? extractElementText(signedXml, "RangoNCF") ?? "unknown-ncf";
1038
+ return `${rnc}:${ncf}:${sha256Hex(signedXml)}`;
1039
+ }
1040
+ /**
1041
+ * Run a submission through the persist-before-transmit idempotency flow.
1042
+ *
1043
+ * The pending marker is written first so an interrupted send can be
1044
+ * reconciled instead of re-issued; a successful response replaces it, and
1045
+ * a failure clears it so the caller can retry.
1046
+ */
1047
+ async submitIdempotent(key, send) {
1048
+ const cached = await this.idempotency.get(key);
1049
+ if (cached !== void 0) {
1050
+ if (isPendingSubmission(cached)) {
1051
+ return err(
1052
+ new DgiiError(
1053
+ "A submission with this key is already pending; reconcile via a status lookup before retrying",
1054
+ { code: "IDEMPOTENCY" }
1055
+ )
1056
+ );
1057
+ }
1058
+ return ok(cached);
1059
+ }
1060
+ return this.singleFlight(key, async () => {
1061
+ await this.idempotency.set(key, PENDING_SUBMISSION);
1062
+ const result = await send();
1063
+ if (result.isOk()) {
1064
+ await this.idempotency.set(key, result.value);
1065
+ } else {
1066
+ await this.idempotency.delete(key);
1067
+ }
1068
+ return result;
1069
+ });
1039
1070
  }
1040
1071
  async singleFlight(key, task) {
1041
1072
  const existing = this.inFlight.get(key);
@@ -1108,66 +1139,28 @@ var ECF = class {
1108
1139
  */
1109
1140
  async sendElectronicDocument(signedXml, fileName, buyerHost) {
1110
1141
  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);
1142
+ return this.submitIdempotent(key, () => {
1125
1143
  const url = buyerHost ? buildSenderReceiverUrl(buyerHost, "fe/recepcion/api/ecf" /* SendInvoice */) : buildUrl(this.environment, "recepcion/api/FacturasElectronicas" /* SendInvoice */);
1126
- const result = await this.http.postMultipart(
1144
+ return this.http.postMultipart(
1127
1145
  url,
1128
1146
  fileName,
1129
1147
  signedXml,
1130
1148
  { absoluteUrl: true, resource: "recepcion/api/FacturasElectronicas" /* SendInvoice */ }
1131
1149
  );
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
1150
  });
1139
1151
  }
1140
1152
  /** Submit a signed summary invoice (RFCE). Idempotent like submissions. */
1141
1153
  async sendSummary(signedXml, fileName) {
1142
1154
  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,
1155
+ return this.submitIdempotent(
1156
+ key,
1157
+ () => this.http.postMultipart(
1158
+ buildUrl(this.environment, "recepcionfc/api/recepcion/ecf" /* SendSummary */, BaseUrl.CF),
1160
1159
  fileName,
1161
1160
  signedXml,
1162
1161
  { 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
- });
1162
+ )
1163
+ );
1171
1164
  }
1172
1165
  statusTrackId(trackId) {
1173
1166
  const url = buildUrl(this.environment, "consultaresultado/api/Consultas/Estado" /* TrackResultStatus */);
@@ -1227,21 +1220,37 @@ var ECF = class {
1227
1220
  { absoluteUrl: true, resource: "consultarfce/api/Consultas/Consulta" /* InquiryInvoiceSummary */ }
1228
1221
  );
1229
1222
  }
1223
+ /**
1224
+ * Submit a signed commercial approval (ACECF). Idempotent like submissions,
1225
+ * keyed on `RNC + eNCF + SHA-256(signed XML)`.
1226
+ */
1230
1227
  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 */ }
1228
+ const key = this.buildIdempotencyKey(signedXml);
1229
+ return this.submitIdempotent(
1230
+ key,
1231
+ () => this.http.postMultipart(
1232
+ buildUrl(this.environment, "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */),
1233
+ fileName,
1234
+ signedXml,
1235
+ { absoluteUrl: true, resource: "aprobacionComercial/api/AprobacionComercial" /* CommercialApproval */ }
1236
+ )
1237
1237
  );
1238
1238
  }
1239
+ /**
1240
+ * Submit a signed range-voiding request (ANECF). Idempotent like
1241
+ * submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
1242
+ */
1239
1243
  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
- });
1244
+ const key = this.buildIdempotencyKey(signedXml);
1245
+ return this.submitIdempotent(
1246
+ key,
1247
+ () => this.http.postMultipart(
1248
+ buildUrl(this.environment, "anulacionrangos/api/operaciones/anularrango" /* Void */),
1249
+ fileName,
1250
+ signedXml,
1251
+ { absoluteUrl: true, resource: "anulacionrangos/api/operaciones/anularrango" /* Void */ }
1252
+ )
1253
+ );
1245
1254
  }
1246
1255
  };
1247
1256
  export {