verifactu-node-lib
Version:
Node.js library for generating VeriFacTu invoices compatible with AEAT
577 lines • 26.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInvoice = createInvoice;
exports.cancelInvoice = cancelInvoice;
const xmldom_1 = require("@xmldom/xmldom");
const crypto = __importStar(require("crypto"));
const qrcode = __importStar(require("qrcode"));
const utils_1 = require("./utils");
const NS1 = `xmlns:sum="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd"`;
const NS2 = `xmlns="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd"`;
const VERIFACTU_CANCEL_XML_BASE = `
<sum:RegistroFactura ${NS1} ${NS2}>
<RegistroAnulacion>
<IDVersion>1.0</IDVersion>
<IDFactura>
<IDEmisorFacturaAnulada>???</IDEmisorFacturaAnulada>
<NumSerieFacturaAnulada>????</NumSerieFacturaAnulada>
<FechaExpedicionFacturaAnulada>????</FechaExpedicionFacturaAnulada>
</IDFactura>
<Encadenamiento>
<PrimerRegistro>S</PrimerRegistro>
<RegistroAnterior>
<IDEmisorFactura>????</IDEmisorFactura>
<NumSerieFactura>????</NumSerieFactura>
<FechaExpedicionFactura>????</FechaExpedicionFactura>
<Huella>????</Huella>
</RegistroAnterior>
</Encadenamiento>
<SistemaInformatico>
<NombreRazon>????</NombreRazon>
<NIF>????</NIF>
<NombreSistemaInformatico>????</NombreSistemaInformatico>
<IdSistemaInformatico>????</IdSistemaInformatico>
<Version>????</Version>
<NumeroInstalacion>????</NumeroInstalacion>
<TipoUsoPosibleSoloVerifactu>????</TipoUsoPosibleSoloVerifactu>
<TipoUsoPosibleMultiOT>????</TipoUsoPosibleMultiOT>
<IndicadorMultiplesOT>????</IndicadorMultiplesOT>
</SistemaInformatico>
<FechaHoraHusoGenRegistro>????</FechaHoraHusoGenRegistro>
<TipoHuella>01</TipoHuella>
<Huella>????</Huella>
</RegistroAnulacion>
</sum:RegistroFactura>`.replace(/>\s+</g, "><").replace(/\s*xmlns/g, " xmlns");
const VERIFACTU_INVOICE_XML_BASE = `
<sum:RegistroFactura ${NS1} ${NS2}>
<RegistroAlta>
<IDVersion>1.0</IDVersion>
<IDFactura>
<IDEmisorFactura>????</IDEmisorFactura>
<NumSerieFactura>????</NumSerieFactura>
<FechaExpedicionFactura>????</FechaExpedicionFactura>
</IDFactura>
<NombreRazonEmisor>????</NombreRazonEmisor>
<Subsanacion>???</Subsanacion>
<TipoFactura>F1</TipoFactura>
<TipoRectificativa>????</TipoRectificativa>
<FacturasRectificadas/>
<FacturasSustituidas/>
<ImporteRectificacion/>
<FechaOperacion/>
<DescripcionOperacion>????</DescripcionOperacion>
<EmitidaPorTerceroODestinatario>????</EmitidaPorTerceroODestinatario>
<Tercero>
<NombreRazon>????</NombreRazon>
<NIF>????</NIF>
</Tercero>
<Destinatarios/>
<Desglose/>
<CuotaTotal>????</CuotaTotal>
<ImporteTotal>????</ImporteTotal>
<RetencionSoportada>????</RetencionSoportada>
<Encadenamiento>
<PrimerRegistro>S</PrimerRegistro>
<RegistroAnterior>
<IDEmisorFactura>????</IDEmisorFactura>
<NumSerieFactura>????</NumSerieFactura>
<FechaExpedicionFactura>????</FechaExpedicionFactura>
<Huella>????</Huella>
</RegistroAnterior>
</Encadenamiento>
<SistemaInformatico>
<NombreRazon>????</NombreRazon>
<NIF>????</NIF>
<NombreSistemaInformatico>????</NombreSistemaInformatico>
<IdSistemaInformatico>????</IdSistemaInformatico>
<Version>????</Version>
<NumeroInstalacion>????</NumeroInstalacion>
<TipoUsoPosibleSoloVerifactu>????</TipoUsoPosibleSoloVerifactu>
<TipoUsoPosibleMultiOT>????</TipoUsoPosibleMultiOT>
<IndicadorMultiplesOT>????</IndicadorMultiplesOT>
</SistemaInformatico>
<FechaHoraHusoGenRegistro>????</FechaHoraHusoGenRegistro>
<TipoHuella>01</TipoHuella>
<Huella>????</Huella>
</RegistroAlta>
</sum:RegistroFactura>`.replace(/>\s+</g, "><").replace(/\s*xmlns/g, " xmlns");
// Funciones auxiliares para validaciones básicas
function validateInvoice(invoice) {
if (!invoice.issuer || !invoice.issuer.irsId || !invoice.issuer.name) {
throw new Error("El emisor es obligatorio y debe incluir NIF y nombre");
}
if (!invoice.id || !invoice.id.number || !invoice.id.issuedTime) {
throw new Error("Los datos de identificación de la factura son obligatorios");
}
if (!invoice.vatLines || invoice.vatLines.length === 0) {
throw new Error("Debe incluir al menos una línea de IVA");
}
if (typeof invoice.total !== "number" || typeof invoice.amount !== "number") {
throw new Error("El total y el importe son obligatorios");
}
// Validar que no se usen retentionAmount y retentionLines al mismo tiempo
// if (invoice.retentionAmount !== undefined && invoice.retentionLines && invoice.retentionLines.length > 0) {
// throw new Error("No se puede especificar retentionAmount y retentionLines al mismo tiempo");
// }
}
function validateCancelInvoice(cancelInvoice) {
if (!cancelInvoice.issuer || !cancelInvoice.issuer.irsId || !cancelInvoice.issuer.name) {
throw new Error('El emisor es obligatorio y debe incluir NIF y nombre');
}
if (!cancelInvoice.id || !cancelInvoice.id.number || !cancelInvoice.id.issuedTime) {
throw new Error('Los datos de identificación de la factura a anular son obligatorios');
}
}
function validateSoftware(software) {
if (!software.developerName || !software.developerIrsId || !software.name ||
!software.id || !software.version || !software.number) {
throw new Error('Todos los datos del software son obligatorios');
}
}
// Funciones para agregar elementos al XML
function addRecipientToXml(xml, recipient) {
const destinatarios = (0, utils_1.querySelector)(xml, "Destinatarios");
if (!destinatarios)
return;
if (!recipient) {
// Remover el elemento Destinatarios si no hay destinatario
(0, utils_1.removeElement)(destinatarios);
return;
}
const isIrs = 'irsId' in recipient;
const template = isIrs ? `
<IDDestinatario>
<NombreRazon></NombreRazon>
<NIF></NIF>
</IDDestinatario>` : `
<IDDestinatario>
<NombreRazon></NombreRazon>
<IDOtro>
<CodigoPais></CodigoPais>
<IDType></IDType>
<ID></ID>
</IDOtro>
</IDDestinatario>`;
const newXml = new xmldom_1.DOMParser().parseFromString(template.replace(/>\s+</g, "><"), "application/xml");
if (isIrs) {
const irsRecipient = recipient;
(0, utils_1.updateDocument)(newXml, [
['NombreRazon', irsRecipient.name, utils_1.toStr120],
['NIF', irsRecipient.irsId, utils_1.toNifStr],
]);
}
else {
const otherRecipient = recipient;
(0, utils_1.updateDocument)(newXml, [
['NombreRazon', otherRecipient.name, utils_1.toStr120],
['CodigoPais', otherRecipient.country, utils_1.toString],
['IDType', otherRecipient.idType, utils_1.toString],
['ID', otherRecipient.id, utils_1.toStr20],
]);
}
if (newXml.documentElement) {
destinatarios.appendChild(newXml.documentElement);
}
}
function addVatLinesToXml(xml, vatLines) {
const desglose = (0, utils_1.querySelector)(xml, "Desglose");
if (!desglose)
return;
vatLines.forEach(vatLine => {
const template = `
<DetalleDesglose>
<Impuesto></Impuesto>
<ClaveRegimen></ClaveRegimen>
<CalificacionOperacion></CalificacionOperacion>
<OperacionExenta></OperacionExenta>
<TipoImpositivo></TipoImpositivo>
<BaseImponibleOimporteNoSujeto></BaseImponibleOimporteNoSujeto>
<TipoImpositivo2></TipoImpositivo2>
<CuotaRecargoEquivalencia></CuotaRecargoEquivalencia>
<TipoRecargoEquivalencia></TipoRecargoEquivalencia>
<CuotaRepercutida></CuotaRepercutida>
</DetalleDesglose>`;
const newXml = new xmldom_1.DOMParser().parseFromString(template.replace(/>\s+</g, "><"), "application/xml");
// TipoImpositivo, CuotaRepercutida, TipoRecargoEquivalencia y CuotaRecargoEquivalencia
if (vatLine.vatOperation.startsWith('S')) {
(0, utils_1.updateDocument)(newXml, [
['Impuesto', vatLine.tax || '01', utils_1.toString],
['ClaveRegimen', vatLine.vatKey, utils_1.toString],
['CalificacionOperacion', vatLine.vatOperation, utils_1.toString],
['TipoImpositivo', vatLine.rate, utils_1.round2ToString],
['BaseImponibleOimporteNoSujeto', vatLine.base, utils_1.round2ToString],
['TipoImpositivo2', vatLine.rate2, utils_1.round2ToString],
['CuotaRecargoEquivalencia', vatLine.amount2, utils_1.round2ToString],
['CuotaRepercutida', vatLine.amount, utils_1.round2ToString],
]);
}
else {
(0, utils_1.updateDocument)(newXml, [
['Impuesto', vatLine.tax || '01', utils_1.toString],
['ClaveRegimen', vatLine.vatKey, utils_1.toString],
['CalificacionOperacion', vatLine.vatOperation, utils_1.toString],
['BaseImponibleOimporteNoSujeto', vatLine.base, utils_1.round2ToString],
['TipoImpositivo2', vatLine.rate2, utils_1.round2ToString],
]);
}
if (newXml.documentElement) {
desglose.appendChild(newXml.documentElement);
}
});
}
function addSoftwareToXml(xml, software) {
const selectorsToValues = [
["SistemaInformatico>NombreRazon", software.developerName, utils_1.toStr120],
["SistemaInformatico>NIF", software.developerIrsId, utils_1.toNifStr],
["SistemaInformatico>NombreSistemaInformatico", software.name, utils_1.toStr30],
["SistemaInformatico>IdSistemaInformatico", software.id, utils_1.toStr30],
["SistemaInformatico>Version", software.version, utils_1.toStr50],
["SistemaInformatico>NumeroInstalacion", software.number, utils_1.toStr100],
["SistemaInformatico>TipoUsoPosibleSoloVerifactu", software.useOnlyVerifactu, utils_1.toBooleanString],
["SistemaInformatico>TipoUsoPosibleMultiOT", software.useMulti, utils_1.toBooleanString],
["SistemaInformatico>IndicadorMultiplesOT", software.useCurrentMulti, utils_1.toBooleanString],
];
(0, utils_1.updateDocument)(xml, selectorsToValues);
}
function addPreviousInvoiceToXml(xml, previousId) {
if (previousId) {
(0, utils_1.querySelectorAll)(xml, 'PrimerRegistro').forEach(utils_1.removeElement);
const selectorsToValues = [
["RegistroAnterior>IDEmisorFactura", previousId.issuerIrsId, utils_1.toNifStr],
["RegistroAnterior>NumSerieFactura", previousId.number, utils_1.toStr60],
["RegistroAnterior>FechaExpedicionFactura", previousId.issuedTime, utils_1.toDateString],
["RegistroAnterior>Huella", previousId.hash, utils_1.toStr64],
];
(0, utils_1.updateDocument)(xml, selectorsToValues);
}
else {
(0, utils_1.querySelectorAll)(xml, 'RegistroAnterior').forEach(utils_1.removeElement);
}
}
async function generateHash(invoice, dateGenReg, previousHash) {
let hashString;
if ('vatLines' in invoice) {
// Invoice
hashString = [
`IDEmisorFactura=${invoice.issuer.irsId}`,
`NumSerieFactura=${invoice.id.number}`,
`FechaExpedicionFactura=${(0, utils_1.toDateString)(invoice.id.issuedTime)}`,
`TipoFactura=${invoice.type}`,
`CuotaTotal=${(0, utils_1.round2ToString)(invoice.amount)}`,
`ImporteTotal=${(0, utils_1.round2ToString)(invoice.total)}`,
`Huella=${previousHash}`,
`FechaHoraHusoGenRegistro=${dateGenReg}`,
].join("&");
}
else {
// CancelInvoice
hashString = [
`IDEmisorFacturaAnulada=${invoice.issuer.irsId}`,
`NumSerieFacturaAnulada=${invoice.id.number}`,
`FechaExpedicionFacturaAnulada=${(0, utils_1.toDateString)(invoice.id.issuedTime)}`,
`Huella=${previousHash}`,
`FechaHoraHusoGenRegistro=${dateGenReg}`,
].join("&");
}
const hash = crypto.createHash('sha256');
hash.update(hashString, 'utf8');
return hash.digest('hex').toUpperCase();
}
function getText(xml, selector) {
const element = (0, utils_1.querySelector)(xml, selector);
return element ? element.textContent || '' : '';
}
function getVerifactuUrl(xml, isTesting = false) {
const prefix = isTesting
? "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR"
: "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR";
const issuer = getText(xml, "IDFactura>IDEmisorFactura");
const number = getText(xml, "IDFactura>NumSerieFactura");
const date = getText(xml, "IDFactura>FechaExpedicionFactura");
const total = getText(xml, "ImporteTotal");
const hash = getText(xml, "Huella");
const params = new URLSearchParams({
nif: issuer,
numserie: number,
fecha: date,
importe: total,
hash: hash.slice(-6) // últimos 6 caracteres del hash
});
return `${prefix}?${params.toString()}`;
}
function completeXml(xml, vat, name) {
return `
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sum="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd"
xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" >
<soap:Body>
<sum:RegFactuSistemaFacturacion>
<sum:Cabecera>
<sum1:ObligadoEmision>
<sum1:NombreRazon>${name}</sum1:NombreRazon>
<sum1:NIF>${vat}</sum1:NIF>
</sum1:ObligadoEmision>
</sum:Cabecera>
${completeXmlReplaces(xml)}
</sum:RegFactuSistemaFacturacion>
</soap:Body>
</soap:Envelope>`
.replace(/>\s+</g, "><")
.replace(/\s*xmlns/g, " xmlns");
}
function completeXmlReplaces(xml) {
// Add xmlns prefix to nodes without it
xml = AddXmlnsPrefixToNodesWithoutXmlnsPrefix(xml.replace(NS1, '')
.replace(NS2, ''), 'sum1');
// Replace the empty nodes with empty strings
xml = replaceEmptyNodes(xml);
// Remove xmlns attributes from sum1 namespace
xml = xml.replace(/ xmlns="[^"]*"/g, '');
// Remove xmlns attributes from sum namespace
xml = xml.replace(/ xmlns:sum="[^"]*"/g, '');
xml = xml.replace(/<([^>]*?)\s+>/g, '<$1>');
return xml;
}
function replaceEmptyNodes(xml) {
xml = replaceEmptyNode(xml, 'sum1:TipoRectificativa');
xml = replaceEmptyNode(xml, 'sum1:FacturasRectificadas');
xml = replaceEmptyNode(xml, 'sum1:FacturasSustituidas');
xml = replaceEmptyNode(xml, 'sum1:ImporteRectificacion');
xml = replaceEmptyNode(xml, 'sum1:FechaOperacion');
xml = replaceEmptyNode(xml, 'sum1:OperacionExenta');
xml = replaceEmptyNode(xml, 'sum1:TipoRecargoEquivalencia');
xml = replaceEmptyNode(xml, 'sum1:EmitidaPorTerceroODestinatario');
xml = replaceEmptyNode(xml, 'sum1:Subsanacion');
xml = replaceEmptyNode(xml, 'sum1:TipoImpositivo');
xml = replaceEmptyNode(xml, 'sum1:CuotaRepercutida');
return xml;
}
function replaceEmptyNode(xml, node) {
// should replace <sum1:TipoRectificativa/> to ''
const regex = new RegExp(`<${node}[^>]*?/>`, 'g');
return xml.replace(regex, '');
}
// <sum1:TipoRectificativa/>
function AddXmlnsPrefixToNodesWithoutXmlnsPrefix(xml, prefix) {
// all tags like <TipoHuella> should be changed to <prefix:TipoHuella> . Also,
// </TipoHuella> should be changed to </prefix:TipoHuella>
// Tags with other prefixes should not be changed.
// Replace opening tags that don't have a namespace prefix (no colon in the tag name)
xml = xml.replace(/<([a-zA-Z][a-zA-Z0-9]*)\b(?![^>]*:)([^>]*)>/g, `<${prefix}:$1$2>`);
// Replace closing tags that don't have a namespace prefix (no colon in the tag name)
xml = xml.replace(/<\/([a-zA-Z][a-zA-Z0-9]*)(?!:)>/g, `</${prefix}:$1>`);
return xml;
}
function getChainInfo(xml) {
function getIssuedDate() {
const d = getText(xml, "IDFactura>FechaExpedicionFactura");
return new Date(d.split("-").reverse().join("-"));
}
return {
issuerIrsId: getText(xml, "IDFactura>IDEmisorFactura"),
issuedTime: getIssuedDate(),
number: getText(xml, "IDFactura>NumSerieFactura"),
hash: getText(xml, "Huella").replace(/\s/g, ""),
};
}
function getCancelChainInfo(xml) {
function getIssuedDate() {
const d = getText(xml, "IDFactura>FechaExpedicionFacturaAnulada");
return new Date(d.split("-").reverse().join("-"));
}
return {
issuerIrsId: getText(xml, "IDFactura>IDEmisorFacturaAnulada"),
issuedTime: getIssuedDate(),
number: getText(xml, "IDFactura>NumSerieFacturaAnulada"),
hash: getText(xml, "Huella").replace(/\s/g, ""),
};
}
function getWSInfo(isTesting) {
const wsld = isTesting
? "https://prewww1.aeat.es/static_files/common/internet/dep/aplicaciones/es/aeat/tikeV1.0/cont/ws/SistemaFacturacion.wsdl"
: "https://www1.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tikeV1.0/cont/ws/SistemaFacturacion.wsdl";
const endpoint = isTesting
? "https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP"
: "https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP";
return { wsld, endpoint };
}
async function createInvoice(invoice, software, previousId = null, options = {}, isTesting = true) {
// Validaciones
validateInvoice(invoice);
validateSoftware(software);
const dateGenReg = new Date().toISOString();
const xml = new xmldom_1.DOMParser().parseFromString(VERIFACTU_INVOICE_XML_BASE, "application/xml");
// Datos básicos de la factura
const selectorsToValues = [
["IDFactura>IDEmisorFactura", invoice.issuer.irsId, utils_1.toNifStr],
["IDFactura>NumSerieFactura", invoice.id.number, utils_1.toStr60],
["IDFactura>FechaExpedicionFactura", invoice.id.issuedTime, utils_1.toDateString],
["NombreRazonEmisor", invoice.issuer.name, utils_1.toStr120],
["TipoFactura", invoice.type, utils_1.toString],
["DescripcionOperacion", invoice.description?.text || "", utils_1.toStr500],
["EmitidaPorTerceroODestinatario", invoice.issuedBy || "", utils_1.toString],
["CuotaTotal", invoice.amount, utils_1.round2ToString],
["ImporteTotal", invoice.total, utils_1.round2ToString],
["FechaHoraHusoGenRegistro", dateGenReg, utils_1.toStr30],
["Subsanacion", invoice.id.replacement ? "S" : "", utils_1.toStr30],
["TipoRectificativa", invoice.type === "R1" ? "I" : "", utils_1.toStr2]
];
(0, utils_1.updateDocument)(xml, selectorsToValues);
// Remove Tercero section if invoice is not issued by third party
if ((invoice.issuedBy || "N") === "N") {
const terceroElement = (0, utils_1.querySelector)(xml, "Tercero");
if (terceroElement) {
(0, utils_1.removeElement)(terceroElement);
}
}
// Agregar destinatario si existe
addRecipientToXml(xml, invoice.recipient);
// Agregar líneas de IVA
addVatLinesToXml(xml, invoice.vatLines);
// Calcular y añadir información de retención IRPF
let retentionAmount = 0;
// if (invoice.retentionAmount !== undefined) {
// retentionAmount = invoice.retentionAmount;
// } else if (invoice.retentionLines && invoice.retentionLines.length > 0) {
// const retentionLinesFull = completeRetentionLines(invoice.retentionLines);
// retentionAmount = computeRetentionTotal(retentionLinesFull);
// }
// Añadir retención al XML si es mayor que 0
if (retentionAmount > 0) {
const retentionElement = (0, utils_1.querySelector)(xml, "RetencionSoportada");
if (retentionElement) {
retentionElement.textContent = (0, utils_1.round2ToString)(retentionAmount);
}
}
else {
// Eliminar el elemento si no hay retención
(0, utils_1.querySelectorAll)(xml, "RetencionSoportada").forEach(utils_1.removeElement);
}
// Agregar información del software
addSoftwareToXml(xml, software);
// Agregar información de encadenamiento
addPreviousInvoiceToXml(xml, previousId);
// Generar hash
const previousHash = previousId?.hash || "";
const hash = await generateHash(invoice, dateGenReg, previousHash);
// Find the final Huella element (not the one inside RegistroAnterior)
const huellaElements = (0, utils_1.querySelectorAll)(xml, "Huella");
const hashElement = huellaElements.find(el => {
// Find the Huella that is NOT inside RegistroAnterior
let parent = el.parentNode;
while (parent) {
if (parent.tagName === 'RegistroAnterior' || parent.localName === 'RegistroAnterior') {
return false; // Skip this one, it's inside RegistroAnterior
}
parent = parent.parentNode;
}
return true; // This is the final Huella element
});
if (hashElement) {
hashElement.textContent = hash;
}
// Generar QR code
const url = getVerifactuUrl(xml, isTesting);
const qrcodeData = await qrcode.toDataURL(url);
// Obtener información de encadenamiento
const chainInfo = getChainInfo(xml);
// Convertir XML a string y codificar en base64
const xmlString = completeXml(new xmldom_1.XMLSerializer().serializeToString(xml), (0, utils_1.toNifStr)(invoice.issuer.irsId), (0, utils_1.toStr60)(invoice.issuer.name));
const verifactuXml = Buffer.from(xmlString).toString('base64');
const { wsld, endpoint } = getWSInfo(isTesting);
return {
qrcode: qrcodeData,
chainInfo,
verifactuXml,
wsld,
endpoint,
hash
};
}
async function cancelInvoice(cancelInvoice, software, previousId = null, options = {}, isTesting = true) {
// Validaciones
validateCancelInvoice(cancelInvoice);
validateSoftware(software);
const dateGenReg = new Date().toISOString();
const xml = new xmldom_1.DOMParser().parseFromString(VERIFACTU_CANCEL_XML_BASE, "application/xml");
// Datos básicos de la anulación
const selectorsToValues = [
["IDFactura>IDEmisorFacturaAnulada", cancelInvoice.issuer.irsId, utils_1.toNifStr],
["IDFactura>NumSerieFacturaAnulada", cancelInvoice.id.number, utils_1.toStr60],
["IDFactura>FechaExpedicionFacturaAnulada", cancelInvoice.id.issuedTime, utils_1.toDateString],
["FechaHoraHusoGenRegistro", dateGenReg, utils_1.toStr30],
];
(0, utils_1.updateDocument)(xml, selectorsToValues);
// Agregar información del software
addSoftwareToXml(xml, software);
// Agregar información de encadenamiento
addPreviousInvoiceToXml(xml, previousId);
// Generar hash
const previousHash = previousId?.hash || "";
const hash = await generateHash(cancelInvoice, dateGenReg, previousHash);
// Find the final Huella element (not the one inside RegistroAnterior)
const huellaElements = (0, utils_1.querySelectorAll)(xml, "Huella");
const hashElement = huellaElements.find(el => {
// Find the Huella that is NOT inside RegistroAnterior
let parent = el.parentNode;
while (parent) {
if (parent.tagName === 'RegistroAnterior' || parent.localName === 'RegistroAnterior') {
return false; // Skip this one, it's inside RegistroAnterior
}
parent = parent.parentNode;
}
return true; // This is the final Huella element
});
if (hashElement) {
hashElement.textContent = hash;
}
// Obtener información de encadenamiento para la anulación
const chainInfo = getCancelChainInfo(xml);
// Convertir XML a string y codificar en base64
const xmlString = completeXml(new xmldom_1.XMLSerializer().serializeToString(xml), (0, utils_1.toNifStr)(cancelInvoice.issuer.irsId), (0, utils_1.toStr60)(cancelInvoice.issuer.name));
const verifactuXml = Buffer.from(xmlString).toString('base64');
const { wsld, endpoint } = getWSInfo(isTesting);
return {
qrcode: null, // Las anulaciones no generan QR
chainInfo,
verifactuXml,
wsld,
endpoint,
hash
};
}
//# sourceMappingURL=verifactu.js.map