Refactor for additional test cases

This commit is contained in:
David Bomba 2025-04-25 16:09:40 +10:00
parent dd60eb3b58
commit fdf7d2d3cf
11 changed files with 550 additions and 553 deletions

View File

@ -13,7 +13,8 @@ abstract class BaseXmlModel
{ {
$element = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':' . $name); $element = $doc->createElementNS(self::XML_NAMESPACE, self::XML_NAMESPACE_PREFIX . ':' . $name);
if ($value !== null) { if ($value !== null) {
$element->nodeValue = $value; $textNode = $doc->createTextNode($value);
$element->appendChild($textNode);
} }
foreach ($attributes as $attrName => $attrValue) { foreach ($attributes as $attrName => $attrValue) {
$element->setAttribute($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); $element = $doc->createElementNS(self::XML_DS_NAMESPACE, self::XML_DS_NAMESPACE_PREFIX . ':' . $name);
if ($value !== null) { if ($value !== null) {
$element->nodeValue = $value; $textNode = $doc->createTextNode($value);
$element->appendChild($textNode);
} }
foreach ($attributes as $attrName => $attrValue) { foreach ($attributes as $attrName => $attrValue) {
$element->setAttribute($attrName, $attrValue); $element->setAttribute($attrName, $attrValue);
@ -37,12 +39,12 @@ abstract class BaseXmlModel
{ {
$elements = $parent->getElementsByTagNameNS($namespace, $name); $elements = $parent->getElementsByTagNameNS($namespace, $name);
if ($elements->length > 0) { if ($elements->length > 0) {
return $elements->item(0)->nodeValue; return $elements->item(0)->textContent;
} }
return null; return null;
} }
abstract public function toXml(): string; abstract public function toXml(\DOMDocument $doc): \DOMElement;
public static function fromXml($xml): self public static function fromXml($xml): self
{ {
@ -55,6 +57,8 @@ abstract class BaseXmlModel
} }
$doc = new \DOMDocument(); $doc = new \DOMDocument();
$doc->formatOutput = true;
$doc->preserveWhiteSpace = false;
if (!$doc->loadXML($xml)) { if (!$doc->loadXML($xml)) {
throw new \DOMException('Failed to load XML: Invalid XML format'); throw new \DOMException('Failed to load XML: Invalid XML format');
} }

View File

@ -9,13 +9,9 @@ class Cupon extends BaseXmlModel
protected float $importeCupon; protected float $importeCupon;
protected ?string $descripcionCupon = null; 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'); $root = $this->createElement($doc, 'Cupon');
$doc->appendChild($root);
// Add required elements // Add required elements
$root->appendChild($this->createElement($doc, 'IDCupon', $this->idCupon)); $root->appendChild($this->createElement($doc, 'IDCupon', $this->idCupon));
@ -27,7 +23,7 @@ class Cupon extends BaseXmlModel
$root->appendChild($this->createElement($doc, 'DescripcionCupon', $this->descripcionCupon)); $root->appendChild($this->createElement($doc, 'DescripcionCupon', $this->descripcionCupon));
} }
return $doc->saveXML(); return $root;
} }
public static function fromDOMElement(\DOMElement $element): self public static function fromDOMElement(\DOMElement $element): self

View File

@ -11,13 +11,9 @@ class Desglose extends BaseXmlModel
protected ?array $desgloseIRPF = null; protected ?array $desgloseIRPF = null;
protected ?array $desgloseIS = 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'); $root = $this->createElement($doc, 'Desglose');
$doc->appendChild($root);
// Create DetalleDesglose element // Create DetalleDesglose element
$detalleDesglose = $this->createElement($doc, 'DetalleDesglose'); $detalleDesglose = $this->createElement($doc, 'DetalleDesglose');
@ -49,8 +45,10 @@ class Desglose extends BaseXmlModel
} }
// Add BaseImponibleOimporteNoSujeto (required) // Add BaseImponibleOimporteNoSujeto (required)
$detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', if (isset($this->desgloseFactura['BaseImponibleOimporteNoSujeto'])) {
number_format($this->desgloseFactura['BaseImponible'], 2, '.', ''))); $detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto',
number_format($this->desgloseFactura['BaseImponibleOimporteNoSujeto'], 2, '.', '')));
}
// Add BaseImponibleACoste if present // Add BaseImponibleACoste if present
if (isset($this->desgloseFactura['BaseImponibleACoste'])) { if (isset($this->desgloseFactura['BaseImponibleACoste'])) {
@ -59,9 +57,9 @@ class Desglose extends BaseXmlModel
} }
// Add CuotaRepercutida if present // Add CuotaRepercutida if present
if (isset($this->desgloseFactura['Cuota'])) { if (isset($this->desgloseFactura['CuotaRepercutida'])) {
$detalleDesglose->appendChild($this->createElement($doc, 'CuotaRepercutida', $detalleDesglose->appendChild($this->createElement($doc, 'CuotaRepercutida',
number_format($this->desgloseFactura['Cuota'], 2, '.', ''))); number_format($this->desgloseFactura['CuotaRepercutida'], 2, '.', '')));
} }
// Add TipoRecargoEquivalencia if present // Add TipoRecargoEquivalencia if present
@ -95,13 +93,15 @@ class Desglose extends BaseXmlModel
} }
// Add BaseImponibleOimporteNoSujeto (required) // Add BaseImponibleOimporteNoSujeto (required)
$detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto', if (isset($this->desgloseIVA['BaseImponibleOimporteNoSujeto'])) {
number_format($this->desgloseIVA['BaseImponible'], 2, '.', ''))); $detalleDesglose->appendChild($this->createElement($doc, 'BaseImponibleOimporteNoSujeto',
number_format($this->desgloseIVA['BaseImponibleOimporteNoSujeto'], 2, '.', '')));
}
// Add CuotaRepercutida if present // Add CuotaRepercutida if present
if (isset($this->desgloseIVA['Cuota'])) { if (isset($this->desgloseIVA['CuotaRepercutida'])) {
$detalleDesglose->appendChild($this->createElement($doc, '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); $root->appendChild($detalleDesglose);
} }
return $doc->saveXML(); return $root;
} }
public static function fromDOMElement(\DOMElement $element): self public static function fromDOMElement(\DOMElement $element): self

