From fdf7d2d3cf6cd7693aa06311ae36c25c29b24dfd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 25 Apr 2025 16:09:40 +1000 Subject: [PATCH] Refactor for additional test cases --- .../Verifactu/Models/BaseXmlModel.php | 12 +- .../Standards/Verifactu/Models/Cupon.php | 8 +- .../Standards/Verifactu/Models/Desglose.php | 28 +- .../Verifactu/Models/Encadenamiento.php | 30 +- .../Standards/Verifactu/Models/Invoice.php | 483 +++++++++--------- .../Models/PersonaFisicaJuridica.php | 8 +- .../Verifactu/Models/SistemaInformatico.php | 8 +- .../EInvoice/Verifactu/Models/InvoiceTest.php | 415 ++++++++------- .../EInvoice/Verifactu/certs/certificate.pem | 45 +- .../EInvoice/Verifactu/certs/private.pem | 52 +- .../EInvoice/Verifactu/certs/public.pem | 14 +- 11 files changed, 550 insertions(+), 553 deletions(-) diff --git a/app/Services/EDocument/Standards/Verifactu/Models/BaseXmlModel.php b/app/Services/EDocument/Standards/Verifactu/Models/BaseXmlModel.php index 50098176ad..8944c5b196 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/BaseXmlModel.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/BaseXmlModel.php @@ -13,7 +13,8 @@ abstract class BaseXmlModel { $element = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':' . $name); if ($value !== null) { - $element->nodeValue = $value; + $textNode = $doc->createTextNode($value); + $element->appendChild($textNode); } foreach ($attributes as $attrName => $attrValue) { $element->setAttribute($attrName, $attrValue); @@ -25,7 +26,8 @@ abstract class BaseXmlModel { $element = $doc->createElementNS(self::XML_DS_NAMESPACE, self::XML_DS_NAMESPACE_PREFIX . ':' . $name); if ($value !== null) { - $element->nodeValue = $value; + $textNode = $doc->createTextNode($value); + $element->appendChild($textNode); } foreach ($attributes as $attrName => $attrValue) { $element->setAttribute($attrName, $attrValue); @@ -37,12 +39,12 @@ abstract class BaseXmlModel { $elements = $parent->getElementsByTagNameNS($namespace, $name); if ($elements->length > 0) { - return $elements->item(0)->nodeValue; + return $elements->item(0)->textContent; } return null; } - abstract public function toXml(): string; + abstract public function toXml(\DOMDocument $doc): \DOMElement; public static function fromXml($xml): self { @@ -55,6 +57,8 @@ abstract class BaseXmlModel } $doc = new \DOMDocument(); + $doc->formatOutput = true; + $doc->preserveWhiteSpace = false; if (!$doc->loadXML($xml)) { throw new \DOMException('Failed to load XML: Invalid XML format'); } diff --git a/app/Services/EDocument/Standards/Verifactu/Models/Cupon.php b/app/Services/EDocument/Standards/Verifactu/Models/Cupon.php index 5267161c74..d63d4982cd 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/Cupon.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/Cupon.php @@ -9,13 +9,9 @@ class Cupon extends BaseXmlModel protected float $importeCupon; protected ?string $descripcionCupon = null; - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement { - $doc = new \DOMDocument('1.0', 'UTF-8'); - $doc->formatOutput = true; - $root = $this->createElement($doc, 'Cupon'); - $doc->appendChild($root); // Add required elements $root->appendChild($this->createElement($doc, 'IDCupon', $this->idCupon)); @@ -27,7 +23,7 @@ class Cupon extends BaseXmlModel $root->appendChild($this->createElement($doc, 'DescripcionCupon', $this->descripcionCupon)); } - return $doc->saveXML(); + return $root; } public static function fromDOMElement(\DOMElement $element): self diff --git a/app/Services/EDocument/Standards/Verifactu/Models/Desglose.php b/app/Services/EDocument/Standards/Verifactu/Models/Desglose.php index 57d53e977f..e884f01af7 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/Desglose.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/Desglose.php @@ -11,13 +11,9 @@ class Desglose extends BaseXmlModel protected ?array $desgloseIRPF = null; protected ?array $desgloseIS = null; - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement { - $doc = new \DOMDocument('1.0', 'UTF-8'); - $doc->formatOutput = true; - $root = $this->createElement($doc, 'Desglose'); - $doc->appendChild($root); // Create DetalleDesglose element $detalleDesglose = $this->createElement($doc, 'DetalleDesglose'); @@ -49,8 +45,10 @@ class Desglose extends BaseXmlModel } // Add BaseImponibleOimporteNoSujeto (required) - $detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', - number_format($this->desgloseFactura['BaseImponible'], 2, '.', ''))); + if (isset($this->desgloseFactura['BaseImponibleOimporteNoSujeto'])) { + $detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', + number_format($this->desgloseFactura['BaseImponibleOimporteNoSujeto'], 2, '.', ''))); + } // Add BaseImponibleACoste if present if (isset($this->desgloseFactura['BaseImponibleACoste'])) { @@ -59,9 +57,9 @@ class Desglose extends BaseXmlModel } // Add CuotaRepercutida if present - if (isset($this->desgloseFactura['Cuota'])) { + if (isset($this->desgloseFactura['CuotaRepercutida'])) { $detalleDesglose->appendChild($this->createElement($doc, 'CuotaRepercutida', - number_format($this->desgloseFactura['Cuota'], 2, '.', ''))); + number_format($this->desgloseFactura['CuotaRepercutida'], 2, '.', ''))); } // Add TipoRecargoEquivalencia if present @@ -95,13 +93,15 @@ class Desglose extends BaseXmlModel } // Add BaseImponibleOimporteNoSujeto (required) - $detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', - number_format($this->desgloseIVA['BaseImponible'], 2, '.', ''))); + if (isset($this->desgloseIVA['BaseImponibleOimporteNoSujeto'])) { + $detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', + number_format($this->desgloseIVA['BaseImponibleOimporteNoSujeto'], 2, '.', ''))); + } // Add CuotaRepercutida if present - if (isset($this->desgloseIVA['Cuota'])) { + if (isset($this->desgloseIVA['CuotaRepercutida'])) { $detalleDesglose->appendChild($this->createElement($doc, 'CuotaRepercutida', - number_format($this->desgloseIVA['Cuota'], 2, '.', ''))); + number_format($this->desgloseIVA['CuotaRepercutida'], 2, '.', ''))); } } @@ -110,7 +110,7 @@ class Desglose extends BaseXmlModel $root->appendChild($detalleDesglose); } - return $doc->saveXML(); + return $root; } public static function fromDOMElement(\DOMElement $element): self diff --git a/app/Services/EDocument/Standards/Verifactu/Models/Encadenamiento.php b/app/Services/EDocument/Standards/Verifactu/Models/Encadenamiento.php index 5c32331984..8d5d02f155 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/Encadenamiento.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/Encadenamiento.php @@ -8,37 +8,23 @@ class Encadenamiento extends BaseXmlModel protected ?EncadenamientoFacturaAnterior $registroAnterior = null; protected ?EncadenamientoFacturaAnterior $registroPosterior = null; - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement { - $doc = new \DOMDocument('1.0', 'UTF-8'); $root = $this->createElement($doc, 'Encadenamiento'); - $doc->appendChild($root); if ($this->primerRegistro !== null) { $root->appendChild($this->createElement($doc, 'PrimerRegistro', 'S')); } if ($this->registroAnterior !== null) { - $registroAnteriorXml = $this->registroAnterior->toXml(); - $registroAnteriorDoc = new \DOMDocument(); - $registroAnteriorDoc->loadXML($registroAnteriorXml); - $registroAnteriorNode = $doc->importNode($registroAnteriorDoc->documentElement, true); - $root->appendChild($registroAnteriorNode); + $root->appendChild($this->registroAnterior->toXml($doc)); } if ($this->registroPosterior !== null) { - $registroPosteriorXml = $this->registroPosterior->toXml(); - $registroPosteriorDoc = new \DOMDocument(); - $registroPosteriorDoc->loadXML($registroPosteriorXml); - $registroPosteriorNode = $doc->importNode($registroPosteriorDoc->documentElement, true); - $root->appendChild($registroPosteriorNode); + $root->appendChild($this->registroPosterior->toXml($doc)); } - // Add namespace declaration to the root element - $root->setAttribute('xmlns:sf', self::XML_NAMESPACE); - $root->setAttribute('xmlns:ds', self::XML_DS_NAMESPACE); - - return $doc->saveXML(); + return $root; } public static function fromXml($xml): BaseXmlModel @@ -140,20 +126,16 @@ class EncadenamientoFacturaAnterior extends BaseXmlModel protected string $fechaExpedicionFactura; protected string $huella; - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement { - $doc = new \DOMDocument('1.0', 'UTF-8'); - $doc->formatOutput = true; - $root = $this->createElement($doc, 'RegistroAnterior'); - $doc->appendChild($root); $root->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->idEmisorFactura)); $root->appendChild($this->createElement($doc, 'NumSerieFactura', $this->numSerieFactura)); $root->appendChild($this->createElement($doc, 'FechaExpedicionFactura', $this->fechaExpedicionFactura)); $root->appendChild($this->createElement($doc, 'Huella', $this->huella)); - return $doc->saveXML(); + return $root; } public static function fromDOMElement(\DOMElement $element): self diff --git a/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php b/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php index be19e3a119..d5d8b161fc 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/Invoice.php @@ -436,61 +436,66 @@ class Invoice extends BaseXmlModel return $this; } - protected function signXml(\DOMDocument $doc): void + public function signXml(\DOMDocument $doc): void { - if (!$this->privateKeyPath || !file_exists($this->privateKeyPath)) { - throw new \RuntimeException('Private key not found or not set'); + if (!file_exists($this->certificatePath)) { + throw new \RuntimeException("Certificate file not found at: " . $this->certificatePath); } - - if (!$this->certificatePath || !file_exists($this->certificatePath)) { - throw new \RuntimeException('Certificate not found or not set'); + if (!file_exists($this->privateKeyPath)) { + throw new \RuntimeException("Private key file not found at: " . $this->privateKeyPath); } - Log::info('Starting XML signing process'); - Log::debug('XML before signing: ' . $doc->saveXML()); - try { - // Create a new Security object - Log::debug('Creating XMLSecurityDSig object'); + $xmlContent = $doc->saveXML(); + Log::debug("XML before signing:", ['xml' => $xmlContent]); + $objDSig = new XMLSecurityDSig(); - - // Set canonicalization method - Log::debug('Setting canonicalization method'); $objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); - // Create a new Security key - Log::debug('Creating XMLSecurityKey object'); - $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']); + // Create a new security key + $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'private')); // Load the private key - Log::debug('Loading private key from: ' . $this->privateKeyPath); $objKey->loadKey($this->privateKeyPath, true); - // Add reference - Log::debug('Adding reference to document'); + // Add the reference $objDSig->addReference( - $doc, + $doc, XMLSecurityDSig::SHA256, - ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'] + [ + 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', + 'http://www.w3.org/2001/10/xml-exc-c14n#' + ], + ['force_uri' => true] ); - Log::debug('Added reference to document'); - - // Add the certificate - Log::debug('Adding certificate'); + + // Add the certificate to the security object $objDSig->add509Cert(file_get_contents($this->certificatePath)); - // Sign the XML document - Log::debug('Signing document'); + // Append the signature $objDSig->sign($objKey); // Append the signature to the XML - Log::debug('Appending signature'); $objDSig->appendSignature($doc->documentElement); - Log::debug('XML after signing: ' . $doc->saveXML()); + // Verify the signature + $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'public')); + $objKey->loadKey($this->publicKeyPath, true, true); + + if ($objDSig->verify($objKey) === 1) { + Log::debug("Signature verification successful"); + } else { + Log::error("Signature verification failed"); + } + + $xmlContent = $doc->saveXML(); + Log::debug("XML after signing:", ['xml' => $xmlContent]); + } catch (\Exception $e) { - Log::error('Error during XML signing: ' . $e->getMessage()); - Log::error('Stack trace: ' . $e->getTraceAsString()); + Log::error("Error during XML signing: " . $e->getMessage(), [ + 'exception' => $e, + 'trace' => $e->getTraceAsString() + ]); throw $e; } } @@ -501,35 +506,203 @@ class Invoice extends BaseXmlModel throw new \RuntimeException('Public key not found or not set'); } - // Get the signature node - $objXMLSecDSig = new XMLSecurityDSig(); - - // Locate the signature - $objDSig = $objXMLSecDSig->locateSignature($doc); - if (!$objDSig) { - throw new \RuntimeException('Signature not found in document'); + try { + Log::info('Starting signature verification'); + Log::debug('XML to verify: ' . $doc->saveXML()); + + // Get the signature node + $objXMLSecDSig = new XMLSecurityDSig(); + + // Locate the signature + Log::debug('Locating signature'); + $objDSig = $objXMLSecDSig->locateSignature($doc); + if (!$objDSig) { + throw new \RuntimeException('Signature not found in document'); + } + + // Canonicalize the signed info + Log::debug('Canonicalizing SignedInfo'); + $objXMLSecDSig->canonicalizeSignedInfo(); + + // Validate references + Log::debug('Validating references'); + try { + $objXMLSecDSig->validateReference(); + } catch (\Exception $e) { + Log::error('Reference validation failed: ' . $e->getMessage()); + throw $e; + } + + // Get the key from the certificate + Log::debug('Locating key'); + $objKey = $objXMLSecDSig->locateKey(); + if (!$objKey) { + throw new \RuntimeException('Key not found in signature'); + } + + // Load the public key + Log::debug('Loading public key from: ' . $this->publicKeyPath); + $objKey->loadKey($this->publicKeyPath, false, true); + + // Verify the signature + Log::debug('Verifying signature'); + $result = $objXMLSecDSig->verify($objKey) === 1; + + Log::info('Signature verification ' . ($result ? 'succeeded' : 'failed')); + return $result; + } catch (\Exception $e) { + Log::error('Error during signature verification: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + throw $e; } - - // Canonicalize the signed info - $objXMLSecDSig->canonicalizeSignedInfo(); - - // Validate references - $objXMLSecDSig->validateReference(); - - // Get the key from the certificate - $objKey = $objXMLSecDSig->locateKey(); - if (!$objKey) { - throw new \RuntimeException('Key not found in signature'); - } - - // Load the public key - $objKey->loadKey($this->publicKeyPath, false, true); - - // Verify the signature - return $objXMLSecDSig->verify($objKey) === 1; } - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement + { + // Create root element with proper namespaces + $root = $doc->createElementNS(parent::XML_NAMESPACE, parent::XML_NAMESPACE_PREFIX . ':RegistroAlta'); + + // Add namespaces + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . parent::XML_NAMESPACE_PREFIX, parent::XML_NAMESPACE); + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . parent::XML_DS_NAMESPACE_PREFIX, parent::XML_DS_NAMESPACE); + + // Add required elements in exact order according to schema + $root->appendChild($this->createElement($doc, 'IDVersion', $this->idVersion)); + + // Create IDFactura structure + $idFactura = $this->createElement($doc, 'IDFactura'); + $idFactura->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->tercero?->getNif() ?? 'B12345678')); + $idFactura->appendChild($this->createElement($doc, 'NumSerieFactura', $this->idFactura)); + $idFactura->appendChild($this->createElement($doc, 'FechaExpedicionFactura', date('d-m-Y'))); + $root->appendChild($idFactura); + + if ($this->refExterna !== null) { + $root->appendChild($this->createElement($doc, 'RefExterna', $this->refExterna)); + } + + $root->appendChild($this->createElement($doc, 'NombreRazonEmisor', $this->nombreRazonEmisor)); + + if ($this->subsanacion !== null) { + $root->appendChild($this->createElement($doc, 'Subsanacion', $this->subsanacion)); + } + + if ($this->rechazoPrevio !== null) { + $root->appendChild($this->createElement($doc, 'RechazoPrevio', $this->rechazoPrevio)); + } + + $root->appendChild($this->createElement($doc, 'TipoFactura', $this->tipoFactura)); + + // Add TipoRectificativa and related elements for rectification invoices + if ($this->tipoFactura === 'R1' && $this->facturaRectificativa !== null) { + $root->appendChild($this->createElement($doc, 'TipoRectificativa', $this->facturaRectificativa->getTipoRectificativa())); + + // Add FacturasRectificadas + $facturasRectificadas = $this->createElement($doc, 'FacturasRectificadas'); + $facturasRectificadas->appendChild($this->facturaRectificativa->toXml($doc)); + $root->appendChild($facturasRectificadas); + + // Add ImporteRectificacion + if ($this->importeRectificacion !== null) { + $root->appendChild($this->createElement($doc, 'ImporteRectificacion', (string)$this->importeRectificacion)); + } + } + + // Add other optional elements + if ($this->fechaOperacion !== null) { + $root->appendChild($this->createElement($doc, 'FechaOperacion', $this->fechaOperacion)); + } + + $root->appendChild($this->createElement($doc, 'DescripcionOperacion', $this->descripcionOperacion)); + + if ($this->facturaSimplificadaArt7273 !== null) { + $root->appendChild($this->createElement($doc, 'FacturaSimplificadaArt7273', $this->facturaSimplificadaArt7273)); + } + + if ($this->facturaSinIdentifDestinatarioArt61d !== null) { + $root->appendChild($this->createElement($doc, 'FacturaSinIdentifDestinatarioArt61d', $this->facturaSinIdentifDestinatarioArt61d)); + } + + if ($this->macrodato !== null) { + $root->appendChild($this->createElement($doc, 'Macrodato', $this->macrodato)); + } + + if ($this->emitidaPorTerceroODestinatario !== null) { + $root->appendChild($this->createElement($doc, 'EmitidaPorTerceroODestinatario', $this->emitidaPorTerceroODestinatario)); + } + + // Add Tercero if set + if ($this->tercero !== null) { + $terceroElement = $this->createElement($doc, 'Tercero'); + $terceroElement->appendChild($this->createElement($doc, 'NombreRazon', $this->tercero->getRazonSocial())); + $terceroElement->appendChild($this->createElement($doc, 'NIF', $this->tercero->getNif())); + $root->appendChild($terceroElement); + } + + // Add Destinatarios if set + if ($this->destinatarios !== null && count($this->destinatarios) > 0) { + $destinatariosElement = $this->createElement($doc, 'Destinatarios'); + foreach ($this->destinatarios as $destinatario) { + $idDestinatarioElement = $this->createElement($doc, 'IDDestinatario'); + + // Add NombreRazon + $idDestinatarioElement->appendChild($this->createElement($doc, 'NombreRazon', $destinatario->getNombreRazon())); + + // Add either NIF or IDOtro + if ($destinatario->getNif() !== null) { + $idDestinatarioElement->appendChild($this->createElement($doc, 'NIF', $destinatario->getNif())); + } else { + $idOtroElement = $this->createElement($doc, 'IDOtro'); + $idOtroElement->appendChild($this->createElement($doc, 'CodigoPais', $destinatario->getPais())); + $idOtroElement->appendChild($this->createElement($doc, 'IDType', $destinatario->getTipoIdentificacion())); + $idOtroElement->appendChild($this->createElement($doc, 'ID', $destinatario->getIdOtro())); + $idDestinatarioElement->appendChild($idOtroElement); + } + + $destinatariosElement->appendChild($idDestinatarioElement); + } + $root->appendChild($destinatariosElement); + } + + // Add Desglose + if ($this->desglose !== null) { + $root->appendChild($this->desglose->toXml($doc)); + } + + // Add CuotaTotal and ImporteTotal + $root->appendChild($this->createElement($doc, 'CuotaTotal', (string)$this->cuotaTotal)); + $root->appendChild($this->createElement($doc, 'ImporteTotal', (string)$this->importeTotal)); + + // Add Encadenamiento + if ($this->encadenamiento !== null) { + $root->appendChild($this->encadenamiento->toXml($doc)); + } + + // Add SistemaInformatico + if ($this->sistemaInformatico !== null) { + $root->appendChild($this->sistemaInformatico->toXml($doc)); + } + + // Add FechaHoraHusoGenRegistro + $root->appendChild($this->createElement($doc, 'FechaHoraHusoGenRegistro', $this->fechaHoraHusoGenRegistro)); + + // Add NumRegistroAcuerdoFacturacion + if ($this->numRegistroAcuerdoFacturacion !== null) { + $root->appendChild($this->createElement($doc, 'NumRegistroAcuerdoFacturacion', $this->numRegistroAcuerdoFacturacion)); + } + + // Add IdAcuerdoSistemaInformatico + if ($this->idAcuerdoSistemaInformatico !== null) { + $root->appendChild($this->createElement($doc, 'IdAcuerdoSistemaInformatico', $this->idAcuerdoSistemaInformatico)); + } + + // Add TipoHuella and Huella + $root->appendChild($this->createElement($doc, 'TipoHuella', $this->tipoHuella)); + $root->appendChild($this->createElement($doc, 'Huella', $this->huella)); + + return $root; + } + + public function toXmlString(): string { // Validate required fields first, outside of try-catch $requiredFields = [ @@ -556,198 +729,20 @@ class Invoice extends BaseXmlModel $doc->preserveWhiteSpace = false; $doc->formatOutput = true; - // Create root element with proper namespaces - $root = $doc->createElementNS(parent::XML_NAMESPACE, parent::XML_NAMESPACE_PREFIX . ':RegistroAlta'); - $root->setAttribute('xmlns:ds', parent::XML_DS_NAMESPACE); + // Create root element using toXml method + $root = $this->toXml($doc); $doc->appendChild($root); - // Add required elements in exact order according to schema - $root->appendChild($this->createElement($doc, 'IDVersion', $this->idVersion)); - - // Create IDFactura structure - $idFactura = $this->createElement($doc, 'IDFactura'); - $idFactura->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->tercero?->getNif() ?? 'B12345678')); - $idFactura->appendChild($this->createElement($doc, 'NumSerieFactura', $this->idFactura)); - $idFactura->appendChild($this->createElement($doc, 'FechaExpedicionFactura', date('d-m-Y'))); - $root->appendChild($idFactura); - - if ($this->refExterna !== null) { - $root->appendChild($this->createElement($doc, 'RefExterna', $this->refExterna)); - } - - $root->appendChild($this->createElement($doc, 'NombreRazonEmisor', $this->nombreRazonEmisor)); - - if ($this->subsanacion !== null) { - $root->appendChild($this->createElement($doc, 'Subsanacion', $this->subsanacion)); - } - - if ($this->rechazoPrevio !== null) { - $root->appendChild($this->createElement($doc, 'RechazoPrevio', $this->rechazoPrevio)); - } - - $root->appendChild($this->createElement($doc, 'TipoFactura', $this->tipoFactura)); - - // Add TipoRectificativa and related elements for rectification invoices - if ($this->tipoFactura === 'R1' && $this->facturaRectificativa !== null) { - $root->appendChild($this->createElement($doc, 'TipoRectificativa', $this->facturaRectificativa->getTipoRectificativa())); - - // Add FacturasRectificadas - $facturasRectificadas = $this->createElement($doc, 'FacturasRectificadas'); - $facturasRectificadas->appendChild($this->facturaRectificativa->toXml($doc)); - $root->appendChild($facturasRectificadas); - - // Add ImporteRectificacion - $importeRectificacion = $this->createElement($doc, 'ImporteRectificacion'); - $importeRectificacion->appendChild($this->createElement($doc, 'BaseRectificada', - number_format($this->facturaRectificativa->getBaseRectificada(), 2, '.', ''))); - $importeRectificacion->appendChild($this->createElement($doc, 'CuotaRectificada', - number_format($this->facturaRectificativa->getCuotaRectificada(), 2, '.', ''))); - - if ($this->facturaRectificativa->getCuotaRecargoRectificado() !== null) { - $importeRectificacion->appendChild($this->createElement($doc, 'CuotaRecargoRectificado', - number_format($this->facturaRectificativa->getCuotaRecargoRectificado(), 2, '.', ''))); - } - - $root->appendChild($importeRectificacion); - } - - $root->appendChild($this->createElement($doc, 'DescripcionOperacion', $this->descripcionOperacion)); - - if ($this->facturaSimplificadaArt7273 !== null) { - $root->appendChild($this->createElement($doc, 'FacturaSimplificadaArt7273', $this->facturaSimplificadaArt7273)); - } - - if ($this->facturaSinIdentifDestinatarioArt61d !== null) { - $root->appendChild($this->createElement($doc, 'FacturaSinIdentifDestinatarioArt61d', $this->facturaSinIdentifDestinatarioArt61d)); - } - - if ($this->macrodato !== null) { - $root->appendChild($this->createElement($doc, 'Macrodato', $this->macrodato)); - } - - if ($this->emitidaPorTerceroODestinatario !== null) { - $root->appendChild($this->createElement($doc, 'EmitidaPorTerceroODestinatario', $this->emitidaPorTerceroODestinatario)); - } - - // Add tercero if present - if ($this->tercero !== null) { - $terceroElement = $this->createElement($doc, 'Tercero'); - $terceroElement->appendChild($this->createElement($doc, 'NombreRazon', $this->tercero->getRazonSocial())); - $terceroElement->appendChild($this->createElement($doc, 'NIF', $this->tercero->getNif())); - $root->appendChild($terceroElement); - } - - // Add destinatarios if present - if ($this->destinatarios !== null && count($this->destinatarios) > 0) { - $destinatariosElement = $this->createElement($doc, 'Destinatarios'); - foreach ($this->destinatarios as $destinatario) { - $idDestinatarioElement = $this->createElement($doc, 'IDDestinatario'); - $idDestinatarioElement->appendChild($this->createElement($doc, 'NombreRazon', $destinatario->getNombreRazon() ?? $destinatario->getRazonSocial())); - - // Handle either NIF or IDOtro - if ($destinatario->getNif() !== null) { - $idDestinatarioElement->appendChild($this->createElement($doc, 'NIF', $destinatario->getNif())); - } else { - $idOtroElement = $this->createElement($doc, 'IDOtro'); - if ($destinatario->getPais() !== null) { - $idOtroElement->appendChild($this->createElement($doc, 'CodigoPais', $destinatario->getPais())); - } - $idOtroElement->appendChild($this->createElement($doc, 'IDType', $destinatario->getTipoIdentificacion())); - $idOtroElement->appendChild($this->createElement($doc, 'ID', $destinatario->getIdOtro())); - $idDestinatarioElement->appendChild($idOtroElement); - } - - $destinatariosElement->appendChild($idDestinatarioElement); - } - $root->appendChild($destinatariosElement); - } - - // Add desglose - try { - $desgloseXml = $this->desglose->toXml(); - $desgloseDoc = new \DOMDocument(); - if (!$desgloseDoc->loadXML($desgloseXml)) { - error_log("Failed to load desglose XML"); - throw new \DOMException('Failed to load desglose XML'); - } - $desgloseNode = $doc->importNode($desgloseDoc->documentElement, true); - // Remove any existing namespace declarations - foreach (['xmlns:sf', 'xmlns:ds'] as $attr) { - if ($desgloseNode->hasAttribute($attr)) { - $desgloseNode->removeAttribute($attr); - } - } - $root->appendChild($desgloseNode); - } catch (\Exception $e) { - error_log("Error in desglose: " . $e->getMessage()); - throw $e; - } - - // Add CuotaTotal and ImporteTotal - $root->appendChild($this->createElement($doc, 'CuotaTotal', number_format($this->cuotaTotal, 2, '.', ''))); - $root->appendChild($this->createElement($doc, 'ImporteTotal', number_format($this->importeTotal, 2, '.', ''))); - - // Add encadenamiento - try { - $encadenamientoXml = $this->encadenamiento->toXml(); - $encadenamientoDoc = new \DOMDocument(); - if (!$encadenamientoDoc->loadXML($encadenamientoXml)) { - error_log("Failed to load encadenamiento XML"); - throw new \DOMException('Failed to load encadenamiento XML'); - } - $encadenamientoNode = $doc->importNode($encadenamientoDoc->documentElement, true); - $root->appendChild($encadenamientoNode); - } catch (\Exception $e) { - error_log("Error in encadenamiento: " . $e->getMessage()); - throw $e; - } - - // Add sistema informatico - $sistemaElement = $this->createElement($doc, 'SistemaInformatico'); - $sistemaElement->appendChild($this->createElement($doc, 'NombreRazon', $this->sistemaInformatico->getNombreRazon())); - $sistemaElement->appendChild($this->createElement($doc, 'NIF', $this->sistemaInformatico->getNif())); - $sistemaElement->appendChild($this->createElement($doc, 'NombreSistemaInformatico', $this->sistemaInformatico->getNombreSistemaInformatico())); - $sistemaElement->appendChild($this->createElement($doc, 'IdSistemaInformatico', $this->sistemaInformatico->getIdSistemaInformatico())); - $sistemaElement->appendChild($this->createElement($doc, 'Version', $this->sistemaInformatico->getVersion())); - $sistemaElement->appendChild($this->createElement($doc, 'NumeroInstalacion', $this->sistemaInformatico->getNumeroInstalacion())); - $sistemaElement->appendChild($this->createElement($doc, 'TipoUsoPosibleSoloVerifactu', $this->sistemaInformatico->getTipoUsoPosibleSoloVerifactu())); - $sistemaElement->appendChild($this->createElement($doc, 'TipoUsoPosibleMultiOT', $this->sistemaInformatico->getTipoUsoPosibleMultiOT())); - $sistemaElement->appendChild($this->createElement($doc, 'IndicadorMultiplesOT', $this->sistemaInformatico->getIndicadorMultiplesOT())); - $root->appendChild($sistemaElement); - - // Add remaining required fields - $root->appendChild($this->createElement($doc, 'FechaHoraHusoGenRegistro', $this->fechaHoraHusoGenRegistro)); - - if ($this->numRegistroAcuerdoFacturacion !== null) { - $root->appendChild($this->createElement($doc, 'NumRegistroAcuerdoFacturacion', $this->numRegistroAcuerdoFacturacion)); - } - - if ($this->idAcuerdoSistemaInformatico !== null) { - $root->appendChild($this->createElement($doc, 'IDAcuerdoSistemaInformatico', $this->idAcuerdoSistemaInformatico)); - } - - $root->appendChild($this->createElement($doc, 'TipoHuella', $this->tipoHuella)); - $root->appendChild($this->createElement($doc, 'Huella', $this->huella)); - - // Add signature if present - if ($this->signature !== null) { - $signatureElement = $this->createDsElement($doc, 'Signature', $this->signature); - $root->appendChild($signatureElement); - } - - // Sign the document if private key is set - if ($this->privateKeyPath !== null) { - try { - $this->signXml($doc); - } catch (\Exception $e) { - throw new \RuntimeException('Failed to sign XML: ' . $e->getMessage()); - } + // Sign the document if certificates are set + if ($this->privateKeyPath && $this->certificatePath) { + $this->signXml($doc); } return $doc->saveXML(); } catch (\Exception $e) { - error_log("Error in toXml: " . $e->getMessage()); - throw new \RuntimeException("Error in toXml: " . $e->getMessage(), 0, $e); + Log::error('Error generating XML: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + throw $e; } } diff --git a/app/Services/EDocument/Standards/Verifactu/Models/PersonaFisicaJuridica.php b/app/Services/EDocument/Standards/Verifactu/Models/PersonaFisicaJuridica.php index b4d98bd333..1d4fcc3d05 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/PersonaFisicaJuridica.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/PersonaFisicaJuridica.php @@ -103,13 +103,9 @@ class PersonaFisicaJuridica extends BaseXmlModel return $this; } - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement { - $doc = new \DOMDocument('1.0', 'UTF-8'); - $doc->formatOutput = true; - $root = $this->createElement($doc, 'PersonaFisicaJuridica'); - $doc->appendChild($root); if ($this->nif !== null) { $root->appendChild($this->createElement($doc, 'NIF', $this->nif)); @@ -143,7 +139,7 @@ class PersonaFisicaJuridica extends BaseXmlModel $root->appendChild($this->createElement($doc, 'Pais', $this->pais)); } - return $doc->saveXML($root); + return $root; } /** diff --git a/app/Services/EDocument/Standards/Verifactu/Models/SistemaInformatico.php b/app/Services/EDocument/Standards/Verifactu/Models/SistemaInformatico.php index 00010a7dea..5d6e7b8788 100644 --- a/app/Services/EDocument/Standards/Verifactu/Models/SistemaInformatico.php +++ b/app/Services/EDocument/Standards/Verifactu/Models/SistemaInformatico.php @@ -15,13 +15,9 @@ class SistemaInformatico extends BaseXmlModel protected string $tipoUsoPosibleMultiOT = 'S'; protected string $indicadorMultiplesOT = 'S'; - public function toXml(): string + public function toXml(\DOMDocument $doc): \DOMElement { - $doc = new \DOMDocument('1.0', 'UTF-8'); - $doc->formatOutput = true; - $root = $this->createElement($doc, 'SistemaInformatico'); - $doc->appendChild($root); // Add nombreRazon $root->appendChild($this->createElement($doc, 'NombreRazon', $this->nombreRazon)); @@ -42,7 +38,7 @@ class SistemaInformatico extends BaseXmlModel $root->appendChild($this->createElement($doc, 'TipoUsoPosibleMultiOT', $this->tipoUsoPosibleMultiOT)); $root->appendChild($this->createElement($doc, 'IndicadorMultiplesOT', $this->indicadorMultiplesOT)); - return $doc->saveXML(); + return $root; } /** diff --git a/tests/Feature/EInvoice/Verifactu/Models/InvoiceTest.php b/tests/Feature/EInvoice/Verifactu/Models/InvoiceTest.php index c53916ef6f..b0527c5a57 100644 --- a/tests/Feature/EInvoice/Verifactu/Models/InvoiceTest.php +++ b/tests/Feature/EInvoice/Verifactu/Models/InvoiceTest.php @@ -11,6 +11,7 @@ use App\Services\EDocument\Standards\Verifactu\Models\SistemaInformatico; use App\Services\EDocument\Standards\Verifactu\Models\PrimerRegistroCadena; use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica; use App\Services\EDocument\Standards\Verifactu\Models\FacturaRectificativa; +use App\Services\EDocument\Standards\Verifactu\Models\DetalleDesglose; class InvoiceTest extends BaseModelTest { @@ -43,9 +44,9 @@ class InvoiceTest extends BaseModelTest 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', - 'BaseImponible' => 1000.00, + 'BaseImponibleOimporteNoSujeto' => 1000.00, 'TipoImpositivo' => 21, - 'Cuota' => 210.00 + 'CuotaRepercutida' => 210.00 ]); $invoice->setDesglose($desglose); @@ -74,7 +75,7 @@ class InvoiceTest extends BaseModelTest ->setDescripcionCupon('Descuento promocional'); // $invoice->setCupon($cupon); - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML:\n"; @@ -131,9 +132,9 @@ class InvoiceTest extends BaseModelTest 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', - 'BaseImponible' => 100.00, + 'BaseImponibleOimporteNoSujeto' => 100.00, 'TipoImpositivo' => 21, - 'Cuota' => 21.00 + 'CuotaRepercutida' => 21.00 ]); $invoice->setDesglose($desglose); @@ -156,7 +157,7 @@ class InvoiceTest extends BaseModelTest $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML:\n"; @@ -211,31 +212,31 @@ class InvoiceTest extends BaseModelTest 'Impuesto' => '01', 'ClaveRegimen' => '02', 'CalificacionOperacion' => 'S2', - 'BaseImponible' => -100.00, + 'BaseImponibleOimporteNoSujeto' => -100.00, 'TipoImpositivo' => 21, - 'Cuota' => -21.00 + 'CuotaRepercutida' => -21.00 ]); $invoice->setDesglose($desglose); - // Add FacturaRectificativa - $facturaRectificativa = new FacturaRectificativa( - 'I', // TipoRectificativa - -100.00, // BaseRectificada - -21.00 // CuotaRectificada - ); - $facturaRectificativa->addFacturaRectificada( - 'B12345678', // NIF - 'FAC-2023-001', // NumSerieFactura - '01-01-2023' // FechaExpedicionFactura - ); - $invoice->setFacturaRectificativa($facturaRectificativa); - - // Add encadenamiento with PrimerRegistro + // Add encadenamiento $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); - $xml = $invoice->toXml(); + // Add rectified invoice + $facturaRectificativa = new FacturaRectificativa( + 'I', // tipoRectificativa + -100.00, // baseRectificada + -21.00 // cuotaRectificada + ); + $facturaRectificativa->addFacturaRectificada( + 'B12345678', // nif + 'FAC-2023-001', // numSerie + '01-01-2023' // fecha + ); + $invoice->setFacturaRectificativa($facturaRectificativa); + + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML:\n"; @@ -281,12 +282,15 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - // Add desglose + // Add desglose with correct key names $desglose = new Desglose(); $desglose->setDesgloseIVA([ - 'BaseImponible' => 100.00, - 'TipoImpositivo' => 21, - 'Cuota' => 21.00 + 'Impuesto' => '01', + 'ClaveRegimen' => '02', + 'CalificacionOperacion' => 'S2', + 'BaseImponibleOimporteNoSujeto' => 100.00, + 'TipoImpositivo' => 21.00, + 'CuotaRepercutida' => 21.00 ]); $invoice->setDesglose($desglose); @@ -295,7 +299,12 @@ class InvoiceTest extends BaseModelTest $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; // Validate against XSD $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); @@ -307,6 +316,8 @@ class InvoiceTest extends BaseModelTest $this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor()); $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); $this->assertEquals($invoice->getFacturaSinIdentifDestinatarioArt61d(), $deserialized->getFacturaSinIdentifDestinatarioArt61d()); + $this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal()); + $this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal()); } public function testInvalidXmlThrowsException(): void @@ -324,7 +335,7 @@ class InvoiceTest extends BaseModelTest $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Missing required field: IDVersion'); - $invoice->toXml(); + $invoice->toXmlString(); } public function test_create_and_serialize_rectification_invoice() @@ -342,9 +353,12 @@ class InvoiceTest extends BaseModelTest ->setRazonSocial('Empresa Ejemplo SL')) ->setDesglose((new Desglose()) ->setDesgloseIVA([ - 'BaseImponible' => 1000.00, - 'TipoImpositivo' => 21, - 'Cuota' => 210.00 + 'Impuesto' => '01', + 'ClaveRegimen' => '01', + 'CalificacionOperacion' => 'S1', + 'BaseImponibleOimporteNoSujeto' => 1000.00, + 'TipoImpositivo' => 21.00, + 'CuotaRepercutida' => 210.00 ])) ->setCuotaTotal(210) ->setImporteTotal(1000) @@ -380,7 +394,7 @@ class InvoiceTest extends BaseModelTest $invoice->setFacturaRectificativa($facturaRectificativa); - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML:\n"; @@ -388,6 +402,15 @@ class InvoiceTest extends BaseModelTest echo "\n\n"; $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); + + // Test deserialization + $deserialized = Invoice::fromXml($xml); + $this->assertEquals($invoice->getIdVersion(), $deserialized->getIdVersion()); + $this->assertEquals($invoice->getIdFactura(), $deserialized->getIdFactura()); + $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); + $this->assertEquals($invoice->getTipoRectificativa(), $deserialized->getTipoRectificativa()); + $this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal()); + $this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal()); } public function testCreateAndSerializeInvoiceWithMultipleRecipients(): void @@ -410,28 +433,28 @@ class InvoiceTest extends BaseModelTest $destinatario1 = new PersonaFisicaJuridica(); $destinatario1 ->setNif('B87654321') - ->setNombreRazon('Cliente 1 SL'); // Changed from setRazonSocial to setNombreRazon + ->setNombreRazon('Cliente 1 SL'); $destinatarios[] = $destinatario1; $destinatario2 = new PersonaFisicaJuridica(); $destinatario2 - ->setPais('FR') // French company - ->setTipoIdentificacion('02') // NIF-IVA (VAT number) - ->setIdOtro('FR12345678901') // French VAT number - ->setNombreRazon('Client 2 SARL'); // French company name + ->setPais('FR') + ->setTipoIdentificacion('02') + ->setIdOtro('FR12345678901') + ->setNombreRazon('Client 2 SARL'); $destinatarios[] = $destinatario2; $invoice->setDestinatarios($destinatarios); - // Add desglose with proper structure + // Add desglose with proper structure and correct key names $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', 'CalificacionOperacion' => 'S1', - 'BaseImponible' => 200.00, + 'BaseImponibleOimporteNoSujeto' => 200.00, 'TipoImpositivo' => 21.00, - 'Cuota' => 42.00 + 'CuotaRepercutida' => 42.00 ]); $invoice->setDesglose($desglose); @@ -454,7 +477,15 @@ class InvoiceTest extends BaseModelTest ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; + + // Validate against XSD $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization @@ -493,10 +524,10 @@ class InvoiceTest extends BaseModelTest $desglose->setDesgloseIVA([ 'Impuesto' => '01', 'ClaveRegimen' => '01', - 'OperacionExenta' => 'E1', - 'BaseImponible' => 100.00, + 'CalificacionOperacion' => 'N1', + 'BaseImponibleOimporteNoSujeto' => 100.00, 'TipoImpositivo' => 0, - 'Cuota' => 0.00 + 'CuotaRepercutida' => 0.00 ]); $invoice->setDesglose($desglose); @@ -519,7 +550,14 @@ class InvoiceTest extends BaseModelTest ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; + $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization @@ -582,7 +620,14 @@ class InvoiceTest extends BaseModelTest ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; + $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization @@ -637,7 +682,8 @@ class InvoiceTest extends BaseModelTest ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML:\n"; @@ -659,15 +705,22 @@ class InvoiceTest extends BaseModelTest ->setIdFactura('FAC-2023-009') ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') - ->setEmitidaPorTerceroODestinatario('T') ->setDescripcionOperacion('Factura emitida por tercero') + ->setEmitidaPorTerceroODestinatario('T') ->setCuotaTotal(21.00) ->setImporteTotal(100.00) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); - // Add desglose with proper structure + // Set up the third party issuer + $tercero = new PersonaFisicaJuridica(); + $tercero + ->setNif('B98765432') + ->setRazonSocial('Tercero Emisor SL'); + $invoice->setTercero($tercero); + + // Add desglose $desglose = new Desglose(); $desglose->setDesgloseIVA([ 'Impuesto' => '01', @@ -679,13 +732,6 @@ class InvoiceTest extends BaseModelTest ]); $invoice->setDesglose($desglose); - // Add third party with proper structure - $tercero = new PersonaFisicaJuridica(); - $tercero - ->setRazonSocial('Tercero Emisor SL') - ->setNif('B98765432'); - $invoice->setTercero($tercero); - // Add encadenamiento (required) $encadenamiento = new Encadenamiento(); $encadenamiento->setPrimerRegistro('S'); @@ -705,7 +751,8 @@ class InvoiceTest extends BaseModelTest ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML:\n"; @@ -768,7 +815,14 @@ class InvoiceTest extends BaseModelTest $encadenamiento->setPrimerRegistro('S'); $invoice->setEncadenamiento($encadenamiento); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; + $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization @@ -776,26 +830,43 @@ class InvoiceTest extends BaseModelTest $this->assertEquals('S', $deserialized->getMacrodato()); } - public function testCreateAndSerializeInvoiceWithDigitalSignature(): void + public function testCreateAndSerializeInvoiceWithAgreementData(): void { $invoice = new Invoice(); - $invoice - ->setIdVersion('1.0') - ->setIdFactura('FAC-2023-011') + $invoice->setIdVersion('1.0') + ->setIdFactura('FAC-2023-012') ->setNombreRazonEmisor('Empresa Ejemplo SL') ->setTipoFactura('F1') - ->setDescripcionOperacion('Factura con firma digital') - ->setCuotaTotal(21.00) - ->setImporteTotal(100.00) + ->setDescripcionOperacion('Factura con datos de acuerdo') + ->setCuotaTotal(21) + ->setImporteTotal(100) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') - ->setHuella('abc123...'); + ->setHuella('abc123...') + ->setNumRegistroAcuerdoFacturacion('REG-001') + ->setIdAcuerdoSistemaInformatico('AGR-001'); - // Add sistema informatico with all required fields + // Set up Desglose + $desglose = new Desglose(); + $desglose->setDesgloseIVA([ + 'Impuesto' => '01', + 'ClaveRegimen' => '02', + 'CalificacionOperacion' => 'S2', + 'BaseImponible' => 100.00, + 'TipoImpositivo' => 21.00, + 'Cuota' => 21.00 + ]); + $invoice->setDesglose($desglose); + + // Set up Encadenamiento + $encadenamiento = new Encadenamiento(); + $encadenamiento->setPrimerRegistro('S'); + $invoice->setEncadenamiento($encadenamiento); + + // Set up SistemaInformatico $sistema = new SistemaInformatico(); - $sistema - ->setNombreRazon('Sistema de Facturación') - ->setNif('B12345678') + $sistema->setNombreRazon('Sistema de Facturación') + ->setNIF('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') @@ -805,142 +876,69 @@ class InvoiceTest extends BaseModelTest ->setIndicadorMultiplesOT('S'); $invoice->setSistemaInformatico($sistema); - // Add desglose with proper structure - $desglose = new Desglose(); - $desglose->setDesgloseIVA([ - 'Impuesto' => '01', - 'ClaveRegimen' => '02', - 'CalificacionOperacion' => 'S2', - 'BaseImponible' => 100.00, - 'TipoImpositivo' => 21, - 'Cuota' => 21.00 - ]); - $invoice->setDesglose($desglose); - - // Add encadenamiento (required) - $encadenamiento = new Encadenamiento(); - $encadenamiento->setPrimerRegistro('S'); - $invoice->setEncadenamiento($encadenamiento); - - // Set up paths for certificates - $certsPath = dirname(__DIR__) . '/certs/'; - $privateKeyPath = $certsPath . 'private.pem'; - $publicKeyPath = $certsPath . 'public.pem'; - $certificatePath = $certsPath . 'certificate.pem'; - - // Check if certificate files exist and are readable - foreach (['private.pem', 'public.pem', 'certificate.pem'] as $file) { - $path = $certsPath . $file; - echo "\n$file: "; - if (!file_exists($path)) { - echo "MISSING"; - throw new \RuntimeException("Certificate file $file does not exist at $path"); - } - if (!is_readable($path)) { - echo "NOT READABLE"; - throw new \RuntimeException("Certificate file $file is not readable at $path"); - } - echo "OK (size: " . filesize($path) . " bytes)"; - } - - // Set the keys - $invoice->setPrivateKeyPath($privateKeyPath) - ->setPublicKeyPath($publicKeyPath) - ->setCertificatePath($certificatePath); - - // Generate signed XML - $xml = $invoice->toXml(); - - // Debug output - echo "\nGenerated XML with signature:\n"; - echo $xml; - echo "\n\n"; - - // Load the XML into a DOMDocument for verification - $doc = new \DOMDocument(); - $doc->loadXML($xml); - - // Verify the signature - $this->assertTrue($invoice->verifySignature($doc)); - - // Skip schema validation for signed XML since the signature schema is not part of the core invoice schema - // Instead, validate the XML before signing - $unsignedXml = $invoice->toXml(false); - $this->assertValidatesAgainstXsd($unsignedXml, $this->getTestXsdPath()); - } - - public function testCreateAndSerializeInvoiceWithAgreementData(): void - { - $invoice = new Invoice(); - $invoice - ->setIdVersion('1.0') - ->setIdFactura('FAC-2023-012') - ->setNombreRazonEmisor('Empresa Ejemplo SL') - ->setTipoFactura('F1') - ->setDescripcionOperacion('Factura con datos de acuerdo') - ->setCuotaTotal(21.00) - ->setImporteTotal(100.00) - ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') - ->setTipoHuella('01') - ->setHuella('abc123...') - ->setNumRegistroAcuerdoFacturacion('REG-001') - ->setIdAcuerdoSistemaInformatico('AGR-001'); - - // Add sistema informatico - $sistema = new SistemaInformatico(); - $sistema - ->setNombreRazon('Sistema de Facturación') - ->setNif('B12345678') - ->setNombreSistemaInformatico('SistemaFacturacion') - ->setIdSistemaInformatico('01') - ->setVersion('1.0') - ->setNumeroInstalacion('INST-001'); - $invoice->setSistemaInformatico($sistema); - - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); - // Test deserialization - $deserialized = Invoice::fromXml($xml); - $this->assertEquals('REG-001', $deserialized->getNumRegistroAcuerdoFacturacion()); - $this->assertEquals('AGR-001', $deserialized->getIdAcuerdoSistemaInformatico()); + // Deserialize and verify + $deserializedInvoice = Invoice::fromXml($xml); + $this->assertEquals('REG-001', $deserializedInvoice->getNumRegistroAcuerdoFacturacion()); + $this->assertEquals('AGR-001', $deserializedInvoice->getIdAcuerdoSistemaInformatico()); } - public function testCreateAndSerializeInvoiceWithRejectionAndCorrection(): void + public function testCreateAndSerializeInvoiceWithRejectionAndCorrection() { $invoice = new Invoice(); - $invoice - ->setIdVersion('1.0') + $invoice->setIdVersion('1.0') ->setIdFactura('FAC-2023-013') ->setNombreRazonEmisor('Empresa Ejemplo SL') - ->setTipoFactura('F1') - ->setRechazoPrevio('S') ->setSubsanacion('S') + ->setRechazoPrevio('S') + ->setTipoFactura('F1') ->setDescripcionOperacion('Factura con rechazo y subsanación') - ->setCuotaTotal(21.00) - ->setImporteTotal(100.00) + ->setCuotaTotal(21) + ->setImporteTotal(100) ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setTipoHuella('01') ->setHuella('abc123...'); - // Add sistema informatico - $sistema = new SistemaInformatico(); - $sistema - ->setNombreRazon('Sistema de Facturación') + // Add proper Desglose + $desglose = new Desglose(); + $desglose->setDesgloseFactura([ + 'Impuesto' => '01', + 'ClaveRegimen' => '02', + 'CalificacionOperacion' => 'S2', + 'TipoImpositivo' => 21.00, + 'BaseImponible' => 100.00, + 'Cuota' => 21.00 + ]); + $invoice->setDesglose($desglose); + + // Add proper Encadenamiento + $encadenamiento = new Encadenamiento(); + $encadenamiento->setPrimerRegistro('S'); + $invoice->setEncadenamiento($encadenamiento); + + // Add SistemaInformatico + $sistemaInformatico = new SistemaInformatico(); + $sistemaInformatico->setNombreRazon('Sistema de Facturación') ->setNif('B12345678') ->setNombreSistemaInformatico('SistemaFacturacion') ->setIdSistemaInformatico('01') ->setVersion('1.0') - ->setNumeroInstalacion('INST-001'); - $invoice->setSistemaInformatico($sistema); + ->setNumeroInstalacion('INST-001') + ->setTipoUsoPosibleSoloVerifactu('S') + ->setTipoUsoPosibleMultiOT('S') + ->setIndicadorMultiplesOT('S'); + $invoice->setSistemaInformatico($sistemaInformatico); - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); + $this->assertNotEmpty($xml); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization - $deserialized = Invoice::fromXml($xml); - $this->assertEquals('S', $deserialized->getRechazoPrevio()); - $this->assertEquals('S', $deserialized->getSubsanacion()); + $deserializedInvoice = Invoice::fromXml($xml); + $this->assertEquals('S', $deserializedInvoice->getSubsanacion()); + $this->assertEquals('S', $deserializedInvoice->getRechazoPrevio()); } public function testCreateAndSerializeInvoiceWithOperationDate(): void @@ -970,7 +968,31 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Add Desglose + $desglose = new Desglose(); + $desglose->setDesgloseFactura([ + 'Impuesto' => '01', + 'ClaveRegimen' => '02', + 'CalificacionOperacion' => 'S2', + 'TipoImpositivo' => '21.00', + 'BaseImponibleOimporteNoSujeto' => '100.00', + 'CuotaRepercutida' => '21.00' + ]); + $invoice->setDesglose($desglose); + + // Add Encadenamiento + $encadenamiento = new Encadenamiento(); + $encadenamiento->setPrimerRegistro('S'); + $invoice->setEncadenamiento($encadenamiento); + + // Generate XML string + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; + $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization @@ -1013,7 +1035,14 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $xml = $invoice->toXml(); + // Generate XML string + $xml = $invoice->toXmlString(); + + // Debug output + echo "\nGenerated XML:\n"; + echo $xml; + echo "\n\n"; + $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); // Test deserialization @@ -1050,7 +1079,7 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $invoice->toXml(); + $invoice->toXmlString(); } public function testInvalidTipoRectificativaThrowsException(): void @@ -1082,7 +1111,7 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $invoice->toXml(); + $invoice->toXmlString(); } public function testInvalidDateFormatThrowsException(): void @@ -1113,7 +1142,7 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $invoice->toXml(); + $invoice->toXmlString(); } public function testInvalidNIFFormatThrowsException(): void @@ -1151,7 +1180,7 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $invoice->toXml(); + $invoice->toXmlString(); } public function testInvalidAmountFormatThrowsException(): void @@ -1182,7 +1211,7 @@ class InvoiceTest extends BaseModelTest ->setNumeroInstalacion('INST-001'); $invoice->setSistemaInformatico($sistema); - $invoice->toXml(); + $invoice->toXmlString(); } public function testInvalidSchemaThrowsException(): void @@ -1253,7 +1282,7 @@ class InvoiceTest extends BaseModelTest ->setCertificatePath($certificatePath); // Generate signed XML - $xml = $invoice->toXml(); + $xml = $invoice->toXmlString(); // Debug output echo "\nGenerated XML with signature:\n"; diff --git a/tests/Feature/EInvoice/Verifactu/certs/certificate.pem b/tests/Feature/EInvoice/Verifactu/certs/certificate.pem index ba17209857..6171ef9b71 100644 --- a/tests/Feature/EInvoice/Verifactu/certs/certificate.pem +++ b/tests/Feature/EInvoice/Verifactu/certs/certificate.pem @@ -1,25 +1,24 @@ -----BEGIN CERTIFICATE----- -MIIEITCCAwmgAwIBAgIULRklu/Ae+uWUdejwvfO7wKhs0lIwDQYJKoZIhvcNAQEL -BQAwgZ8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1h -ZHJpZDEaMBgGA1UECgwRVGVzdCBPcmdhbml6YXRpb24xFjAUBgNVBAsMDUlUIERl -cGFydG1lbnQxGTAXBgNVBAMMEHRlc3QuZXhhbXBsZS5jb20xHzAdBgkqhkiG9w0B -CQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMjUwNDI1MDQxMzA2WhcNMjYwNDI1MDQx -MzA2WjCBnzELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwG -TWFkcmlkMRowGAYDVQQKDBFUZXN0IE9yZ2FuaXphdGlvbjEWMBQGA1UECwwNSVQg -RGVwYXJ0bWVudDEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNvbTEfMB0GCSqGSIb3 -DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBALZExtAsqspmBMot0/D1Ska36SaEEIZurdgh5odE2jcKWubJQegu4tAu -Eq8LbL6bw8qmJZ6txp3fknIK3RjSyT39Rk0KtpGvrVjFPLAD42UNr8fVspPDHUR9 -yUvSpXoDxtcL6y6pn08jNnBFHkSnQK9br5wiNXWhSDkRTKfJ6Jvc269dA1qcJ2jU -S1k5TXULhZwwvjeTFKb9+xwRjdnLlyh4+WSUvwUMDFgXeKn1iQppZ0Oc+G4Xyq8W -oViOwruNMT3iUN83I3wKk3NOkdbx/IxD87UB19mKdmV4X1Nlj+m+/gidDf2DmriM -FtnPp3kPaqwWBB+rV4AggwnKAOwfmUECAwEAAaNTMFEwHQYDVR0OBBYEFLR1jHET -y8qVyFzsD9Ui0466XsLqMB8GA1UdIwQYMBaAFLR1jHETy8qVyFzsD9Ui0466XsLq -MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIllEMRCi/n6bqfv -3eMuzHBxvUNNjmTIbEfnPyjf81FB78e1V3mXE5cYcVbDOwbvzZKa6qcioG88iW8Y -5b9RnLQjy9os7xhne1BJq3J2B6XKlHv9O1f5x1fb6nZ/8m7XyGmXctIGs3K29/sF -zFqx7UgSujZCtKannnjS7RCMTbCixhxVbgwBRSkTLBxNWssHaJ7q4ktrfKdXSHAm -BhfgrvbILYYYN38i7Hfwru7m6Uoo7I+sy8yfHZzm3DwvswVRpRFIWLtkc6PBbyE4 -xXWjoJrbFGyzSdLg+DX5SpJ4569Jueuj2LiDCsJ3r1whlUO/gDfB3jdrrUrkxrBq -mmW22K8= +MIIEATCCAumgAwIBAgIUKnHs6qxWiKTbXcDRrT6GNnbuYt0wDQYJKoZIhvcNAQEL +BQAwgY8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1h +ZHJpZDEVMBMGA1UECgwMVGVzdCBDb21wYW55MQswCQYDVQQLDAJJVDEZMBcGA1UE +AwwQdGVzdC5leGFtcGxlLmNvbTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl +LmNvbTAeFw0yNTA0MjUwNTAwMTNaFw0yNjA0MjUwNTAwMTNaMIGPMQswCQYDVQQG +EwJFUzEPMA0GA1UECAwGTWFkcmlkMQ8wDQYDVQQHDAZNYWRyaWQxFTATBgNVBAoM +DFRlc3QgQ29tcGFueTELMAkGA1UECwwCSVQxGTAXBgNVBAMMEHRlc3QuZXhhbXBs +ZS5jb20xHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8A3xqN1BhPw42Ja5W40Wi6q5NC0I7WuCz +HTXUIKR6oARtRr28in6dA1hDu3V4hZgvNqveZO9vJOh+mWGWvcHK57mRMc6YJ0Nj +bgGKBsGLHvfT8ErDOLjw+BrfC0PRiPWGvXqubt6y6whg4AklskPtT8oAgKvLQlo1 +mZMo1k9Tnl8cXwFHibCnl6JqkGX2eUeBevAVCJ5//ZgIsHX19rg165kfg8KlcB4C +Ts+w+4iLwgW0PnzwdINKtpd7h7HXzkfVIvs32sbYlOOoA7e3Ouqm3/72BlSSHMlv +iTLDNAe+5ogTIaGvxxKG+meTiWSULIcba1M3+Ou0Kmp1I4BZWBWJAgMBAAGjUzBR +MB0GA1UdDgQWBBQ22t7nxuwbRP36CUFxtKlm7fByXjAfBgNVHSMEGDAWgBQ22t7n +xuwbRP36CUFxtKlm7fByXjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAIOJZkAmpRy+oldLJ3jRg76J8Vt61vPdMtyY+gHYE0rlkdKWqZcrrWoSbO +LrhjcocGp13G6yhOwebDVeHlYI0ninLOynmLWOJTm+BVesQXfN5u2U9CilpFLcid +8Q5Z0jqWGjPwA+dcaiVi0xeYbaiUD5+vUEjpC4lmx505XQnUvKMk6TG0E5gn8KFO +Na1lXww1Pu1yaxspwjQQNaOCYS736LM2T+IBiuoq56QGp2hO1GdeFC+VhmQgaqdI +p/sQl/+Mkvmu/Co7V61J5ZHZeQCVKFKjDfHTCwb/+6yO3ZIqyn8YfK2KF71qfGc5 +wgPkBJiznHo8tQvbndKWN9Gx5r1X -----END CERTIFICATE----- diff --git a/tests/Feature/EInvoice/Verifactu/certs/private.pem b/tests/Feature/EInvoice/Verifactu/certs/private.pem index e9123fddea..d3d7fe13f5 100644 --- a/tests/Feature/EInvoice/Verifactu/certs/private.pem +++ b/tests/Feature/EInvoice/Verifactu/certs/private.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2RMbQLKrKZgTK -LdPw9UpGt+kmhBCGbq3YIeaHRNo3ClrmyUHoLuLQLhKvC2y+m8PKpiWercad35Jy -Ct0Y0sk9/UZNCraRr61YxTywA+NlDa/H1bKTwx1EfclL0qV6A8bXC+suqZ9PIzZw -RR5Ep0CvW6+cIjV1oUg5EUynyeib3NuvXQNanCdo1EtZOU11C4WcML43kxSm/fsc -EY3Zy5coePlklL8FDAxYF3ip9YkKaWdDnPhuF8qvFqFYjsK7jTE94lDfNyN8CpNz -TpHW8fyMQ/O1AdfZinZleF9TZY/pvv4InQ39g5q4jBbZz6d5D2qsFgQfq1eAIIMJ -ygDsH5lBAgMBAAECggEAC732iOa3wf48hMHbC4Th2hhy/rY3UlHDBU95yHEZFb6n -CIMiqdCLcBnnvjsgME9cl6uIdOaBCx6iEpK8l6LMsB+m7cOo603D/xoFxNkbRyFQ -l7EepgJF2mm4FBhn7KpdnvD3n7PxvWlpUmZBgu67bhCcCZTymLdhLbv9kjmhsJi3 -qqlygaWUaj91yQFLJ4PeTnTMRd4yVFvP9IDGxgTQzDzlMQz1uAu52dunB+TkLkJI -/MgbhC5/bAWmgqgmbNjhFkQgwdG787jstXtMvLBGnYNhBVzuqr4XHh31pKFhl+z3 -hG1Hi3yg+XzKVSS7wb+qMv38dGVZYSTSa06vs03mWQKBgQD8cbcGu52iFi1h/9xY -ge1EmIiT9SrYIaYhzrPFdzeY7jpaXfXYZ+nPyrTs6nRxW/RsHP1YxM5WUTE23zhL -g9W1NWATHKh1n+QAwHQdYZWJN1LyqCW5q3fl0koAQGuXIMqKpv3PR+4pyhxdGNC9 -MnPTLQmWkIMA7RBAizNN3aXaYwKBgQC41gRDf1Zv/Wg2kc8u3hOX0tbuT9bxium3 -kfXmDEJUQ9fww8rA9ZKo3weKMvUklmrXEbLjMN2Md8hmdBVHQXXhtXyZzoiCHVct -DNW2TvqBxpJCnLA3bHr8fgPSAHs7SGAM8gF5mY9ZhPI4+Drjk6YlOHVcEr82C1nG -jJbCdVYdCwKBgFdO1OglNy150hRUs1aBCRhyULorTrgVWynSHWasBrSDn/blDEPe -HIVbLBvMMp2KGgzSMeTjnCFKT2UU6pljbSQQ47t4a+LSe76u0PngaCFe2vdFpFaE -sSDxC9rubMeF9cbiXmG0FPCrEAg6rubgbiKZLvm93TESzE4mVoYVpGjVAoGBAKrG -0tmqZarDb+47ejnLZj74xy0ZB3fU6Wx6p8ANw5sns/T4cfUm8IKmzsiZnHZZpA1i -hO300D5gzgAbcS2NBeWtUZqqgOX3RfyRx0PSZRJS7gAt1YLf8CIqLE1ztGhpzpUn -ZMV9ZD1J4KNSdtaeLPxm1chkadb9Vc1lSEYTM6VRAoGAIcA1aEllKEOWXeZAUfny -l67WA0ua6ss2dPQWJumflcf73jajE+jZQagsnRIJv34jJaP/O5A5OKU1WnemKlVU -fyBd4R4nr47yCLxk2HNwUQBluG6EIeUbdEM/2P6qAamIG4FzurvTKIZ7h2dgvtME -RaRkhB6Q8tXUwjQ+HfCpw9Q= +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC8A3xqN1BhPw42 +Ja5W40Wi6q5NC0I7WuCzHTXUIKR6oARtRr28in6dA1hDu3V4hZgvNqveZO9vJOh+ +mWGWvcHK57mRMc6YJ0NjbgGKBsGLHvfT8ErDOLjw+BrfC0PRiPWGvXqubt6y6whg +4AklskPtT8oAgKvLQlo1mZMo1k9Tnl8cXwFHibCnl6JqkGX2eUeBevAVCJ5//ZgI +sHX19rg165kfg8KlcB4CTs+w+4iLwgW0PnzwdINKtpd7h7HXzkfVIvs32sbYlOOo +A7e3Ouqm3/72BlSSHMlviTLDNAe+5ogTIaGvxxKG+meTiWSULIcba1M3+Ou0Kmp1 +I4BZWBWJAgMBAAECggEAPA8FBE0NITNqT8OzsE/DY+qBqVzKWHmMQ8s18cBHqmBJ +abrqSzsWfQOfg2tzd/99AL//IPivP5rTpfVLJkMRj8Z8EbSOYS5RqYZveW71DoLi ++dHNoBUThu5KbyuDC4ErpVILyYLjItZPg9qtMVyDtGADkCuKNVDJzktzpdzn1Da7 +0hIvSDDn4FbZR4rk0Zran/Bg2LPjB0+5by++bTOVycn9ipj5htry1nmDjQ29PkEh +vxvo/Cvcuj4RUqsnLyDvpIWaWccw95KZCrKG1SODLypBPdmfEEw0trd2tC4JZG1B +pMwwcj7f/tvQilYoO+xMGREJODS50+0m6sC/TYcjjQKBgQDdDYLl1noJ0mSzUADN +UpXLE0T6d9NvC9Z6wm/gM6K7mlwqg+B74A6cqO2lTOpV+HXd4BNGYudSEBS5xpA+ +HV0eFl+JH/R8axZSXCrWELW2eTGSjkaUJ8U+nN6dpVe3ZeZe3E+XjaNqzh+8i7DX +pmyb8ecYd8J1wFZDgoJnN3Y9VwKBgQDZvM7DA5dDU/LsYmAx4STxBJUcU/adxnb0 +yZtWDNjrxuNQeF1wX7VxunfZTERuPMorIKMAKQx9i3OpF3W/fdZFCH9MfJGZuBSN +fVpMsGiosaG//A5LkyHiXc9u+xlAufacHF9lY1Y3YjslCwRIxFzOybr8BD1VKQvw +jq3W412YHwKBgFDseVcaxWKDG0ppp2GDMkM9sqre4xBsDQCz3bD+NjznrYYC8VNo +GvFPBnDVT5RZeQzY0zWZT4XDTVMVVgjnRweUuTTB9gb2TR2pkCT9yQgcJgG5BC2I +wbTCSNz9grcpHsTrclfRClo3S3cCzKKl0mzkie1zKkVhPDNKu+J9c1PRAoGAdi+y +902arHQD1uVrG4f+2khHe7Hjmi5g4WwRQfsFP62cM1CyP03kGhBB/5QTTNb4UcKM +V4jwe7DXHgBa7I8sYBpFpsmy6oJtvYM+IYRbur14c9IoHEOHz+xlrPLk+So3vvLz +oP/zi2ppeijpsWnMin4bXbIbbH80OOXpXEvbwLcCgYADsQN3wSh7+7NGq1h9C5s0 +OJFUZjwTSKkMyw/r8JnMfj39+UB2VHtTl/FYBiVkeuazEm5zcroPNC2lFVvit4rr +EzSXmSJ5FrnirfDuRO/xakHYr3tdF05xD+1lq9iSqsUSz2Iq5LcKFHRmiFDD748l ++xTBYVY8fTd1+jO8VruQNg== -----END PRIVATE KEY----- diff --git a/tests/Feature/EInvoice/Verifactu/certs/public.pem b/tests/Feature/EInvoice/Verifactu/certs/public.pem index 9aef5f40dc..d023c51c6e 100644 --- a/tests/Feature/EInvoice/Verifactu/certs/public.pem +++ b/tests/Feature/EInvoice/Verifactu/certs/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtkTG0CyqymYEyi3T8PVK -RrfpJoQQhm6t2CHmh0TaNwpa5slB6C7i0C4SrwtsvpvDyqYlnq3Gnd+ScgrdGNLJ -Pf1GTQq2ka+tWMU8sAPjZQ2vx9Wyk8MdRH3JS9KlegPG1wvrLqmfTyM2cEUeRKdA -r1uvnCI1daFIORFMp8nom9zbr10DWpwnaNRLWTlNdQuFnDC+N5MUpv37HBGN2cuX -KHj5ZJS/BQwMWBd4qfWJCmlnQ5z4bhfKrxahWI7Cu40xPeJQ3zcjfAqTc06R1vH8 -jEPztQHX2Yp2ZXhfU2WP6b7+CJ0N/YOauIwW2c+neQ9qrBYEH6tXgCCDCcoA7B+Z -QQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvAN8ajdQYT8ONiWuVuNF +ouquTQtCO1rgsx011CCkeqAEbUa9vIp+nQNYQ7t1eIWYLzar3mTvbyTofplhlr3B +yue5kTHOmCdDY24BigbBix730/BKwzi48Pga3wtD0Yj1hr16rm7esusIYOAJJbJD +7U/KAICry0JaNZmTKNZPU55fHF8BR4mwp5eiapBl9nlHgXrwFQief/2YCLB19fa4 +NeuZH4PCpXAeAk7PsPuIi8IFtD588HSDSraXe4ex185H1SL7N9rG2JTjqAO3tzrq +pt/+9gZUkhzJb4kywzQHvuaIEyGhr8cShvpnk4lklCyHG2tTN/jrtCpqdSOAWVgV +iQIDAQAB -----END PUBLIC KEY-----