invoiceninja/tests/Feature/EInvoice/Verifactu/Models/WSTest.php

172 lines
8.1 KiB
PHP

<?php
namespace Tests\Feature\EInvoice\Verifactu\Models;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class WSTest extends TestCase
{
public function test_send_aeat_example_to_verifactu()
{
// Generate current timestamp in the correct format
$currentTimestamp = date('Y-m-d\TH:i:sP');
$soapXml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sum="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd"
xmlns:sum1="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd">
<soapenv:Header/>
<soapenv:Body>
<sum:RegFactuSistemaFacturacion>
<sum:Cabecera>
<!-- ObligadoEmision: The computer system submitting on behalf of the invoice issuer -->
<sum1:ObligadoEmision>
<sum1:NombreRazon>CERTIFICADO FISICA PRUEBAS</sum1:NombreRazon>
<sum1:NIF>99999910G</sum1:NIF>
</sum1:ObligadoEmision>
</sum:Cabecera>
<sum:RegistroFactura>
<sum1:RegistroAlta>
<sum1:IDVersion>1.0</sum1:IDVersion>
<!-- IDFactura: The actual invoice issuer (using same test NIF) -->
<sum1:IDFactura>
<sum1:IDEmisorFactura>99999910G</sum1:IDEmisorFactura>
<sum1:NumSerieFactura>TEST-003</sum1:NumSerieFactura>
<sum1:FechaExpedicionFactura>24-06-2025</sum1:FechaExpedicionFactura>
</sum1:IDFactura>
<!-- NombreRazonEmisor: The actual business that issued the invoice -->
<sum1:NombreRazonEmisor>CERTIFICADO FISICA PRUEBAS</sum1:NombreRazonEmisor>
<sum1:TipoFactura>F1</sum1:TipoFactura>
<sum1:DescripcionOperacion>Test invoice submitted by computer system on behalf of business</sum1:DescripcionOperacion>
<sum1:Destinatarios>
<sum1:IDDestinatario>
<sum1:NombreRazon>Test Recipient Company</sum1:NombreRazon>
<sum1:NIF>B12345674</sum1:NIF>
</sum1:IDDestinatario>
</sum1:Destinatarios>
<sum1:Desglose>
<sum1:DetalleDesglose>
<sum1:ClaveRegimen>01</sum1:ClaveRegimen>
<sum1:CalificacionOperacion>S1</sum1:CalificacionOperacion>
<sum1:TipoImpositivo>21</sum1:TipoImpositivo>
<sum1:BaseImponibleOimporteNoSujeto>100.00</sum1:BaseImponibleOimporteNoSujeto>
<sum1:CuotaRepercutida>21.00</sum1:CuotaRepercutida>
</sum1:DetalleDesglose>
</sum1:Desglose>
<sum1:CuotaTotal>21.00</sum1:CuotaTotal>
<sum1:ImporteTotal>121.00</sum1:ImporteTotal>
<!-- Encadenamiento: Required chaining information -->
<sum1:Encadenamiento>
<sum1:PrimerRegistro>S</sum1:PrimerRegistro>
</sum1:Encadenamiento>
<!-- SistemaInformatico: The computer system details (same as ObligadoEmision) -->
<sum1:SistemaInformatico>
<sum1:NombreRazon>CERTIFICADO FISICA PRUEBAS</sum1:NombreRazon>
<sum1:NIF>99999910G</sum1:NIF>
<sum1:NombreSistemaInformatico>InvoiceNinja</sum1:NombreSistemaInformatico>
<sum1:IdSistemaInformatico>77</sum1:IdSistemaInformatico>
<sum1:Version>1.0.03</sum1:Version>
<sum1:NumeroInstalacion>383</sum1:NumeroInstalacion>
<sum1:TipoUsoPosibleSoloVerifactu>N</sum1:TipoUsoPosibleSoloVerifactu>
<sum1:TipoUsoPosibleMultiOT>S</sum1:TipoUsoPosibleMultiOT>
<sum1:IndicadorMultiplesOT>S</sum1:IndicadorMultiplesOT>
</sum1:SistemaInformatico>
<sum1:FechaHoraHusoGenRegistro>{$currentTimestamp}</sum1:FechaHoraHusoGenRegistro>
<sum1:TipoHuella>01</sum1:TipoHuella>
<sum1:Huella>PLACEHOLDER_HUELLA</sum1:Huella>
</sum1:RegistroAlta>
</sum:RegistroFactura>
</sum:RegFactuSistemaFacturacion>
</soapenv:Body>
</soapenv:Envelope>
XML;
// Calculate the correct hash using AEAT's specified format
$correctHash = $this->calculateVerifactuHash(
'99999910G', // IDEmisorFactura
'TEST-003', // NumSerieFactura
'24-06-2025', // FechaExpedicionFactura
'F1', // TipoFactura
'21.00', // CuotaTotal
'121.00', // ImporteTotal
'', // Huella (empty for first calculation)
$currentTimestamp // FechaHoraHusoGenRegistro (current time)
);
// Replace the placeholder with the correct hash
$soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml);
\Log::info('Calculated hash for XML: ' . $correctHash);
// Sign the XML before sending
$certPath = storage_path('aeat-cert2.pem');
$keyPath = storage_path('aeat-key2.pem');
$signingService = new \App\Services\EDocument\Standards\Verifactu\Signing\SigningService($soapXml, file_get_contents($keyPath), file_get_contents($certPath));
$soapXml = $signingService->sign();
// Try direct HTTP approach instead of SOAP client
$response = Http::withHeaders([
'Content-Type' => 'text/xml; charset=utf-8',
'SOAPAction' => '',
])
->withOptions([
'cert' => storage_path('aeat-cert2.pem'),
'ssl_key' => storage_path('aeat-key2.pem'),
'verify' => false,
'timeout' => 30,
])
->withBody($soapXml, 'text/xml')
->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP');
\Log::info('Request with AEAT official test data:');
\Log::info($soapXml);
\Log::info('Response with AEAT official test data:');
\Log::info('Response Status: ' . $response->status());
\Log::info('Response Headers: ' . json_encode($response->headers()));
\Log::info('Response Body: ' . $response->body());
if (!$response->successful()) {
\Log::error('Request failed with status: ' . $response->status());
\Log::error('Response body: ' . $response->body());
}
$this->assertTrue($response->successful());
}
/**
* Calculate Verifactu hash using AEAT's specified format
* Based on AEAT response showing the exact format they use
*/
private function calculateVerifactuHash(
string $idEmisorFactura,
string $numSerieFactura,
string $fechaExpedicionFactura,
string $tipoFactura,
string $cuotaTotal,
string $importeTotal,
string $huella,
string $fechaHoraHusoGenRegistro
): string {
// Build the hash input string exactly as AEAT expects it
$hashInput = "IDEmisorFactura={$idEmisorFactura}&" .
"NumSerieFactura={$numSerieFactura}&" .
"FechaExpedicionFactura={$fechaExpedicionFactura}&" .
"TipoFactura={$tipoFactura}&" .
"CuotaTotal={$cuotaTotal}&" .
"ImporteTotal={$importeTotal}&" .
"Huella={$huella}&" .
"FechaHoraHusoGenRegistro={$fechaHoraHusoGenRegistro}";
\Log::info('Hash input string: ' . $hashInput);
// Calculate SHA256 hash and return in uppercase
return strtoupper(hash('sha256', $hashInput));
}
}