View File

@ -8,37 +8,23 @@ class Encadenamiento extends BaseXmlModel
protected ?EncadenamientoFacturaAnterior $registroAnterior = null; protected ?EncadenamientoFacturaAnterior $registroAnterior = null;
protected ?EncadenamientoFacturaAnterior $registroPosterior = 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'); $root = $this->createElement($doc, 'Encadenamiento');
$doc->appendChild($root);
if ($this->primerRegistro !== null) { if ($this->primerRegistro !== null) {
$root->appendChild($this->createElement($doc, 'PrimerRegistro', 'S')); $root->appendChild($this->createElement($doc, 'PrimerRegistro', 'S'));
} }
if ($this->registroAnterior !== null) { if ($this->registroAnterior !== null) {
$registroAnteriorXml = $this->registroAnterior->toXml(); $root->appendChild($this->registroAnterior->toXml($doc));
$registroAnteriorDoc = new \DOMDocument();
$registroAnteriorDoc->loadXML($registroAnteriorXml);
$registroAnteriorNode = $doc->importNode($registroAnteriorDoc->documentElement, true);
$root->appendChild($registroAnteriorNode);
} }
if ($this->registroPosterior !== null) { if ($this->registroPosterior !== null) {
$registroPosteriorXml = $this->registroPosterior->toXml(); $root->appendChild($this->registroPosterior->toXml($doc));
$registroPosteriorDoc = new \DOMDocument();
$registroPosteriorDoc->loadXML($registroPosteriorXml);
$registroPosteriorNode = $doc->importNode($registroPosteriorDoc->documentElement, true);
$root->appendChild($registroPosteriorNode);
} }
// Add namespace declaration to the root element return $root;
$root->setAttribute('xmlns:sf', self::XML_NAMESPACE);
$root->setAttribute('xmlns:ds', self::XML_DS_NAMESPACE);
return $doc->saveXML();
} }
public static function fromXml($xml): BaseXmlModel public static function fromXml($xml): BaseXmlModel
@ -140,20 +126,16 @@ class EncadenamientoFacturaAnterior extends BaseXmlModel
protected string $fechaExpedicionFactura; protected string $fechaExpedicionFactura;
protected string $huella; 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'); $root = $this->createElement($doc, 'RegistroAnterior');
$doc->appendChild($root);
$root->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->idEmisorFactura)); $root->appendChild($this->createElement($doc, 'IDEmisorFactura', $this->idEmisorFactura));
$root->appendChild($this->createElement($doc, 'NumSerieFactura', $this->numSerieFactura)); $root->appendChild($this->createElement($doc, 'NumSerieFactura', $this->numSerieFactura));
$root->appendChild($this->createElement($doc, 'FechaExpedicionFactura', $this->fechaExpedicionFactura)); $root->appendChild($this->createElement($doc, 'FechaExpedicionFactura', $this->fechaExpedicionFactura));
$root->appendChild($this->createElement($doc, 'Huella', $this->huella)); $root->appendChild($this->createElement($doc, 'Huella', $this->huella));
return $doc->saveXML(); return $root;
} }
public static function fromDOMElement(\DOMElement $element): self public static function fromDOMElement(\DOMElement $element): self

View File

