-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
Buenas, estoy usando nodejs 20 con typescript para generar el xml usando verifactu-node-lib y y firmandolo antes de enviar a la AEAT, pero siempre me da ese error, te anexo lo que estoy haciendo a ver si me pueds echar una mano.
import {
createVerifactuInvoice,
Invoice as VfInvoice,
Software as VfSoftware,
} from "verifactu-node-lib";
async function submitInvoice(req: Request, res: Response) {
const { invoiceNumber } = req.params;
const factura = await InvoiceModel.findOne({ invoiceNumber });
if (!factura) {
return res.status(404).json({ error: "Factura no encontrada" });
}
const { registro } = factura;
/* 2. Configura software para la librería */
const software: VfSoftware = {
developerName: "Alba Centro de Bienestar Emocional SLP",
developerIrsId: "B75935544",
name: "centroalba",
id: "AA",
version: "1.0.0",
number: "001",
useOnlyVerifactu: true,
useMulti: false,
useCurrentMulti: false,
};
/* 3. Mapea a modelo de la librería */
const invoice: VfInvoice = {
issuer: {
irsId: registro.IDEmisorFactura,
name: "Alba Centro de Bienestar Emocional SLP",
},
recipient: {
irsId: "00000000T",
name: "Paciente anónimo",
country: "ES",
},
id: {
number: registro.NumSerieFactura,
issuedTime: new Date(registro.FechaExpedicionFactura),
},
type: registro.TipoFactura as any,
description: {
text: "Consulta psicológica",
operationDate: new Date(registro.FechaExpedicionFactura),
},
vatLines: [
{
vatOperation: "S1",
base: +(parseFloat(registro.ImporteTotal) / 1.21).toFixed(2),
rate: 21,
amount: +registro.CuotaTotal,
vatKey: "01",
},
],
total: +registro.ImporteTotal,
amount: +registro.CuotaTotal,
};
/* 4. Genera RegistroAlta y huella */
const {
verifactuXml, // base64
hash: huella,
} = await createVerifactuInvoice(invoice, software, null, {}, true);
factura.huella = huella;
const registroAltaXML = Buffer.from(verifactuXml, "base64").toString();
/* 5. Firma XAdES SOLO al <sf1:RegistroAlta> */
const certPath = path.resolve(__dirname, "../../../certs/cert.pem");
const keyPath = path.resolve(__dirname, "../../../certs/key-pkcs8.pem");
const xmlFirmado = await firmarXades(registroAltaXML, certPath, keyPath); // Retorna RegistroAlta firmado
factura.xmlFirmado = xmlFirmado;
/* 6. Construye sobre SOAP */
const soapEnvelope = buildSoapEnvelope(xmlFirmado, huella);
factura.xmlSOAP = soapEnvelope;
/* 7. Envía a VeriFACTU (entorno pruebas) */
const httpsAgent = new https.Agent({
cert: fs.readFileSync(certPath, "utf8"),
key: fs.readFileSync(keyPath, "utf8"),
minVersion: "TLSv1.2",
maxVersion: "TLSv1.2",
rejectUnauthorized: false,
});
const { data } = await axios.post(
"https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP",
soapEnvelope,
{
httpsAgent,
timeout: 20000,
headers: {
"Content-Type": "text/xml;charset=UTF-8",
SOAPAction: "RegFactuSistemaFacturacion",
"User-Agent": "VeriFACTU Client",
Accept: "text/xml",
"Cache-Control": "no-cache",
Pragma: "no-cache",
},
},
);
console.log(data);
/* 8. Guarda y responde */
factura.estado = "enviada";
factura.AEATResponse = data;
await factura.save();
res.json({ message: "Factura enviada a la AEAT (VeriFACTU).", huella });
}
mi metodo firmarXades hace lo siguiente:
import fs from "fs";
import * as xmldom from "xmldom";
import * as xadesjs from "xadesjs";
import { X509Certificate } from "@peculiar/x509";
export async function firmarXades(
xmlStr: string,
certPath: string,
keyPath: string,
): Promise<string> {
const cert = fs.readFileSync(certPath, "utf-8");
const key = fs.readFileSync(keyPath, "utf-8");
const xmlDoc = new xmldom.DOMParser().parseFromString(
xmlStr,
"application/xml",
);
const xml = new xadesjs.SignedXml();
const cryptoKey = await xadesjs.Application.crypto.subtle.importKey(
"pkcs8",
Buffer.from(
key.replace(/-----(BEGIN|END) PRIVATE KEY-----|\n/g, ""),
"base64",
),
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
false,
["sign"],
);
const x509 = new X509Certificate(cert);
// Validación: ¿caducado?
if (x509.notAfter.getTime() < Date.now()) {
throw new Error("El certificado está caducado");
}
// Convertir el certificado a base64
let x509Base64 = "";
if (x509.rawData) {
x509Base64 = Buffer.from(x509.rawData).toString("base64");
} else {
x509Base64 = cert
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.replace(/\s+/g, "");
}
await xml.Sign(
{ name: "RSASSA-PKCS1-v1_5" },
cryptoKey,
xmlDoc.documentElement,
{
id: `xmldsig-${Date.now().toString()}`, // Añadir ID único para la firma
x509: [x509Base64],
references: [
{
id: `r-id-${Date.now().toString()}`,
uri: "",
transforms: ["enveloped"],
hash: "SHA-256",
},
],
policy: {
hash: "SHA-1",
identifier: {
value: "urn:oid:2.16.724.1.3.1.1.2.1.9",
// @ts-ignore
qualifiers: [
{
value:
"https://sede.administracion.gob.es/politica_de_firma_anexo_1.pdf",
},
],
},
digestValue: "ELVALUE=",
},
},
);
const signature = xml.XmlSignature.GetXml();
xmlDoc.documentElement.appendChild(signature);
return new xmldom.XMLSerializer().serializeToString(xmlDoc);
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels