@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
JavaScript
// 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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
}
};
// 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