@deuriib/dgii-ecf 0.4.1 → 0.5.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.d.cts CHANGED
@@ -46,7 +46,11 @@ declare const buildSenderReceiverUrl: (host: string, endpoint: SenderReceiverEnd
46
46
 
47
47
  interface Mensaje {
48
48
  valor: string;
49
- codigo: number;
49
+ /**
50
+ * DGII message code. Numeric on the status endpoints, a string on the RFCE
51
+ * response, so both shapes are preserved instead of coerced.
52
+ */
53
+ codigo: number | string;
50
54
  }
51
55
  interface AuthToken {
52
56
  token: string;
@@ -259,6 +263,10 @@ interface HttpClientOptions {
259
263
  policies?: ResiliencePolicies;
260
264
  timeoutMs?: number;
261
265
  fetchImpl?: typeof fetch;
266
+ /** Runs before every authenticated request; may refresh credentials. */
267
+ beforeRequest?: () => Promise<void>;
268
+ /** Runs on a 401; return `true` to replay the request once. */
269
+ onUnauthorized?: () => Promise<boolean>;
262
270
  }
263
271
  interface RequestOptions {
264
272
  method?: 'GET' | 'POST';
@@ -269,6 +277,8 @@ interface RequestOptions {
269
277
  body?: BodyInit;
270
278
  query?: Record<string, string | number | undefined>;
271
279
  resource?: string;
280
+ /** Skip the auth hooks (used by the authentication round-trip itself). */
281
+ skipAuthHooks?: boolean;
272
282
  }
273
283
  /** Thin HTTP client over the native `fetch`, wrapped in resilience policies. */
274
284
  declare class HttpClient {
@@ -276,17 +286,22 @@ declare class HttpClient {
276
286
  private readonly policies;
277
287
  private readonly fetchImpl;
278
288
  private readonly baseUrl;
289
+ private readonly beforeRequest;
290
+ private readonly onUnauthorized;
279
291
  constructor(options: HttpClientOptions);
280
292
  setToken(token: string): void;
293
+ getToken(): string | undefined;
281
294
  clearToken(): void;
282
295
  private buildUrl;
283
296
  private authHeaders;
284
297
  private readBody;
285
298
  private parseResponse;
286
299
  request<T>(options: RequestOptions): Promise<Result<T, DgiiError>>;
300
+ private execute;
287
301
  get<T>(path: string, query?: RequestOptions['query'], opts?: {
288
302
  absoluteUrl?: boolean;
289
303
  resource?: string;
304
+ skipAuthHooks?: boolean;
290
305
  }): Promise<Result<T, DgiiError>>;
291
306
  /**
292
307
  * POST a signed XML as a multipart `xml` file part. The runtime computes
@@ -295,6 +310,7 @@ declare class HttpClient {
295
310
  postMultipart<T>(path: string, fileName: string, xml: string, opts?: {
296
311
  absoluteUrl?: boolean;
297
312
  resource?: string;
313
+ skipAuthHooks?: boolean;
298
314
  }): Promise<Result<T, DgiiError>>;
299
315
  }
300
316
 
@@ -364,6 +380,45 @@ declare class Transformer {
364
380
  xml2json<T>(xml: string): Result<T, DgiiError>;
365
381
  }
366
382
 
383
+ /** Every e-CF type code defined by DGII. */
384
+ declare const ECF_TYPE_CODES: readonly ["31", "32", "33", "34", "41", "43", "44", "45", "46", "47"];
385
+ type EcfTypeCode = (typeof ECF_TYPE_CODES)[number];
386
+ /**
387
+ * Consumption invoices (E32) at or above this amount go to the general
388
+ * reception endpoint; below it they must be sent as an RFCE summary.
389
+ */
390
+ declare const CONSUMPTION_SUMMARY_THRESHOLD = "250000";
391
+ interface GeneralReceptionRoute {
392
+ readonly kind: 'general';
393
+ readonly endpoint: Endpoint.SendInvoice;
394
+ }
395
+ interface SummaryReceptionRoute {
396
+ readonly kind: 'summary';
397
+ readonly endpoint: Endpoint.SendSummary;
398
+ }
399
+ type ReceptionRoute = GeneralReceptionRoute | SummaryReceptionRoute;
400
+ declare const isEcfTypeCode: (value: string) => value is EcfTypeCode;
401
+ /**
402
+ * Compare two non-negative decimal strings without going through `number`, so
403
+ * a money amount never picks up binary floating-point error.
404
+ *
405
+ * Returns a negative number when `a < b`, zero when equal, positive otherwise.
406
+ */
407
+ declare const compareDecimal: (a: string, b: string) => number;
408
+ /**
409
+ * Decide which DGII reception endpoint a signed document belongs to.
410
+ *
411
+ * - `<RFCE>` → the consumption-summary reception (`recepcionfc`).
412
+ * - `<ECF>` with any type other than E32 → the general reception.
413
+ * - `<ECF>` type E32 at or above RD$250,000 → the general reception.
414
+ * - `<ECF>` type E32 below RD$250,000 → rejected: DGII only accepts the
415
+ * summary form for those, so the caller must run `convertECF32ToRFCE` first.
416
+ *
417
+ * The decision reads `TipoeCF` and `MontoTotal` from the signed document, so it
418
+ * cannot drift from what is actually being transmitted.
419
+ */
420
+ declare const resolveReceptionRoute: (signedXml: string) => Result<ReceptionRoute, DgiiError>;
421
+
367
422
  /** First 6 chars of the `<SignatureValue>` (DGII security code). */
368
423
  declare const getCodeSixDigitfromSignature: (signedXml: string) => Result<string, DgiiError>;
369
424
  /** QR URL for a factura de consumo (FC). */
@@ -394,6 +449,13 @@ interface EcfOptions {
394
449
  timeoutMs?: number;
395
450
  /** Injectable fetch (useful for tests). */
396
451
  fetchImpl?: typeof fetch;
452
+ /**
453
+ * Refresh the token when its remaining lifetime falls below this window.
454
+ * Defaults to 5 minutes.
455
+ */
456
+ tokenRefreshSkewMs?: number;
457
+ /** Injectable clock (epoch milliseconds); useful for tests. */
458
+ now?: () => number;
397
459
  }
398
460
  /**
399
461
  * DGII e-CF client. Every method returns a `Result` — no thrown domain errors.
@@ -404,6 +466,10 @@ declare class ECF {
404
466
  private readonly idempotency;
405
467
  private readonly credentials;
406
468
  private readonly inFlight;
469
+ private readonly tokenRefreshSkewMs;
470
+ private readonly now;
471
+ private tokenExpiresAt;
472
+ private refreshPromise;
407
473
  constructor(credentials: P12ReaderData, environment?: Environment, options?: EcfOptions);
408
474
  private buildIdempotencyKey;
409
475
  /**
@@ -415,6 +481,17 @@ declare class ECF {
415
481
  */
416
482
  private submitIdempotent;
417
483
  private singleFlight;
484
+ /**
485
+ * Refresh the token when it is missing or about to expire.
486
+ *
487
+ * A pre-supplied `token` has no known expiry, so it is never refreshed
488
+ * proactively — only a 401 triggers a re-authentication for it.
489
+ */
490
+ private ensureToken;
491
+ /** Authenticate, coalescing concurrent refreshes into a single round-trip. */
492
+ private refreshToken;
493
+ /** Drop the rejected token and authenticate once. Returns `true` on success. */
494
+ private reauthenticate;
418
495
  /** Authenticate against DGII (or a buyer host) and store the bearer token. */
419
496
  authenticate(buyerHost?: string): Promise<Result<AuthToken, DgiiError>>;
420
497
  private getSeed;
@@ -426,6 +503,15 @@ declare class ECF {
426
503
  sendElectronicDocument(signedXml: string, fileName: string, buyerHost?: string): Promise<Result<InvoiceResponse, DgiiError>>;
427
504
  /** Submit a signed summary invoice (RFCE). Idempotent like submissions. */
428
505
  sendSummary(signedXml: string, fileName: string): Promise<Result<InvoiceSummaryResponse, DgiiError>>;
506
+ /**
507
+ * Submit a signed document to the endpoint its own content belongs to.
508
+ *
509
+ * Reads the root element, `TipoeCF` and `MontoTotal` from the signed XML and
510
+ * dispatches to {@link sendSummary} for an RFCE or to
511
+ * {@link sendElectronicDocument} for an ECF. An E32 below RD$250,000 is
512
+ * rejected because DGII only accepts the summary form for those.
513
+ */
514
+ sendDocument(signedXml: string, fileName: string, buyerHost?: string): Promise<Result<InvoiceResponse | InvoiceSummaryResponse, DgiiError>>;
429
515
  statusTrackId(trackId: string): Promise<Result<TrackingStatusResponse, DgiiError>>;
430
516
  inquiryStatus(rncEmisor: string, ncfElectronico: string, rncComprador?: string, codigoSeguridad?: string): Promise<Result<InquiryStatusResponse, DgiiError>>;
431
517
  trackStatuses(rncEmisor: string, encf: string): Promise<Result<SummaryTrackingStatusResponse[], DgiiError>>;
@@ -445,4 +531,4 @@ declare class ECF {
445
531
  voidENCF(signedXml: string, fileName: string): Promise<Result<VoidNCFResponse, DgiiError>>;
446
532
  }
447
533
 
448
- export { type AuthToken, BaseUrl, type BaseUrlKey, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, type EcfOptions, Endpoint, Environment, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type Mensaje, P12Reader, type P12ReaderData, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SenderReceiverEndpoint, type ServiceDirectoryResponse, Signature, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildUrl, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isRetryableError, isRetryableStatus, roundHalfUpToCents, toDgiiError, validateXMLCertificate };
534
+ export { type AuthToken, BaseUrl, type BaseUrlKey, CONSUMPTION_SUMMARY_THRESHOLD, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, ECF_TYPE_CODES, type EcfOptions, type EcfTypeCode, Endpoint, Environment, type GeneralReceptionRoute, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type Mensaje, P12Reader, type P12ReaderData, type ReceptionRoute, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SenderReceiverEndpoint, type ServiceDirectoryResponse, Signature, type SummaryReceptionRoute, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildUrl, compareDecimal, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isEcfTypeCode, isRetryableError, isRetryableStatus, resolveReceptionRoute, roundHalfUpToCents, toDgiiError, validateXMLCertificate };
package/dist/index.d.ts CHANGED
@@ -46,7 +46,11 @@ declare const buildSenderReceiverUrl: (host: string, endpoint: SenderReceiverEnd
46
46
 
47
47
  interface Mensaje {
48
48
  valor: string;
49
- codigo: number;
49
+ /**
50
+ * DGII message code. Numeric on the status endpoints, a string on the RFCE
51
+ * response, so both shapes are preserved instead of coerced.
52
+ */
53
+ codigo: number | string;
50
54
  }
51
55
  interface AuthToken {
52
56
  token: string;
@@ -259,6 +263,10 @@ interface HttpClientOptions {
259
263
  policies?: ResiliencePolicies;
260
264
  timeoutMs?: number;
261
265
  fetchImpl?: typeof fetch;
266
+ /** Runs before every authenticated request; may refresh credentials. */
267
+ beforeRequest?: () => Promise<void>;
268
+ /** Runs on a 401; return `true` to replay the request once. */
269
+ onUnauthorized?: () => Promise<boolean>;
262
270
  }
263
271
  interface RequestOptions {
264
272
  method?: 'GET' | 'POST';
@@ -269,6 +277,8 @@ interface RequestOptions {
269
277
  body?: BodyInit;
270
278
  query?: Record<string, string | number | undefined>;
271
279
  resource?: string;
280
+ /** Skip the auth hooks (used by the authentication round-trip itself). */
281
+ skipAuthHooks?: boolean;
272
282
  }
273
283
  /** Thin HTTP client over the native `fetch`, wrapped in resilience policies. */
274
284
  declare class HttpClient {
@@ -276,17 +286,22 @@ declare class HttpClient {
276
286
  private readonly policies;
277
287
  private readonly fetchImpl;
278
288
  private readonly baseUrl;
289
+ private readonly beforeRequest;
290
+ private readonly onUnauthorized;
279
291
  constructor(options: HttpClientOptions);
280
292
  setToken(token: string): void;
293
+ getToken(): string | undefined;
281
294
  clearToken(): void;
282
295
  private buildUrl;
283
296
  private authHeaders;
284
297
  private readBody;
285
298
  private parseResponse;
286
299
  request<T>(options: RequestOptions): Promise<Result<T, DgiiError>>;
300
+ private execute;
287
301
  get<T>(path: string, query?: RequestOptions['query'], opts?: {
288
302
  absoluteUrl?: boolean;
289
303
  resource?: string;
304
+ skipAuthHooks?: boolean;
290
305
  }): Promise<Result<T, DgiiError>>;
291
306
  /**
292
307
  * POST a signed XML as a multipart `xml` file part. The runtime computes
@@ -295,6 +310,7 @@ declare class HttpClient {
295
310
  postMultipart<T>(path: string, fileName: string, xml: string, opts?: {
296
311
  absoluteUrl?: boolean;
297
312
  resource?: string;
313
+ skipAuthHooks?: boolean;
298
314
  }): Promise<Result<T, DgiiError>>;
299
315
  }
300
316
 
@@ -364,6 +380,45 @@ declare class Transformer {
364
380
  xml2json<T>(xml: string): Result<T, DgiiError>;
365
381
  }
366
382
 
383
+ /** Every e-CF type code defined by DGII. */
384
+ declare const ECF_TYPE_CODES: readonly ["31", "32", "33", "34", "41", "43", "44", "45", "46", "47"];
385
+ type EcfTypeCode = (typeof ECF_TYPE_CODES)[number];
386
+ /**
387
+ * Consumption invoices (E32) at or above this amount go to the general
388
+ * reception endpoint; below it they must be sent as an RFCE summary.
389
+ */
390
+ declare const CONSUMPTION_SUMMARY_THRESHOLD = "250000";
391
+ interface GeneralReceptionRoute {
392
+ readonly kind: 'general';
393
+ readonly endpoint: Endpoint.SendInvoice;
394
+ }
395
+ interface SummaryReceptionRoute {
396
+ readonly kind: 'summary';
397
+ readonly endpoint: Endpoint.SendSummary;
398
+ }
399
+ type ReceptionRoute = GeneralReceptionRoute | SummaryReceptionRoute;
400
+ declare const isEcfTypeCode: (value: string) => value is EcfTypeCode;
401
+ /**
402
+ * Compare two non-negative decimal strings without going through `number`, so
403
+ * a money amount never picks up binary floating-point error.
404
+ *
405
+ * Returns a negative number when `a < b`, zero when equal, positive otherwise.
406
+ */
407
+ declare const compareDecimal: (a: string, b: string) => number;
408
+ /**
409
+ * Decide which DGII reception endpoint a signed document belongs to.
410
+ *
411
+ * - `<RFCE>` → the consumption-summary reception (`recepcionfc`).
412
+ * - `<ECF>` with any type other than E32 → the general reception.
413
+ * - `<ECF>` type E32 at or above RD$250,000 → the general reception.
414
+ * - `<ECF>` type E32 below RD$250,000 → rejected: DGII only accepts the
415
+ * summary form for those, so the caller must run `convertECF32ToRFCE` first.
416
+ *
417
+ * The decision reads `TipoeCF` and `MontoTotal` from the signed document, so it
418
+ * cannot drift from what is actually being transmitted.
419
+ */
420
+ declare const resolveReceptionRoute: (signedXml: string) => Result<ReceptionRoute, DgiiError>;
421
+
367
422
  /** First 6 chars of the `<SignatureValue>` (DGII security code). */
368
423
  declare const getCodeSixDigitfromSignature: (signedXml: string) => Result<string, DgiiError>;
369
424
  /** QR URL for a factura de consumo (FC). */
@@ -394,6 +449,13 @@ interface EcfOptions {
394
449
  timeoutMs?: number;
395
450
  /** Injectable fetch (useful for tests). */
396
451
  fetchImpl?: typeof fetch;
452
+ /**
453
+ * Refresh the token when its remaining lifetime falls below this window.
454
+ * Defaults to 5 minutes.
455
+ */
456
+ tokenRefreshSkewMs?: number;
457
+ /** Injectable clock (epoch milliseconds); useful for tests. */
458
+ now?: () => number;
397
459
  }
398
460
  /**
399
461
  * DGII e-CF client. Every method returns a `Result` — no thrown domain errors.
@@ -404,6 +466,10 @@ declare class ECF {
404
466
  private readonly idempotency;
405
467
  private readonly credentials;
406
468
  private readonly inFlight;
469
+ private readonly tokenRefreshSkewMs;
470
+ private readonly now;
471
+ private tokenExpiresAt;
472
+ private refreshPromise;
407
473
  constructor(credentials: P12ReaderData, environment?: Environment, options?: EcfOptions);
408
474
  private buildIdempotencyKey;
409
475
  /**
@@ -415,6 +481,17 @@ declare class ECF {
415
481
  */
416
482
  private submitIdempotent;
417
483
  private singleFlight;
484
+ /**
485
+ * Refresh the token when it is missing or about to expire.
486
+ *
487
+ * A pre-supplied `token` has no known expiry, so it is never refreshed
488
+ * proactively — only a 401 triggers a re-authentication for it.
489
+ */
490
+ private ensureToken;
491
+ /** Authenticate, coalescing concurrent refreshes into a single round-trip. */
492
+ private refreshToken;
493
+ /** Drop the rejected token and authenticate once. Returns `true` on success. */
494
+ private reauthenticate;
418
495
  /** Authenticate against DGII (or a buyer host) and store the bearer token. */
419
496
  authenticate(buyerHost?: string): Promise<Result<AuthToken, DgiiError>>;
420
497
  private getSeed;
@@ -426,6 +503,15 @@ declare class ECF {
426
503
  sendElectronicDocument(signedXml: string, fileName: string, buyerHost?: string): Promise<Result<InvoiceResponse, DgiiError>>;
427
504
  /** Submit a signed summary invoice (RFCE). Idempotent like submissions. */
428
505
  sendSummary(signedXml: string, fileName: string): Promise<Result<InvoiceSummaryResponse, DgiiError>>;
506
+ /**
507
+ * Submit a signed document to the endpoint its own content belongs to.
508
+ *
509
+ * Reads the root element, `TipoeCF` and `MontoTotal` from the signed XML and
510
+ * dispatches to {@link sendSummary} for an RFCE or to
511
+ * {@link sendElectronicDocument} for an ECF. An E32 below RD$250,000 is
512
+ * rejected because DGII only accepts the summary form for those.
513
+ */
514
+ sendDocument(signedXml: string, fileName: string, buyerHost?: string): Promise<Result<InvoiceResponse | InvoiceSummaryResponse, DgiiError>>;
429
515
  statusTrackId(trackId: string): Promise<Result<TrackingStatusResponse, DgiiError>>;
430
516
  inquiryStatus(rncEmisor: string, ncfElectronico: string, rncComprador?: string, codigoSeguridad?: string): Promise<Result<InquiryStatusResponse, DgiiError>>;
431
517
  trackStatuses(rncEmisor: string, encf: string): Promise<Result<SummaryTrackingStatusResponse[], DgiiError>>;
@@ -445,4 +531,4 @@ declare class ECF {
445
531
  voidENCF(signedXml: string, fileName: string): Promise<Result<VoidNCFResponse, DgiiError>>;
446
532
  }
447
533
 
448
- export { type AuthToken, BaseUrl, type BaseUrlKey, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, type EcfOptions, Endpoint, Environment, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type Mensaje, P12Reader, type P12ReaderData, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SenderReceiverEndpoint, type ServiceDirectoryResponse, Signature, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildUrl, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isRetryableError, isRetryableStatus, roundHalfUpToCents, toDgiiError, validateXMLCertificate };
534
+ export { type AuthToken, BaseUrl, type BaseUrlKey, CONSUMPTION_SUMMARY_THRESHOLD, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, ECF_TYPE_CODES, type EcfOptions, type EcfTypeCode, Endpoint, Environment, type GeneralReceptionRoute, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type Mensaje, P12Reader, type P12ReaderData, type ReceptionRoute, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SenderReceiverEndpoint, type ServiceDirectoryResponse, Signature, type SummaryReceptionRoute, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildUrl, compareDecimal, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isEcfTypeCode, isRetryableError, isRetryableStatus, resolveReceptionRoute, roundHalfUpToCents, toDgiiError, validateXMLCertificate };