@ -436,61 +436,66 @@ class Invoice extends BaseXmlModel
return $this; return $this;
} }
protected function signXml(\DOMDocument $doc): void public function signXml(\DOMDocument $doc): void
{ {
if (!$this->privateKeyPath || !file_exists($this->privateKeyPath)) { if (!file_exists($this->certificatePath)) {
throw new \RuntimeException('Private key not found or not set'); throw new \RuntimeException("Certificate file not found at: " . $this->certificatePath);
} }
if (!file_exists($this->privateKeyPath)) {
if (!$this->certificatePath || !file_exists($this->certificatePath)) { throw new \RuntimeException("Private key file not found at: " . $this->privateKeyPath);
throw new \RuntimeException('Certificate not found or not set');
} }
Log::info('Starting XML signing process');
Log::debug('XML before signing: ' . $doc->saveXML());
try { try {
// Create a new Security object $xmlContent = $doc->saveXML();
Log::debug('Creating XMLSecurityDSig object'); Log::debug("XML before signing:", ['xml' => $xmlContent]);
$objDSig = new XMLSecurityDSig(); $objDSig = new XMLSecurityDSig();
// Set canonicalization method
Log::debug('Setting canonicalization method');
$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); $objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
// Create a new Security key // Create a new security key
Log::debug('Creating XMLSecurityKey object'); $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'private'));
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
// Load the private key // Load the private key
Log::debug('Loading private key from: ' . $this->privateKeyPath);
$objKey->loadKey($this->privateKeyPath, true); $objKey->loadKey($this->privateKeyPath, true);
// Add reference // Add the reference
Log::debug('Adding reference to document');
$objDSig->addReference( $objDSig->addReference(
$doc, $doc,
XMLSecurityDSig::SHA256, 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 to the security object
// Add the certificate
Log::debug('Adding certificate');
$objDSig->add509Cert(file_get_contents($this->certificatePath)); $objDSig->add509Cert(file_get_contents($this->certificatePath));
// Sign the XML document // Append the signature
Log::debug('Signing document');
$objDSig->sign($objKey); $objDSig->sign($objKey);
// Append the signature to the XML // Append the signature to the XML
Log::debug('Appending signature');
$objDSig->appendSignature($doc->documentElement); $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) { } catch (\Exception $e) {
Log::error('Error during XML signing: ' . $e->getMessage()); Log::error("Error during XML signing: " . $e->getMessage(), [
Log::error('Stack trace: ' . $e->getTraceAsString()); 'exception' => $e,
'trace' => $e->getTraceAsString()
]);
throw $e; throw $e;
} }
} }
@ -501,35 +506,203 @@ class Invoice extends BaseXmlModel
throw new \RuntimeException('Public key not found or not set'); throw new \RuntimeException('Public key not found or not set');
} }
// Get the signature node try {
$objXMLSecDSig = new XMLSecurityDSig(); Log::info('Starting signature verification');
Log::debug('XML to verify: ' . $doc->saveXML());
// Locate the signature
$objDSig = $objXMLSecDSig->locateSignature($doc); // Get the signature node
if (!$objDSig) { $objXMLSecDSig = new XMLSecurityDSig();
throw new \RuntimeException('Signature not found in document');
// 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 // Validate required fields first, outside of try-catch
$requiredFields = [ $requiredFields = [
@ -556,198 +729,20 @@ class Invoice extends BaseXmlModel
$doc->preserveWhiteSpace = false; $doc->preserveWhiteSpace = false;
$doc->formatOutput = true; $doc->formatOutput = true;
// Create root element with proper namespaces // Create root element using toXml method
$root = $doc->createElementNS(parent::XML_NAMESPACE, parent::XML_NAMESPACE_PREFIX . ':RegistroAlta'); $root = $this->toXml($doc);
$root->setAttribute('xmlns:ds', parent::XML_DS_NAMESPACE);
$doc->appendChild($root); $doc->appendChild($root);
// Add required elements in exact order according to schema // Sign the document if certificates are set
$root->appendChild($this->createElement($doc, 'IDVersion', $this->idVersion)); if ($this->privateKeyPath && $this->certificatePath) {
$this->signXml($doc);
// 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());
}
} }
return $doc->saveXML(); return $doc->saveXML();
} catch (\Exception $e) { } catch (\Exception $e) {
error_log("Error in toXml: " . $e->getMessage()); Log::error('Error generating XML: ' . $e->getMessage());
throw new \RuntimeException("Error in toXml: " . $e->getMessage(), 0, $e); Log::error('Stack trace: ' . $e->getTraceAsString());
throw $e;
} }
} }

View File

