UNPKG

@nodecfdi/sat-ws-descarga-masiva

Version:

Librería para usar el servicio web del SAT de Descarga Masiva

1,590 lines (1,548 loc) 82.5 kB
// src/internal/helpers.ts var Helpers = class { static nospaces(input) { return input.replaceAll(/^\s*/gm, "").replaceAll(/\s*\n/g, "").replaceAll("?><", "?>\n<"); } static cleanPemContents(pemContents) { const filteredLines = pemContents.split("\n").filter((line) => !line.startsWith("-----")); return filteredLines.map((line) => line.trim()).join(""); } static htmlspecialchars(stringToReplace) { return stringToReplace.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#039;"); } }; // src/internal/interacts_xml_trait.ts import { getParser } from "@nodecfdi/cfdi-core"; var InteractsXmlTrait = class { readXmlDocument(source) { if (source === "") { throw new Error("Cannot load an xml with empty content"); } return getParser().parseFromString(source, "text/xml"); } readXmlElement(source) { const document = this.readXmlDocument(source); const element = document.documentElement; return element; } findElement(element, ...names) { const first = names.shift(); const current = first ? first.toLowerCase() : ""; const children = element.childNodes; let index = 0; for (index; index < children.length; index += 1) { const child = children[index]; if (child.nodeType === child.ELEMENT_NODE) { const localName = child.localName.toLowerCase(); if (localName === current) { return names.length > 0 ? this.findElement(child, ...names) : child; } } } return void 0; } findContent(element, ...names) { const found = this.findElement(element, ...names); if (!found) { return ""; } return this.extractElementContent(found); } extractElementContent(element) { const buffer = []; const children = element.childNodes; let index = 0; for (index; index < children.length; index += 1) { const child = children[index]; if (child.nodeType === 3) { const c = child; if (c.textContent !== null) { buffer.push(c.textContent); } } } return buffer.join(""); } findElements(element, ...names) { const last = names.pop(); const current = last ? last.toLowerCase() : ""; const temporaryElement = this.findElement(element, ...names); if (!temporaryElement) { return []; } const tempElement = temporaryElement; const found = []; const children = tempElement.childNodes; let index = 0; for (index; index < children.length; index += 1) { const child = children[index]; if (child.nodeType === child.ELEMENT_NODE) { const localName = child.localName.toLowerCase(); if (localName === current) { found.push(child); } } } return found; } findContents(element, ...names) { return this.findElements(element, ...names).map( (elementItem) => this.extractElementContent(elementItem) ); } /** * Find the element determined by the chain of children and return the attributes as an * array using the attribute name as array key and attribute value as entry value. */ findAtrributes(element, ...search) { const found = this.findElement(element, ...search); if (!found) { return {}; } const attributes = /* @__PURE__ */ new Map(); const elementAttributes = found.attributes; let index = 0; for (index; index < elementAttributes.length; index += 1) { attributes.set( elementAttributes[index].localName.toLowerCase(), elementAttributes[index].value ); } return Object.fromEntries(attributes); } }; // src/web_client/crequest.ts var CRequest = class { _method; _uri; _body; _headers; _timeout; constructor(method, uri, body, headers, timeout) { this._method = method; this._uri = uri; this._body = body; const map = new Map([...Object.entries(this.defaultHeaders()), ...Object.entries(headers)]); this._headers = Object.fromEntries(map); this._timeout = timeout; } getMethod() { return this._method; } getUri() { return this._uri; } getBody() { return this._body; } getHeaders() { return this._headers; } getTimeout() { return this._timeout; } defaultHeaders() { return { "Content-type": 'text/xml; charset="utf-8"', "Accept": "text/xml", "Cache-Control": "no-cache" }; } toJSON() { return { method: this._method, uri: this._uri, body: this._body, headers: this._headers, timeout: this._timeout }; } }; // src/web_client/exceptions/web_client_exception.ts var WebClientException = class extends Error { _request; _response; _previous; constructor(message, request, response, previous) { super(message); this._request = request; this._response = response; this._previous = previous; } getRequest() { return this._request; } getResponse() { return this._response; } getPrevious() { return this._previous; } }; // src/web_client/exceptions/http_client_error.ts var HttpClientError = class extends WebClientException { }; // src/web_client/exceptions/http_server_error.ts var HttpServerError = class extends WebClientException { }; // src/web_client/exceptions/http_timeout_error.ts var HttpTimeoutError = class extends WebClientException { }; // src/web_client/exceptions/soap_fault_error.ts var SoapFaultError = class extends HttpClientError { _fault; constructor(request, response, fault, previous) { const message = `Fault: ${fault.getCode()} - ${fault.getMessage()}`; super(message, request, response, previous); this._fault = fault; } getFault() { return this._fault; } }; // src/web_client/soap_fault_info.ts var SoapFaultInfo = class { _code; _message; constructor(code, message) { this._code = code; this._message = message; } getCode() { return this._code; } getMessage() { return this._message; } toJSON() { return { code: this._code, message: this._message }; } }; // src/internal/soap_fault_info_extractor.ts var SoapFaultInfoExtractor = class _SoapFaultInfoExtractor extends InteractsXmlTrait { static extract(source) { return new _SoapFaultInfoExtractor().obtainFault(source); } obtainFault(source) { let env; try { env = this.readXmlElement(source); } catch { return; } const code = (this.findElement(env, "body", "fault", "faultcode")?.textContent ?? "").trim(); const message = (this.findElement(env, "body", "fault", "faultstring")?.textContent ?? "").trim(); if (code === "" && message === "") { return; } return new SoapFaultInfo(code, message); } }; // src/internal/service_consumer.ts var ServiceConsumer = class _ServiceConsumer { static async consume(webClient, soapAction, uri, body, token) { return new _ServiceConsumer().execute(webClient, soapAction, uri, body, token); } async execute(webClient, soapAction, uri, body, token) { const headers = this.createHeaders(soapAction, token); const request = this.createRequest(uri, body, headers); let exception; let response; try { response = await this.runRequest(webClient, request); } catch (error) { const webError = error; exception = webError; response = webError.getResponse(); } this.checkErrors(request, response, exception); return response.getBody(); } createRequest(uri, body, headers) { return new CRequest("POST", uri, body, headers); } createHeaders(soapAction, token) { const headers = /* @__PURE__ */ new Map(); headers.set("SOAPAction", soapAction); if (token) { headers.set("Authorization", `WRAP access_token="${token.getValue()}"`); } return Object.fromEntries(headers); } async runRequest(webClient, request) { webClient.fireRequest(request); let response; try { response = await webClient.call(request); } catch (error) { const webError = error; webClient.fireResponse(webError.getResponse()); throw webError; } webClient.fireResponse(response); return response; } checkErrors(request, response, exception) { if (response.statusCodeIsTimeoutError()) { const message = `Unexpected timeout error: ${response.getBody()}`; throw new HttpTimeoutError(message, request, response, exception); } const fault = SoapFaultInfoExtractor.extract(response.getBody()); if (fault) { throw new SoapFaultError(request, response, fault, exception); } if (response.statusCodeIsClientError()) { const message = `Unexpected client error status code ${response.getStatusCode()}`; throw new HttpClientError(message, request, response, exception); } if (response.statusCodeIsServerError()) { const message = `Unexpected server error status code ${response.getStatusCode()}`; throw new HttpServerError(message, request, response, exception); } if (response.isEmpty()) { throw new HttpServerError( "Unexpected empty response from server", request, response, exception ); } } }; // src/package_reader/internal/file_filters/cfdi_file_filter.ts var CfdiFileFilter = class _CfdiFileFilter { static obtainUuidFromXmlCfdi(xmlContent) { const pattern = /:Complemento.*?:TimbreFiscalDigital.*?UUID="(?<uuid>[\dA-Za-z-]{36})"/s; const found = pattern.exec(xmlContent); if (found?.groups?.uuid) { return found.groups.uuid.toLowerCase(); } return ""; } filterFilename(filename) { return /^[^/\\]+\.xml/i.test(filename); } filterContents(contents) { return _CfdiFileFilter.obtainUuidFromXmlCfdi(contents) !== ""; } }; // src/package_reader/internal/filtered_package_reader.ts import { randomUUID } from "crypto"; import { readFile, realpath, unlink, writeFile } from "fs/promises"; import os from "os"; import path from "path"; import JSZip from "jszip"; // src/package_reader/exceptions/package_reader_exception.ts var PackageReaderException = class extends Error { }; // src/package_reader/exceptions/create_temporary_file_zip_exception.ts var CreateTemporaryZipFileException = class _CreateTemporaryZipFileException extends PackageReaderException { _previous; constructor(message, previous) { super(message); this._previous = previous; } static create(message, previous) { const messageToSend = previous && previous.message !== "" ? `${message} : ${previous.message}` : message; return new _CreateTemporaryZipFileException(messageToSend, previous); } getPrevious() { return this._previous; } }; // src/package_reader/exceptions/open_zip_file_exception.ts var OpenZipFileException = class _OpenZipFileException extends PackageReaderException { _filename; _previous; _code; constructor(message, code, filename, previous) { super(message); this._filename = filename; this._previous = previous; this._code = code; } static create(filename, code, previous) { const messageToSend = previous && previous.message !== "" ? `Unable to open Zip file ${filename}. previous ${previous.message}` : `Unable to open Zip file ${filename}`; return new _OpenZipFileException(messageToSend, code, filename, previous); } getFileName() { return this._filename; } getCode() { return this._code; } getPrevious() { return this._previous; } }; // src/package_reader/internal/file_filters/null_file_filter.ts var NullFileFilter = class { filterFilename(_filename) { return true; } filterContents(_contents) { return true; } }; // src/package_reader/internal/filtered_package_reader.ts var FilteredPackageReader = class _FilteredPackageReader { _filename; _archive; _removeOnDestruct = false; _filter; constructor(filename, archive) { this._filename = filename; this._archive = archive; } static async createFromFile(filename) { let archive; let data; try { data = await readFile(filename); } catch { throw OpenZipFileException.create(filename, -1); } try { archive = await JSZip.loadAsync(data); } catch { throw OpenZipFileException.create(filename, -1); } return new _FilteredPackageReader(filename, archive); } static async createFromContents(contents) { const tmpdir = await realpath(os.tmpdir()); const tmpfile = path.join(tmpdir, `${randomUUID()}.zip`); try { await writeFile(tmpfile, ""); } catch (error) { throw CreateTemporaryZipFileException.create( "Cannot create a temporary file", error ); } try { await writeFile(tmpfile, contents, { encoding: "binary" }); } catch (error) { throw CreateTemporaryZipFileException.create( "Cannot store contents on temporary file", error ); } let cpackage; try { cpackage = await _FilteredPackageReader.createFromFile(tmpfile); } catch (error) { await unlink(tmpfile); throw error; } cpackage._removeOnDestruct = true; return cpackage; } async destruct() { if (this._removeOnDestruct) { await unlink(this._filename); } } async *fileContents() { const archive = this.getArchive(); const filter = this.getFilter(); const entries = Object.keys(archive.files).map((name) => archive.files[name].name); let contents; for (const entry of entries) { if (!filter.filterFilename(entry)) { continue; } contents = await archive.file(entry)?.async("text"); if (contents === void 0 || !filter.filterContents(contents)) { continue; } yield (/* @__PURE__ */ new Map()).set(entry, contents || ""); } } async count() { let count = 0; for await (const [,] of this.fileContents()) { count += 1; } return count; } getFilename() { return this._filename; } getArchive() { return this._archive; } getFilter() { return this._filter; } setFilter(filter) { this._filter = filter ?? new NullFileFilter(); } changeFilter(filter) { const previous = this.getFilter(); this.setFilter(filter); return previous; } async jsonSerialize() { let files = {}; for await (const item of this.fileContents()) { for (const [key, value] of item) { files = { ...files, [key]: value }; } } return { source: this.getFilename(), files }; } }; // src/package_reader/cfdi_package_reader.ts var CfdiPackageReader = class _CfdiPackageReader { constructor(_packageReader) { this._packageReader = _packageReader; } static async createFromFile(filename) { const packageReader = await FilteredPackageReader.createFromFile(filename); packageReader.setFilter(new CfdiFileFilter()); return new _CfdiPackageReader(packageReader); } static async createFromContents(contents) { const packageReader = await FilteredPackageReader.createFromContents(contents); packageReader.setFilter(new CfdiFileFilter()); await packageReader.destruct(); return new _CfdiPackageReader(packageReader); } async *cfdis() { for await (const content of this._packageReader.fileContents()) { let data = ""; for (const item of content) { data = item[1]; } yield (/* @__PURE__ */ new Map()).set(CfdiFileFilter.obtainUuidFromXmlCfdi(data), data); } } getFilename() { return this._packageReader.getFilename(); } async count() { let count = 0; for await (const [,] of this.fileContents()) { count += 1; } return count; } async *fileContents() { yield* this._packageReader.fileContents(); } async jsonSerialize() { const filtered = await this._packageReader.jsonSerialize(); let cfdis = {}; for await (const item of this.cfdis()) { for (const [key, value] of item) { cfdis = { ...cfdis, [key]: value }; } } return { source: filtered.source, files: filtered.files, cfdis }; } async cfdisToArray() { const cfdis = []; for await (const item of this.cfdis()) { for (const [uuid, content] of item) { cfdis.push({ uuid, content }); } } return cfdis; } async fileContentsToArray() { const contents = []; for await (const item of this.fileContents()) { for (const [name, content] of item) { contents.push({ name, content }); } } return contents; } }; // src/package_reader/internal/csv_reader.ts import { randomUUID as randomUUID2 } from "crypto"; import { createReadStream, realpathSync, writeFileSync } from "fs"; import os2 from "os"; import path2 from "path"; import * as readline from "readline"; var CsvReader = class _CsvReader { constructor(_iterator) { this._iterator = _iterator; } static createIteratorFromContents(contents) { const tmpdir = realpathSync(os2.tmpdir()); const filePath = path2.join(tmpdir, `${randomUUID2()}.csv`); writeFileSync(filePath, contents); const iterator = readline.createInterface({ input: createReadStream(filePath), crlfDelay: Number.POSITIVE_INFINITY }); return iterator; } static createFromContents(contents) { return new _CsvReader(_CsvReader.createIteratorFromContents(contents)); } async *records() { const headers = []; for await (const line of this._iterator) { const clean = line.split(/[|~]/).map((item) => item.trim()); if (clean.length === 0 || JSON.stringify(clean) === '[""]') { continue; } if (headers.length === 0) { headers.push(...clean); continue; } yield this.combine(headers, clean); } } /** * Like array.concat but complement missing values or missing keys (#extra-01, #extra-02, etc...) */ combine(keys, values) { const countValues = values.length; const countKeys = keys.length; let newValues = values; if (countKeys > countValues) { const emptyArray = Array.from({ length: countKeys - countValues }); newValues = [...values, ...emptyArray.fill("")]; } if (countValues > countKeys) { for (let i = 1; i <= countValues - countKeys; i += 1) { const string_ = i.toString().padStart(2, "0"); keys.push(`#extra-${string_}`); } } const map = /* @__PURE__ */ new Map(); for (const [index, value] of newValues.entries()) { map.set(keys[index], value); } return Object.fromEntries(map); } }; // src/package_reader/internal/file_filters/metadata_file_filter.ts var MetadataFileFilter = class { filterFilename(filename) { return /^[^/\\]+\.txt/i.test(filename); } filterContents(contents) { return contents.startsWith("Uuid~RfcEmisor~"); } }; // src/package_reader/internal/file_filters/third_parties_file_filter.ts var ThirdPartiesFileFilter = class { filterFilename(filename) { return /^[^/\\]+_tercero\.txt/i.test(filename); } filterContents(contents) { return contents.startsWith("Uuid~RfcACuentaTerceros~NombreACuentaTerceros"); } }; // src/package_reader/metadata_item.ts var MetadataItem = class { _data; constructor(data) { this._data = Object.entries(data).map(([key, value]) => ({ key, value })); } get(key) { return this._data.find((item) => item.key === key)?.value ?? ""; } /** * * returns all keys and values in a record form. */ all() { return Object.fromEntries(this._data.map((current) => [current.key, current.value])); } [Symbol.iterator]() { return this._data[Symbol.iterator](); } }; // src/package_reader/internal/metadata_preprocessor.ts var MetadataPreprocessor = class { /** The data to process */ _contents; constructor(contents) { this._contents = contents; } CONTROL_CR = "\r"; CONTROL_LF = "\n"; CONTROL_CRLF = "\r\n"; getContents() { return this._contents; } fix() { this.fixEolCrLf(); } fixEolCrLf() { const firstLineFeedPosition = this._contents.indexOf(this.CONTROL_LF); let eolIsCrLf; if (firstLineFeedPosition === -1) { eolIsCrLf = false; } else { eolIsCrLf = firstLineFeedPosition > 0 ? this._contents.slice(firstLineFeedPosition - 1, firstLineFeedPosition) === this.CONTROL_CR : !this._contents.includes(this.CONTROL_CR); } if (!eolIsCrLf) { return; } const lines = this._contents.split(this.CONTROL_CRLF); this._contents = lines.map((line) => line.replaceAll(new RegExp(/\n/, "g"), "")).join(this.CONTROL_LF); } }; // src/package_reader/internal/third_parties_extractor.ts var ThirdPartiesExtractor = class _ThirdPartiesExtractor { constructor(_csvReader) { this._csvReader = _csvReader; } static async createFromPackageReader(packageReader) { if (!(packageReader instanceof FilteredPackageReader)) { throw new TypeError("PackageReader parameter must be a FilteredPackageReader"); } const previousFilter = packageReader.changeFilter(new ThirdPartiesFileFilter()); let contents = ""; for await (const fileContents of packageReader.fileContents()) { for (const item of fileContents) { contents = item[1]; } break; } packageReader.setFilter(previousFilter); return new _ThirdPartiesExtractor(CsvReader.createFromContents(contents)); } async *eachRecord() { let uuid; for await (const data of this._csvReader.records()) { uuid = data.Uuid.toUpperCase(); if (uuid === "") { continue; } const value = { RfcACuentaTerceros: data.RfcACuentaTerceros, NombreACuentaTerceros: data.NombreACuentaTerceros }; yield (/* @__PURE__ */ new Map()).set(uuid, value); } } }; // src/package_reader/internal/third_parties_records.ts var ThirdPartiesRecords = class _ThirdPartiesRecords { constructor(_records) { this._records = _records; } static createEmpty() { return new _ThirdPartiesRecords({}); } static async createFromPackageReader(packageReader) { const thirdPartiesBuilder = await ThirdPartiesExtractor.createFromPackageReader(packageReader); const records = {}; for await (const iterator of thirdPartiesBuilder.eachRecord()) { for (const [key, value] of iterator) { records[_ThirdPartiesRecords.formatUuid(key)] = value; } } return new _ThirdPartiesRecords(records); } static formatUuid(uuid) { return uuid.toLowerCase(); } addToData(data) { const uuid = data.Uuid ?? ""; const values = this.getDataFromUuid(uuid); return { ...data, ...values }; } getDataFromUuid(uuid) { const defaultValue = { RfcACuentaTerceros: "", NombreACuentaTerceros: "" }; return this._records[_ThirdPartiesRecords.formatUuid(uuid)] ?? defaultValue; } }; // src/package_reader/internal/metadata_content.ts var MetadataContent = class _MetadataContent { /** * The iterator will be used in a foreach loop to create MetadataItems * The first iteration must contain an array of header names that will be renames to lower case first letter * The next iterations must contain an array with data */ constructor(_csvReader, _thirdParties) { this._csvReader = _csvReader; this._thirdParties = _thirdParties; } /** * This method fix the content and create a SplTempFileObject to store the information */ static createFromContents(contents, thirdParties) { const defaultThirdParties = thirdParties ?? ThirdPartiesRecords.createEmpty(); const preprocessor = new MetadataPreprocessor(contents); preprocessor.fix(); const csvReader = CsvReader.createFromContents(preprocessor.getContents()); return new _MetadataContent(csvReader, defaultThirdParties); } async *eachItem() { for await (const data of this._csvReader.records()) { yield new MetadataItem( this.changeArrayKeysFirstLetterLoweCase(this._thirdParties.addToData(data)) ); } } changeArrayKeysFirstLetterLoweCase(data) { for (const [key, value] of Object.entries(data)) { const newKey = key.charAt(0).toLowerCase() + key.slice(1); data[newKey] = value; if (key !== newKey) { delete data[key]; } } return data; } }; // src/package_reader/metadata_package_reader.ts var MetadataPackageReader = class _MetadataPackageReader { constructor(_packageReader, _thirdParties) { this._packageReader = _packageReader; this._thirdParties = _thirdParties; } static async createFromFile(fileName) { const packageReader = await FilteredPackageReader.createFromFile(fileName); packageReader.setFilter(new MetadataFileFilter()); const thirdParties = await ThirdPartiesRecords.createFromPackageReader(packageReader); return new _MetadataPackageReader(packageReader, thirdParties); } static async createFromContents(contents) { const packageReader = await FilteredPackageReader.createFromContents(contents); packageReader.setFilter(new MetadataFileFilter()); await packageReader.destruct(); const thirdParties = await ThirdPartiesRecords.createFromPackageReader(packageReader); return new _MetadataPackageReader(packageReader, thirdParties); } async getThirdParties() { this._thirdParties = this._thirdParties ?? await ThirdPartiesRecords.createFromPackageReader(this._packageReader); return this._thirdParties; } async *metadata() { let reader; for await (const content of this._packageReader.fileContents()) { const parties = await this.getThirdParties(); for (const [, value] of content) { reader = MetadataContent.createFromContents(value, parties); for await (const item of reader.eachItem()) { yield item; } } } } getFilename() { return this._packageReader.getFilename(); } async count() { let count = 0; for await (const [,] of this.fileContents()) { count += 1; } return count; } async *fileContents() { yield* this._packageReader.fileContents(); } async jsonSerialize() { const filtered = await this._packageReader.jsonSerialize(); let metadata = {}; for await (const iterator of this.metadata()) { metadata = { ...metadata, [iterator.get("uuid")]: iterator.all() }; } return { source: filtered.source, files: filtered.files, metadata }; } async metadataToArray() { const content = []; for await (const iterator of this.metadata()) { content.push(iterator); } return content; } }; // src/request_builder/fiel_request_builder/fiel.ts import { Credential } from "@nodecfdi/credentials/node"; var Fiel = class _Fiel { constructor(_credential) { this._credential = _credential; } /** * Create a Fiel based on certificate and private key contents */ static create(certificateContents, privateKeyContents, passPhrase) { const credential = Credential.create(certificateContents, privateKeyContents, passPhrase); return new _Fiel(credential); } sign(toSign, algorithm) { return this._credential.sign(toSign, algorithm); } isValid() { if (!this._credential.certificate().satType().isFiel()) { return false; } return this._credential.certificate().validOn(); } getCertificatePemContents() { return this._credential.certificate().pem(); } getRfc() { return this._credential.rfc(); } getCertificateSerial() { return this._credential.certificate().serialNumber().decimal(); } /** missing function this.credential.certificate().issuerAsRfc4514() */ getCertificateIssuerName() { return this._credential.certificate().issuerAsRfc4514(); } }; // src/request_builder/fiel_request_builder/fiel_request_builder.ts import { createHash, randomUUID as randomUUID3 } from "crypto"; // src/shared/abstract_rfc_filter.ts import { Rfc } from "@nodecfdi/rfc"; var AbstractRfcFilter = class _AbstractRfcFilter { constructor(_value) { this._value = _value; } static create(value) { try { return new _AbstractRfcFilter(Rfc.parse(value)); } catch { throw new Error("RFC is invalid"); } } static empty() { return new _AbstractRfcFilter(); } static check(value) { try { _AbstractRfcFilter.create(value); return true; } catch { return false; } } isEmpty() { return this._value === void 0; } getValue() { if (this._value === void 0) { return ""; } return this._value.getRfc(); } toJSON() { return this._value?.toJSON(); } }; // src/shared/rfc_match.ts var RfcMatch = class extends AbstractRfcFilter { }; // src/shared/rfc_matches.ts var RfcMatches = class _RfcMatches { _items; _count; constructor(...items) { this._items = items; this._count = items.length; } static create(...items) { const map = /* @__PURE__ */ new Map(); const values = []; for (const item of items) { const key = item.getValue(); if (!item.isEmpty() && !map.get(key)) { map.set(item.getValue(), item); values.push(item); } } return new _RfcMatches(...values); } static createFromValues(...values) { const valuesRfc = values.map( (value) => value === "" ? RfcMatch.empty() : RfcMatch.create(value) ); return _RfcMatches.create(...valuesRfc); } isEmpty() { return this._count === 0; } getFirst() { return this._items[0] ?? RfcMatch.empty(); } count() { return this._count; } [Symbol.iterator]() { return this._items; } itemsToArray() { const values = []; for (const iterator of this._items) { values.push(iterator); } return values; } toJSON() { return this._items; } }; // src/request_builder/fiel_request_builder/fiel_request_builder.ts var FielRequestBuilder = class _FielRequestBuilder { constructor(_fiel) { this._fiel = _fiel; } getFiel() { return this._fiel; } authorization(created, expires, securityTokenId = "") { const uuid = securityTokenId || _FielRequestBuilder.createXmlSecurityToken(); const certificate = Helpers.cleanPemContents(this.getFiel().getCertificatePemContents()); const keyInfoData = ` <KeyInfo> <o:SecurityTokenReference> <o:Reference URI="#${uuid}" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/> </o:SecurityTokenReference> </KeyInfo> `; const toDigestXml = ` <u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_0"> <u:Created>${created.formatSat()}</u:Created> <u:Expires>${expires.formatSat()}</u:Expires> </u:Timestamp> `; const signatureData = this.createSignature(toDigestXml, "#_0", keyInfoData); const xml = ` <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <s:Header> <o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1"> <u:Timestamp u:Id="_0"> <u:Created>${created.formatSat()}</u:Created> <u:Expires>${expires.formatSat()}</u:Expires> </u:Timestamp> <o:BinarySecurityToken u:Id="${uuid}" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"> ${certificate} </o:BinarySecurityToken> ${signatureData} </o:Security> </s:Header> <s:Body> <Autentica xmlns="http://DescargaMasivaTerceros.gob.mx"/> </s:Body> </s:Envelope> `; return Helpers.nospaces(xml); } query(queryParameters) { if (!queryParameters.getUuid().isEmpty()) { return this.queryFolio(queryParameters); } return this.queryIssuedReceived(queryParameters); } queryFolio(queryParameters) { const rfcSigner = this.getFiel().getRfc().toUpperCase(); const attributes = /* @__PURE__ */ new Map(); attributes.set("RfcSolicitante", rfcSigner); attributes.set("Folio", queryParameters.getUuid().getValue()); return this.buildFinalXml("SolicitaDescargaFolio", attributes, ""); } queryIssuedReceived(queryParameters) { let xmlRfcReceived = ""; const requestType = queryParameters.getRequestType().getQueryAttributeValue(); const rfcSigner = this.getFiel().getRfc().toUpperCase(); const start = queryParameters.getPeriod().getStart().format("yyyy-MM-dd'T'HH:mm:ss"); const end = queryParameters.getPeriod().getEnd().format("yyyy-MM-dd'T'HH:mm:ss"); let rfcIssuer; let rfcReceivers; if (queryParameters.getDownloadType().isTypeOf("issued")) { rfcIssuer = rfcSigner; rfcReceivers = queryParameters.getRfcMatches(); } else { rfcIssuer = queryParameters.getRfcMatches().getFirst().getValue(); rfcReceivers = RfcMatches.create(); } const attributes = /* @__PURE__ */ new Map(); attributes.set("RfcSolicitante", rfcSigner); attributes.set("TipoSolicitud", requestType); attributes.set("FechaInicial", start); attributes.set("FechaFinal", end); attributes.set("RfcEmisor", rfcIssuer); attributes.set("TipoComprobante", queryParameters.getDocumentType().value()); attributes.set( "EstadoComprobante", queryParameters.getDocumentStatus().getQueryAttributeValue() ); attributes.set("RfcACuentaTerceros", queryParameters.getRfcOnBehalf().getValue()); attributes.set("Complemento", queryParameters.getComplement().value()); if (queryParameters.getDownloadType().isTypeOf("received")) { attributes.set("RfcReceptor", rfcSigner); } if (!rfcReceivers.isEmpty()) { xmlRfcReceived = rfcReceivers.itemsToArray().map( (rfcMatch) => `<des:RfcReceptor>${this.parseXml(rfcMatch.getValue())}</des:RfcReceptor>` ).join(""); xmlRfcReceived = `<des:RfcReceptores>${xmlRfcReceived}</des:RfcReceptores>`; } const nodeName = queryParameters.getDownloadType().isTypeOf("issued") ? "SolicitaDescargaEmitidos" : "SolicitaDescargaRecibidos"; return this.buildFinalXml(nodeName, attributes, xmlRfcReceived); } verify(requestId) { const xmlRequestId = this.parseXml(requestId); const xmlRfc = this.parseXml(this.getFiel().getRfc()); const toDigestXml = ` <des:VerificaSolicitudDescarga xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx"> <des:solicitud IdSolicitud="${xmlRequestId}" RfcSolicitante="${xmlRfc}"></des:solicitud> </des:VerificaSolicitudDescarga> `; const signatureData = this.createSignature(toDigestXml); const xml = ` <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx" xmlns:xd="http://www.w3.org/2000/09/xmldsig#"> <s:Header/> <s:Body> <des:VerificaSolicitudDescarga> <des:solicitud IdSolicitud="${xmlRequestId}" RfcSolicitante="${xmlRfc}"> ${signatureData} </des:solicitud> </des:VerificaSolicitudDescarga> </s:Body> </s:Envelope> `; return Helpers.nospaces(xml); } download(packageId) { const xmlPackageId = this.parseXml(packageId); const xmlRfcOwner = this.parseXml(this.getFiel().getRfc()); const toDigestXml = ` <des:PeticionDescargaMasivaTercerosEntrada xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx"> <des:peticionDescarga IdPaquete="${xmlPackageId}" RfcSolicitante="${xmlRfcOwner}"></des:peticionDescarga> </des:PeticionDescargaMasivaTercerosEntrada> `; const signatureData = this.createSignature(toDigestXml); const xml = ` <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx" xmlns:xd="http://www.w3.org/2000/09/xmldsig#"> <s:Header/> <s:Body> <des:PeticionDescargaMasivaTercerosEntrada> <des:peticionDescarga IdPaquete="${xmlPackageId}" RfcSolicitante="${xmlRfcOwner}"> ${signatureData} </des:peticionDescarga> </des:PeticionDescargaMasivaTercerosEntrada> </s:Body> </s:Envelope> `; return Helpers.nospaces(xml); } buildFinalXml(nodeName, attributes, xmlExtra) { const cleanedSolicitudAttributes = /* @__PURE__ */ new Map(); for (const [key, value] of attributes) { if (value !== "") { cleanedSolicitudAttributes.set(key, value); } } const sortedValues = new Map( [...cleanedSolicitudAttributes].sort((a, b) => String(a[0]).localeCompare(b[0])) ); const solicitudAttributesAsText = [...sortedValues].map( ([name, value]) => `${this.parseXml(name)}="${this.parseXml(value)}"` ).join(" "); const toDigestXml = ` <des:${nodeName} xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx"> <des:solicitud ${solicitudAttributesAsText}> ${xmlExtra} </des:solicitud> </des:${nodeName}> `; const signatureData = this.createSignature(toDigestXml); const xml = ` <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:des="http://DescargaMasivaTerceros.sat.gob.mx" xmlns:xd="http://www.w3.org/2000/09/xmldsig#"> <s:Header/> <s:Body> <des:${nodeName}> <des:solicitud ${solicitudAttributesAsText}> ${xmlExtra} ${signatureData} </des:solicitud> </des:${nodeName}> </s:Body> </s:Envelope> `; return Helpers.nospaces(xml); } createSignature(toDigest, signedInfoUri = "", keyInfo = "") { const cleanToDigest = Helpers.nospaces(toDigest); const digested = createHash("sha1").update(cleanToDigest).digest("base64"); let signedInfo = this.createSignedInfoCanonicalExclusive(digested, signedInfoUri); const signatureValue = Buffer.from(this.getFiel().sign(signedInfo, "sha1"), "binary").toString( "base64" ); signedInfo = signedInfo.replace( '<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">', "<SignedInfo>" ); let newKeyInfo = keyInfo; if (newKeyInfo === "") { newKeyInfo = this.createKeyInfoData(); } return ` <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> ${signedInfo} <SignatureValue>${signatureValue}</SignatureValue> ${newKeyInfo} </Signature> `; } createSignedInfoCanonicalExclusive(digested, uri = "") { const xml = ` <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod> <Reference URI="${uri}"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod> <DigestValue>${digested}</DigestValue> </Reference> </SignedInfo> `; return Helpers.nospaces(xml); } createKeyInfoData() { const fiel = this.getFiel(); const certificate = Helpers.cleanPemContents(fiel.getCertificatePemContents()); const serial = fiel.getCertificateSerial(); const issuerName = this.parseXml(fiel.getCertificateIssuerName()); return ` <KeyInfo> <X509Data> <X509IssuerSerial> <X509IssuerName>${issuerName}</X509IssuerName> <X509SerialNumber>${serial}</X509SerialNumber> </X509IssuerSerial> <X509Certificate>${certificate}</X509Certificate> </X509Data> </KeyInfo> `; } parseXml(text) { return Helpers.htmlspecialchars(text); } static createXmlSecurityToken() { const md5 = createHash("md5").update(randomUUID3()).digest("hex"); return `uuid-${md5.slice(0, 8)}-${md5.slice(4, 8)}-${md5.slice(4, 12)}-${md5.slice(4, 16)}-${md5.slice(20)}-1`; } }; // src/request_builder/request_builder_exception.ts var RequestBuilderException = class extends Error { }; // src/shared/date_time.ts import { DateTime as DateTimeImmutable } from "luxon"; var DateTime = class _DateTime { _value; _defaultTimeZone; /** * DateTime constructor. * * If value is an integer is used as a timestamp, if is a string is evaluated * as an argument for DateTimeImmutable and if it is DateTimeImmutable is used as is. * * @throws Error if unable to create a DateTime */ constructor(value, defaultTimeZone) { let newValue = value ?? "now"; const originalValue = newValue; this._defaultTimeZone = defaultTimeZone ?? DateTimeImmutable.now().zone.name; if (typeof newValue === "number") { this._value = DateTimeImmutable.fromSeconds(newValue, { zone: this._defaultTimeZone }); if (!this._value.isValid) { throw new Error(`Unable to create a Datetime("${originalValue}")`); } return; } if (typeof newValue === "string") { newValue = this.castStringToDateTimeImmutable(newValue, originalValue); } if (!(newValue instanceof DateTimeImmutable) || !newValue.isValid) { throw new Error("Unable to create a Datetime"); } this._value = newValue; } /** * Create a DateTime instance * * If value is an integer is used as a timestamp, if is a string is evaluated * as an argument for DateTimeImmutable and if it is DateTimeImmutable is used as is. */ static create(value, defaultTimeZone) { return new _DateTime(value, defaultTimeZone); } static now() { return new _DateTime(); } formatSat() { return this.formatTimeZone("UTC"); } format(format, timezone = "") { let clonedTimeZone = timezone; if (clonedTimeZone === "") { clonedTimeZone = this._defaultTimeZone; } this._value = this._value.setZone(clonedTimeZone); return this._value.toFormat(format); } formateDefaultTimeZone() { return this.formatTimeZone(this._defaultTimeZone); } formatTimeZone(timezone) { return this._value.setZone(timezone).toISO() ?? ""; } /** * add or sub in given DurationLike * */ modify(time) { const temporary = this._value; return new _DateTime(temporary.plus(time)); } compareTo(otherDate) { return this.formatSat().toString().localeCompare(otherDate.formatSat().toString()); } equalsTo(expectedExpires) { return this.formatSat() === expectedExpires.formatSat(); } toJSON() { return this._value.toSeconds(); } castStringToDateTimeImmutable(value, originalValue) { if (value === "now") { return DateTimeImmutable.fromISO(DateTimeImmutable.now().toISO(), { zone: this._defaultTimeZone }); } const temporary = DateTimeImmutable.fromSQL(value, { zone: this._defaultTimeZone }); const newValue = temporary.isValid ? temporary : DateTimeImmutable.fromISO(value); if (!newValue.isValid) { throw new Error(`Unable to create a Datetime("${originalValue}")`); } return newValue; } }; // src/shared/token.ts var Token = class _Token { constructor(created, _expires, _value) { this._expires = _expires; this._value = _value; if (_expires.compareTo(created) < 0) { throw new Error("Cannot create a token with expiration lower than creation"); } this._created = created; } _created; static empty() { return new _Token(DateTime.create(0), DateTime.create(0), ""); } getCreated() { return this._created; } getExpires() { return this._expires; } getValue() { return this._value; } /** * A token is empty if does not contains an internal value */ isValueEmpty() { return this._value === ""; } /** * A token is expired if the expiration date is greater or equal to current time */ isExpired() { return this._expires.compareTo(DateTime.now()) < 0; } /** * A token is valid if contains a value and is not expired */ isValid() { return !(this.isValueEmpty() || this.isExpired()); } toJSON() { return { created: this._created, expires: this._expires, value: this._value }; } }; // src/services/authenticate/authenticate_translator.ts var AuthenticateTranslator = class extends InteractsXmlTrait { createTokenFromSoapResponse(content) { const env = this.readXmlElement(content); let timeContent = this.findContent(env, "header", "security", "timestamp", "created"); const created = DateTime.create(timeContent === "" ? 0 : timeContent); timeContent = this.findContent(env, "header", "security", "timestamp", "expires"); const expires = DateTime.create(timeContent === "" ? 0 : timeContent); const value = this.findContent(env, "body", "autenticaResponse", "autenticaResult"); return new Token(created, expires, value); } createSoapRequest(requestBuilder) { const since = DateTime.now(); const until = since.modify({ minutes: 5 }); return this.createSoapRequestWithData(requestBuilder, since, until); } createSoapRequestWithData(requestBuilder, since, until, securityToken = "") { return requestBuilder.authorization(since, until, securityToken); } }; // src/shared/status_code.ts var StatusCode = class { constructor(_code, _message) { this._code = _code; this._message = _message; } /** * Contains the value of "CodEstatus" */ getCode() { return this._code; } /** * Contains the value of "Mensaje" */ getMessage() { return this._message; } /** * Return true when "CodEstatus" is success * The only success code is "5000: Solicitud recibida con éxito" */ isAccepted() { return this._code === 5e3; } toJSON() { return { code: this._code, message: this._message }; } }; // src/services/download/download_result.ts var DownloadResult = class { constructor(_status, _packageContent) { this._status = _status; this._packageContent = _packageContent; this._packageSize = _packageContent.length; } _packageSize; /** * Status of the download call */ getStatus() { return this._status; } /** * If available, contains the package contents */ getPackageContent() { return this._packageContent; } /** * If available, contains the package contents length in bytesF */ getPackageSize() { return this._packageSize; } toJSON() { return { status: this._status, length: this._packageSize }; } }; // src/services/download/download_translator.ts var DownloadTranslator = class extends InteractsXmlTrait { createDownloadResultFromSoapResponse(content) { const env = this.readXmlElement(content); const values = this.findAtrributes(env, "header", "respuesta"); const status = new StatusCode(Number(values.codestatus), values.mensaje); const cpackage = this.findContent( env, "body", "RespuestaDescargaMasivaTercerosSalida", "Paquete" ); return new DownloadResult(status, Buffer.from(cpackage).toString()); } createSoapRequest(requestBuilder, packageId) { return requestBuilder.download(packageId); } }; // src/services/query/query_result.ts var QueryResult = class { _status; _requestId; constructor(statusCode, requestId) { this._status = statusCode; this._requestId = requestId; } /** * Status of the verification call */ getStatus() { return this._status; } /** * If accepted, contains the request identification required for verification */ getRequestId() { return this._requestId; } toJSON() { return { status: this._status, requestId: this._requestId }; } }; // src/services/query/query_translator.ts var QueryTranslator = class extends InteractsXmlTrait { resolveResponsePath(envelope) { if (this.findElement(envelope, "body", "solicitaDescargaEmitidosResponse")) { return ["body", "solicitaDescargaEmitidosResponse", "solicitaDescargaEmitidosResult"]; } if (this.findElement(envelope, "body", "solicitaDescargaRecibidosResponse")) { return ["body", "solicitaDescargaRecibidosResponse", "solicitaDescargaRecibidosResult"]; } if (this.findElement(envelope, "body", "SolicitaDescargaFolioResponse")) { return ["body", "SolicitaDescargaFolioResponse", "SolicitaDescargaFolioResult"]; } return []; } createQueryResultFromSoapResponse(content) { const env = this.readXmlElement(content); const path3 = this.resolveResponsePath(e