@ -103,13 +103,9 @@ class PersonaFisicaJuridica extends BaseXmlModel
return $this; 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'); $root = $this->createElement($doc, 'PersonaFisicaJuridica');
$doc->appendChild($root);
if ($this->nif !== null) { if ($this->nif !== null) {
$root->appendChild($this->createElement($doc, 'NIF', $this->nif)); $root->appendChild($this->createElement($doc, 'NIF', $this->nif));
@ -143,7 +139,7 @@ class PersonaFisicaJuridica extends BaseXmlModel
$root->appendChild($this->createElement($doc, 'Pais', $this->pais)); $root->appendChild($this->createElement($doc, 'Pais', $this->pais));
} }
return $doc->saveXML($root); return $root;
} }
/** /**

View File

@ -15,13 +15,9 @@ class SistemaInformatico extends BaseXmlModel
protected string $tipoUsoPosibleMultiOT = 'S'; protected string $tipoUsoPosibleMultiOT = 'S';
protected string $indicadorMultiplesOT = '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'); $root = $this->createElement($doc, 'SistemaInformatico');
$doc->appendChild($root);
// Add nombreRazon // Add nombreRazon
$root->appendChild($this->createElement($doc, 'NombreRazon', $this->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, 'TipoUsoPosibleMultiOT', $this->tipoUsoPosibleMultiOT));
$root->appendChild($this->createElement($doc, 'IndicadorMultiplesOT', $this->indicadorMultiplesOT)); $root->appendChild($this->createElement($doc, 'IndicadorMultiplesOT', $this->indicadorMultiplesOT));
return $doc->saveXML(); return $root;
} }
/** /**

View File

@ -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\PrimerRegistroCadena;
use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica; use App\Services\EDocument\Standards\Verifactu\Models\PersonaFisicaJuridica;
use App\Services\EDocument\Standards\Verifactu\Models\FacturaRectificativa; use App\Services\EDocument\Standards\Verifactu\Models\FacturaRectificativa;
use App\Services\EDocument\Standards\Verifactu\Models\DetalleDesglose;
class InvoiceTest extends BaseModelTest class InvoiceTest extends BaseModelTest
{ {
@ -43,9 +44,9 @@ class InvoiceTest extends BaseModelTest
'Impuesto' => '01', 'Impuesto' => '01',
'ClaveRegimen' => '01', 'ClaveRegimen' => '01',
'CalificacionOperacion' => 'S1', 'CalificacionOperacion' => 'S1',
'BaseImponible' => 1000.00, 'BaseImponibleOimporteNoSujeto' => 1000.00,
'TipoImpositivo' => 21, 'TipoImpositivo' => 21,
'Cuota' => 210.00 'CuotaRepercutida' => 210.00
]); ]);
$invoice->setDesglose($desglose); $invoice->setDesglose($desglose);
@ -74,7 +75,7 @@ class InvoiceTest extends BaseModelTest
->setDescripcionCupon('Descuento promocional'); ->setDescripcionCupon('Descuento promocional');
// $invoice->setCupon($cupon); // $invoice->setCupon($cupon);
$xml = $invoice->toXml(); $xml = $invoice->toXmlString();
// Debug output // Debug output
echo "\nGenerated XML:\n"; echo "\nGenerated XML:\n";
@ -131,9 +132,9 @@ class InvoiceTest extends BaseModelTest
'Impuesto' => '01', 'Impuesto' => '01',
'ClaveRegimen' => '02', 'ClaveRegimen' => '02',
'CalificacionOperacion' => 'S2', 'CalificacionOperacion' => 'S2',
'BaseImponible' => 100.00, 'BaseImponibleOimporteNoSujeto' => 100.00,
'TipoImpositivo' => 21, 'TipoImpositivo' => 21,
'Cuota' => 21.00 'CuotaRepercutida' => 21.00
]); ]);
$invoice->setDesglose($desglose); $invoice->setDesglose($desglose);
@ -156,7 +157,7 @@ class InvoiceTest extends BaseModelTest
$encadenamiento->setPrimerRegistro('S'); $encadenamiento->setPrimerRegistro('S');
$invoice->setEncadenamiento($encadenamiento); $invoice->setEncadenamiento($encadenamiento);
$xml = $invoice->toXml(); $xml = $invoice->toXmlString();
// Debug output // Debug output
echo "\nGenerated XML:\n"; echo "\nGenerated XML:\n";
@ -211,31 +212,31 @@ class InvoiceTest extends BaseModelTest
'Impuesto' => '01', 'Impuesto' => '01',
'ClaveRegimen' => '02', 'ClaveRegimen' => '02',
'CalificacionOperacion' => 'S2', 'CalificacionOperacion' => 'S2',
'BaseImponible' => -100.00, 'BaseImponibleOimporteNoSujeto' => -100.00,
'TipoImpositivo' => 21, 'TipoImpositivo' => 21,
'Cuota' => -21.00 'CuotaRepercutida' => -21.00
]); ]);
$invoice->setDesglose($desglose); $invoice->setDesglose($desglose);
// Add FacturaRectificativa // Add encadenamiento
$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
$encadenamiento = new Encadenamiento(); $encadenamiento = new Encadenamiento();
$encadenamiento->setPrimerRegistro('S'); $encadenamiento->setPrimerRegistro('S');
$invoice->setEncadenamiento($encadenamiento); $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 // Debug output
echo "\nGenerated XML:\n"; echo "\nGenerated XML:\n";
@ -281,12 +282,15 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
// Add desglose // Add desglose with correct key names
$desglose = new Desglose(); $desglose = new Desglose();
$desglose->setDesgloseIVA([ $desglose->setDesgloseIVA([
'BaseImponible' => 100.00, 'Impuesto' => '01',
'TipoImpositivo' => 21, 'ClaveRegimen' => '02',
'Cuota' => 21.00 'CalificacionOperacion' => 'S2',
'BaseImponibleOimporteNoSujeto' => 100.00,
'TipoImpositivo' => 21.00,
'CuotaRepercutida' => 21.00
]); ]);
$invoice->setDesglose($desglose); $invoice->setDesglose($desglose);
@ -295,7 +299,12 @@ class InvoiceTest extends BaseModelTest
$encadenamiento->setPrimerRegistro('S'); $encadenamiento->setPrimerRegistro('S');
$invoice->setEncadenamiento($encadenamiento); $invoice->setEncadenamiento($encadenamiento);
$xml = $invoice->toXml(); $xml = $invoice->toXmlString();
// Debug output
echo "\nGenerated XML:\n";
echo $xml;
echo "\n\n";
// Validate against XSD // Validate against XSD
$this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
@ -307,6 +316,8 @@ class InvoiceTest extends BaseModelTest
$this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor()); $this->assertEquals($invoice->getNombreRazonEmisor(), $deserialized->getNombreRazonEmisor());
$this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura()); $this->assertEquals($invoice->getTipoFactura(), $deserialized->getTipoFactura());
$this->assertEquals($invoice->getFacturaSinIdentifDestinatarioArt61d(), $deserialized->getFacturaSinIdentifDestinatarioArt61d()); $this->assertEquals($invoice->getFacturaSinIdentifDestinatarioArt61d(), $deserialized->getFacturaSinIdentifDestinatarioArt61d());
$this->assertEquals($invoice->getCuotaTotal(), $deserialized->getCuotaTotal());
$this->assertEquals($invoice->getImporteTotal(), $deserialized->getImporteTotal());
} }
public function testInvalidXmlThrowsException(): void public function testInvalidXmlThrowsException(): void
@ -324,7 +335,7 @@ class InvoiceTest extends BaseModelTest
$this->expectException(\InvalidArgumentException::class); $this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Missing required field: IDVersion'); $this->expectExceptionMessage('Missing required field: IDVersion');
$invoice->toXml(); $invoice->toXmlString();
} }
public function test_create_and_serialize_rectification_invoice() public function test_create_and_serialize_rectification_invoice()
@ -342,9 +353,12 @@ class InvoiceTest extends BaseModelTest
->setRazonSocial('Empresa Ejemplo SL')) ->setRazonSocial('Empresa Ejemplo SL'))
->setDesglose((new Desglose()) ->setDesglose((new Desglose())
->setDesgloseIVA([ ->setDesgloseIVA([
'BaseImponible' => 1000.00, 'Impuesto' => '01',
'TipoImpositivo' => 21, 'ClaveRegimen' => '01',
'Cuota' => 210.00 'CalificacionOperacion' => 'S1',
'BaseImponibleOimporteNoSujeto' => 1000.00,
'TipoImpositivo' => 21.00,
'CuotaRepercutida' => 210.00
])) ]))
->setCuotaTotal(210) ->setCuotaTotal(210)
->setImporteTotal(1000) ->setImporteTotal(1000)
@ -380,7 +394,7 @@ class InvoiceTest extends BaseModelTest
$invoice->setFacturaRectificativa($facturaRectificativa); $invoice->setFacturaRectificativa($facturaRectificativa);
$xml = $invoice->toXml(); $xml = $invoice->toXmlString();
// Debug output // Debug output
echo "\nGenerated XML:\n"; echo "\nGenerated XML:\n";
@ -388,6 +402,15 @@ class InvoiceTest extends BaseModelTest
echo "\n\n"; echo "\n\n";
$this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); $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 public function testCreateAndSerializeInvoiceWithMultipleRecipients(): void
@ -410,28 +433,28 @@ class InvoiceTest extends BaseModelTest
$destinatario1 = new PersonaFisicaJuridica(); $destinatario1 = new PersonaFisicaJuridica();
$destinatario1 $destinatario1
->setNif('B87654321') ->setNif('B87654321')
->setNombreRazon('Cliente 1 SL'); // Changed from setRazonSocial to setNombreRazon ->setNombreRazon('Cliente 1 SL');
$destinatarios[] = $destinatario1; $destinatarios[] = $destinatario1;
$destinatario2 = new PersonaFisicaJuridica(); $destinatario2 = new PersonaFisicaJuridica();
$destinatario2 $destinatario2
->setPais('FR') // French company ->setPais('FR')
->setTipoIdentificacion('02') // NIF-IVA (VAT number) ->setTipoIdentificacion('02')
->setIdOtro('FR12345678901') // French VAT number ->setIdOtro('FR12345678901')
->setNombreRazon('Client 2 SARL'); // French company name ->setNombreRazon('Client 2 SARL');
$destinatarios[] = $destinatario2; $destinatarios[] = $destinatario2;
$invoice->setDestinatarios($destinatarios); $invoice->setDestinatarios($destinatarios);
// Add desglose with proper structure // Add desglose with proper structure and correct key names
$desglose = new Desglose(); $desglose = new Desglose();
$desglose->setDesgloseIVA([ $desglose->setDesgloseIVA([
'Impuesto' => '01', 'Impuesto' => '01',
'ClaveRegimen' => '01', 'ClaveRegimen' => '01',
'CalificacionOperacion' => 'S1', 'CalificacionOperacion' => 'S1',
'BaseImponible' => 200.00, 'BaseImponibleOimporteNoSujeto' => 200.00,
'TipoImpositivo' => 21.00, 'TipoImpositivo' => 21.00,
'Cuota' => 42.00 'CuotaRepercutida' => 42.00
]); ]);
$invoice->setDesglose($desglose); $invoice->setDesglose($desglose);
@ -454,7 +477,15 @@ class InvoiceTest extends BaseModelTest
->setIndicadorMultiplesOT('S'); ->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistema); $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()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
@ -493,10 +524,10 @@ class InvoiceTest extends BaseModelTest
$desglose->setDesgloseIVA([ $desglose->setDesgloseIVA([
'Impuesto' => '01', 'Impuesto' => '01',
'ClaveRegimen' => '01', 'ClaveRegimen' => '01',
'OperacionExenta' => 'E1', 'CalificacionOperacion' => 'N1',
'BaseImponible' => 100.00, 'BaseImponibleOimporteNoSujeto' => 100.00,
'TipoImpositivo' => 0, 'TipoImpositivo' => 0,
'Cuota' => 0.00 'CuotaRepercutida' => 0.00
]); ]);
$invoice->setDesglose($desglose); $invoice->setDesglose($desglose);
@ -519,7 +550,14 @@ class InvoiceTest extends BaseModelTest
->setIndicadorMultiplesOT('S'); ->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistema); $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()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
@ -582,7 +620,14 @@ class InvoiceTest extends BaseModelTest
->setIndicadorMultiplesOT('S'); ->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistema); $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()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
@ -637,7 +682,8 @@ class InvoiceTest extends BaseModelTest
->setIndicadorMultiplesOT('S'); ->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$xml = $invoice->toXml(); // Generate XML string
$xml = $invoice->toXmlString();
// Debug output // Debug output
echo "\nGenerated XML:\n"; echo "\nGenerated XML:\n";
@ -659,15 +705,22 @@ class InvoiceTest extends BaseModelTest
->setIdFactura('FAC-2023-009') ->setIdFactura('FAC-2023-009')
->setNombreRazonEmisor('Empresa Ejemplo SL') ->setNombreRazonEmisor('Empresa Ejemplo SL')
->setTipoFactura('F1') ->setTipoFactura('F1')
->setEmitidaPorTerceroODestinatario('T')
->setDescripcionOperacion('Factura emitida por tercero') ->setDescripcionOperacion('Factura emitida por tercero')
->setEmitidaPorTerceroODestinatario('T')
->setCuotaTotal(21.00) ->setCuotaTotal(21.00)
->setImporteTotal(100.00) ->setImporteTotal(100.00)
->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00')
->setTipoHuella('01') ->setTipoHuella('01')
->setHuella('abc123...'); ->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 = new Desglose();
$desglose->setDesgloseIVA([ $desglose->setDesgloseIVA([
'Impuesto' => '01', 'Impuesto' => '01',
@ -679,13 +732,6 @@ class InvoiceTest extends BaseModelTest
]); ]);
$invoice->setDesglose($desglose); $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) // Add encadenamiento (required)
$encadenamiento = new Encadenamiento(); $encadenamiento = new Encadenamiento();
$encadenamiento->setPrimerRegistro('S'); $encadenamiento->setPrimerRegistro('S');
@ -705,7 +751,8 @@ class InvoiceTest extends BaseModelTest
->setIndicadorMultiplesOT('S'); ->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$xml = $invoice->toXml(); // Generate XML string
$xml = $invoice->toXmlString();
// Debug output // Debug output
echo "\nGenerated XML:\n"; echo "\nGenerated XML:\n";
@ -768,7 +815,14 @@ class InvoiceTest extends BaseModelTest
$encadenamiento->setPrimerRegistro('S'); $encadenamiento->setPrimerRegistro('S');
$invoice->setEncadenamiento($encadenamiento); $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()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
@ -776,26 +830,43 @@ class InvoiceTest extends BaseModelTest
$this->assertEquals('S', $deserialized->getMacrodato()); $this->assertEquals('S', $deserialized->getMacrodato());
} }
public function testCreateAndSerializeInvoiceWithDigitalSignature(): void public function testCreateAndSerializeInvoiceWithAgreementData(): void
{ {
$invoice = new Invoice(); $invoice = new Invoice();
$invoice $invoice->setIdVersion('1.0')
->setIdVersion('1.0') ->setIdFactura('FAC-2023-012')
->setIdFactura('FAC-2023-011')
->setNombreRazonEmisor('Empresa Ejemplo SL') ->setNombreRazonEmisor('Empresa Ejemplo SL')
->setTipoFactura('F1') ->setTipoFactura('F1')
->setDescripcionOperacion('Factura con firma digital') ->setDescripcionOperacion('Factura con datos de acuerdo')
->setCuotaTotal(21.00) ->setCuotaTotal(21)
->setImporteTotal(100.00) ->setImporteTotal(100)
->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00')
->setTipoHuella('01') ->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 = new SistemaInformatico();
$sistema $sistema->setNombreRazon('Sistema de Facturación')
->setNombreRazon('Sistema de Facturación') ->setNIF('B12345678')
->setNif('B12345678')
->setNombreSistemaInformatico('SistemaFacturacion') ->setNombreSistemaInformatico('SistemaFacturacion')
->setIdSistemaInformatico('01') ->setIdSistemaInformatico('01')
->setVersion('1.0') ->setVersion('1.0')
@ -805,142 +876,69 @@ class InvoiceTest extends BaseModelTest
->setIndicadorMultiplesOT('S'); ->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
// Add desglose with proper structure $xml = $invoice->toXmlString();
$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();
$this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Deserialize and verify
$deserialized = Invoice::fromXml($xml); $deserializedInvoice = Invoice::fromXml($xml);
$this->assertEquals('REG-001', $deserialized->getNumRegistroAcuerdoFacturacion()); $this->assertEquals('REG-001', $deserializedInvoice->getNumRegistroAcuerdoFacturacion());
$this->assertEquals('AGR-001', $deserialized->getIdAcuerdoSistemaInformatico()); $this->assertEquals('AGR-001', $deserializedInvoice->getIdAcuerdoSistemaInformatico());
} }
public function testCreateAndSerializeInvoiceWithRejectionAndCorrection(): void public function testCreateAndSerializeInvoiceWithRejectionAndCorrection()
{ {
$invoice = new Invoice(); $invoice = new Invoice();
$invoice $invoice->setIdVersion('1.0')
->setIdVersion('1.0')
->setIdFactura('FAC-2023-013') ->setIdFactura('FAC-2023-013')
->setNombreRazonEmisor('Empresa Ejemplo SL') ->setNombreRazonEmisor('Empresa Ejemplo SL')
->setTipoFactura('F1')
->setRechazoPrevio('S')
->setSubsanacion('S') ->setSubsanacion('S')
->setRechazoPrevio('S')
->setTipoFactura('F1')
->setDescripcionOperacion('Factura con rechazo y subsanación') ->setDescripcionOperacion('Factura con rechazo y subsanación')
->setCuotaTotal(21.00) ->setCuotaTotal(21)
->setImporteTotal(100.00) ->setImporteTotal(100)
->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00') ->setFechaHoraHusoGenRegistro('2023-01-01T12:00:00')
->setTipoHuella('01') ->setTipoHuella('01')
->setHuella('abc123...'); ->setHuella('abc123...');
// Add sistema informatico // Add proper Desglose
$sistema = new SistemaInformatico(); $desglose = new Desglose();
$sistema $desglose->setDesgloseFactura([
->setNombreRazon('Sistema de Facturación') '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') ->setNif('B12345678')
->setNombreSistemaInformatico('SistemaFacturacion') ->setNombreSistemaInformatico('SistemaFacturacion')
->setIdSistemaInformatico('01') ->setIdSistemaInformatico('01')
->setVersion('1.0') ->setVersion('1.0')
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001')
$invoice->setSistemaInformatico($sistema); ->setTipoUsoPosibleSoloVerifactu('S')
->setTipoUsoPosibleMultiOT('S')
->setIndicadorMultiplesOT('S');
$invoice->setSistemaInformatico($sistemaInformatico);
$xml = $invoice->toXml(); $xml = $invoice->toXmlString();
$this->assertNotEmpty($xml);
$this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
$deserialized = Invoice::fromXml($xml); $deserializedInvoice = Invoice::fromXml($xml);
$this->assertEquals('S', $deserialized->getRechazoPrevio()); $this->assertEquals('S', $deserializedInvoice->getSubsanacion());
$this->assertEquals('S', $deserialized->getSubsanacion()); $this->assertEquals('S', $deserializedInvoice->getRechazoPrevio());
} }
public function testCreateAndSerializeInvoiceWithOperationDate(): void public function testCreateAndSerializeInvoiceWithOperationDate(): void
@ -970,7 +968,31 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $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()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
@ -1013,7 +1035,14 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $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()); $this->assertValidatesAgainstXsd($xml, $this->getTestXsdPath());
// Test deserialization // Test deserialization
@ -1050,7 +1079,7 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$invoice->toXml(); $invoice->toXmlString();
} }
public function testInvalidTipoRectificativaThrowsException(): void public function testInvalidTipoRectificativaThrowsException(): void
@ -1082,7 +1111,7 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$invoice->toXml(); $invoice->toXmlString();
} }
public function testInvalidDateFormatThrowsException(): void public function testInvalidDateFormatThrowsException(): void
@ -1113,7 +1142,7 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$invoice->toXml(); $invoice->toXmlString();
} }
public function testInvalidNIFFormatThrowsException(): void public function testInvalidNIFFormatThrowsException(): void
@ -1151,7 +1180,7 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$invoice->toXml(); $invoice->toXmlString();
} }
public function testInvalidAmountFormatThrowsException(): void public function testInvalidAmountFormatThrowsException(): void
@ -1182,7 +1211,7 @@ class InvoiceTest extends BaseModelTest
->setNumeroInstalacion('INST-001'); ->setNumeroInstalacion('INST-001');
$invoice->setSistemaInformatico($sistema); $invoice->setSistemaInformatico($sistema);
$invoice->toXml(); $invoice->toXmlString();
} }
public function testInvalidSchemaThrowsException(): void public function testInvalidSchemaThrowsException(): void
@ -1253,7 +1282,7 @@ class InvoiceTest extends BaseModelTest
->setCertificatePath($certificatePath); ->setCertificatePath($certificatePath);
// Generate signed XML // Generate signed XML
$xml = $invoice->toXml(); $xml = $invoice->toXmlString();
// Debug output // Debug output
echo "\nGenerated XML with signature:\n"; echo "\nGenerated XML with signature:\n";

View File

@ -1,25 +1,24 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEITCCAwmgAwIBAgIULRklu/Ae+uWUdejwvfO7wKhs0lIwDQYJKoZIhvcNAQEL MIIEATCCAumgAwIBAgIUKnHs6qxWiKTbXcDRrT6GNnbuYt0wDQYJKoZIhvcNAQEL
BQAwgZ8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1h BQAwgY8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1h
ZHJpZDEaMBgGA1UECgwRVGVzdCBPcmdhbml6YXRpb24xFjAUBgNVBAsMDUlUIERl ZHJpZDEVMBMGA1UECgwMVGVzdCBDb21wYW55MQswCQYDVQQLDAJJVDEZMBcGA1UE
cGFydG1lbnQxGTAXBgNVBAMMEHRlc3QuZXhhbXBsZS5jb20xHzAdBgkqhkiG9w0B AwwQdGVzdC5leGFtcGxlLmNvbTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl
CQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMjUwNDI1MDQxMzA2WhcNMjYwNDI1MDQx LmNvbTAeFw0yNTA0MjUwNTAwMTNaFw0yNjA0MjUwNTAwMTNaMIGPMQswCQYDVQQG
MzA2WjCBnzELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwG EwJFUzEPMA0GA1UECAwGTWFkcmlkMQ8wDQYDVQQHDAZNYWRyaWQxFTATBgNVBAoM
TWFkcmlkMRowGAYDVQQKDBFUZXN0IE9yZ2FuaXphdGlvbjEWMBQGA1UECwwNSVQg DFRlc3QgQ29tcGFueTELMAkGA1UECwwCSVQxGTAXBgNVBAMMEHRlc3QuZXhhbXBs
RGVwYXJ0bWVudDEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNvbTEfMB0GCSqGSIb3 ZS5jb20xHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wggEiMA0GCSqG
DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8A3xqN1BhPw42Ja5W40Wi6q5NC0I7WuCz
AQoCggEBALZExtAsqspmBMot0/D1Ska36SaEEIZurdgh5odE2jcKWubJQegu4tAu HTXUIKR6oARtRr28in6dA1hDu3V4hZgvNqveZO9vJOh+mWGWvcHK57mRMc6YJ0Nj
Eq8LbL6bw8qmJZ6txp3fknIK3RjSyT39Rk0KtpGvrVjFPLAD42UNr8fVspPDHUR9 bgGKBsGLHvfT8ErDOLjw+BrfC0PRiPWGvXqubt6y6whg4AklskPtT8oAgKvLQlo1
yUvSpXoDxtcL6y6pn08jNnBFHkSnQK9br5wiNXWhSDkRTKfJ6Jvc269dA1qcJ2jU mZMo1k9Tnl8cXwFHibCnl6JqkGX2eUeBevAVCJ5//ZgIsHX19rg165kfg8KlcB4C
S1k5TXULhZwwvjeTFKb9+xwRjdnLlyh4+WSUvwUMDFgXeKn1iQppZ0Oc+G4Xyq8W Ts+w+4iLwgW0PnzwdINKtpd7h7HXzkfVIvs32sbYlOOoA7e3Ouqm3/72BlSSHMlv
oViOwruNMT3iUN83I3wKk3NOkdbx/IxD87UB19mKdmV4X1Nlj+m+/gidDf2DmriM iTLDNAe+5ogTIaGvxxKG+meTiWSULIcba1M3+Ou0Kmp1I4BZWBWJAgMBAAGjUzBR
FtnPp3kPaqwWBB+rV4AggwnKAOwfmUECAwEAAaNTMFEwHQYDVR0OBBYEFLR1jHET MB0GA1UdDgQWBBQ22t7nxuwbRP36CUFxtKlm7fByXjAfBgNVHSMEGDAWgBQ22t7n
y8qVyFzsD9Ui0466XsLqMB8GA1UdIwQYMBaAFLR1jHETy8qVyFzsD9Ui0466XsLq xuwbRP36CUFxtKlm7fByXjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIllEMRCi/n6bqfv A4IBAQAIOJZkAmpRy+oldLJ3jRg76J8Vt61vPdMtyY+gHYE0rlkdKWqZcrrWoSbO
3eMuzHBxvUNNjmTIbEfnPyjf81FB78e1V3mXE5cYcVbDOwbvzZKa6qcioG88iW8Y LrhjcocGp13G6yhOwebDVeHlYI0ninLOynmLWOJTm+BVesQXfN5u2U9CilpFLcid
5b9RnLQjy9os7xhne1BJq3J2B6XKlHv9O1f5x1fb6nZ/8m7XyGmXctIGs3K29/sF 8Q5Z0jqWGjPwA+dcaiVi0xeYbaiUD5+vUEjpC4lmx505XQnUvKMk6TG0E5gn8KFO
zFqx7UgSujZCtKannnjS7RCMTbCixhxVbgwBRSkTLBxNWssHaJ7q4ktrfKdXSHAm Na1lXww1Pu1yaxspwjQQNaOCYS736LM2T+IBiuoq56QGp2hO1GdeFC+VhmQgaqdI
BhfgrvbILYYYN38i7Hfwru7m6Uoo7I+sy8yfHZzm3DwvswVRpRFIWLtkc6PBbyE4 p/sQl/+Mkvmu/Co7V61J5ZHZeQCVKFKjDfHTCwb/+6yO3ZIqyn8YfK2KF71qfGc5
xXWjoJrbFGyzSdLg+DX5SpJ4569Jueuj2LiDCsJ3r1whlUO/gDfB3jdrrUrkxrBq wgPkBJiznHo8tQvbndKWN9Gx5r1X
mmW22K8=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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
RaRkhB6Q8tXUwjQ+HfCpw9Q= +xTBYVY8fTd1+jO8VruQNg==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@ -1,9 +1,9 @@
-----BEGIN PUBLIC KEY----- -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtkTG0CyqymYEyi3T8PVK MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvAN8ajdQYT8ONiWuVuNF
RrfpJoQQhm6t2CHmh0TaNwpa5slB6C7i0C4SrwtsvpvDyqYlnq3Gnd+ScgrdGNLJ ouquTQtCO1rgsx011CCkeqAEbUa9vIp+nQNYQ7t1eIWYLzar3mTvbyTofplhlr3B
Pf1GTQq2ka+tWMU8sAPjZQ2vx9Wyk8MdRH3JS9KlegPG1wvrLqmfTyM2cEUeRKdA yue5kTHOmCdDY24BigbBix730/BKwzi48Pga3wtD0Yj1hr16rm7esusIYOAJJbJD
r1uvnCI1daFIORFMp8nom9zbr10DWpwnaNRLWTlNdQuFnDC+N5MUpv37HBGN2cuX 7U/KAICry0JaNZmTKNZPU55fHF8BR4mwp5eiapBl9nlHgXrwFQief/2YCLB19fa4
KHj5ZJS/BQwMWBd4qfWJCmlnQ5z4bhfKrxahWI7Cu40xPeJQ3zcjfAqTc06R1vH8 NeuZH4PCpXAeAk7PsPuIi8IFtD588HSDSraXe4ex185H1SL7N9rG2JTjqAO3tzrq
jEPztQHX2Yp2ZXhfU2WP6b7+CJ0N/YOauIwW2c+neQ9qrBYEH6tXgCCDCcoA7B+Z pt/+9gZUkhzJb4kywzQHvuaIEyGhr8cShvpnk4lklCyHG2tTN/jrtCpqdSOAWVgV
QQIDAQAB iQIDAQAB
-----END PUBLIC KEY----- -----END PUBLIC KEY-----