From a431dd43d45c80f28cff848943d500b5337171aa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Jun 2025 17:29:48 +1000 Subject: [PATCH] Fixes for verifactu WS --- composer.lock | 73 +- .../EInvoice/Verifactu/Models/WSTest.php | 850 ++---------------- 2 files changed, 92 insertions(+), 831 deletions(-) diff --git a/composer.lock b/composer.lock index 590bc50fc4..d9aa7b00e1 100644 --- a/composer.lock +++ b/composer.lock @@ -6267,16 +6267,16 @@ }, { "name": "league/csv", - "version": "9.24.0", + "version": "9.24.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "3f11e12ccd7d6dc25729056e7ce386e4084089ea" + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/3f11e12ccd7d6dc25729056e7ce386e4084089ea", - "reference": "3f11e12ccd7d6dc25729056e7ce386e4084089ea", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/e0221a3f16aa2a823047d59fab5809d552e29bc8", + "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8", "shasum": "" }, "require": { @@ -6354,20 +6354,20 @@ "type": "github" } ], - "time": "2025-06-24T19:54:25+00:00" + "time": "2025-06-25T14:53:51+00:00" }, { "name": "league/flysystem", - "version": "3.29.1", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", "shasum": "" }, "require": { @@ -6391,13 +6391,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", - "ext-mongodb": "^1.3", + "ext-mongodb": "^1.3|^2", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2", + "mongodb/mongodb": "^1.2|^2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -6435,9 +6435,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" }, - "time": "2024-10-08T08:58:34+00:00" + "time": "2025-06-25T13:29:59+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -6496,16 +6496,16 @@ }, { "name": "league/flysystem-local", - "version": "3.29.0", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10", + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10", "shasum": "" }, "require": { @@ -6539,9 +6539,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2025-05-21T10:34:19+00:00" }, { "name": "league/fractal", @@ -8603,16 +8603,16 @@ }, { "name": "open-telemetry/api", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "4e3bb38e069876fb73c2ce85c89583bf2b28cd86" + "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/4e3bb38e069876fb73c2ce85c89583bf2b28cd86", - "reference": "4e3bb38e069876fb73c2ce85c89583bf2b28cd86", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", + "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", "shasum": "" }, "require": { @@ -8632,7 +8632,7 @@ ] }, "branch-alias": { - "dev-main": "1.1.x-dev" + "dev-main": "1.4.x-dev" } }, "autoload": { @@ -8669,7 +8669,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-07T12:32:21+00:00" + "time": "2025-06-19T23:36:51+00:00" }, { "name": "open-telemetry/context", @@ -10785,21 +10785,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -10858,9 +10857,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "razorpay/razorpay", @@ -18360,16 +18359,16 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.1", + "version": "v8.8.2", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5" + "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5", - "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", + "reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb", "shasum": "" }, "require": { @@ -18455,7 +18454,7 @@ "type": "patreon" } ], - "time": "2025-06-11T01:04:21+00:00" + "time": "2025-06-25T02:12:12+00:00" }, { "name": "phar-io/manifest", diff --git a/tests/Feature/EInvoice/Verifactu/Models/WSTest.php b/tests/Feature/EInvoice/Verifactu/Models/WSTest.php index 686b5d9663..07ffae1e15 100644 --- a/tests/Feature/EInvoice/Verifactu/Models/WSTest.php +++ b/tests/Feature/EInvoice/Verifactu/Models/WSTest.php @@ -13,10 +13,9 @@ 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 = << + - Certificate One Telematics + CERTIFICADO FISICA PRUEBAS 99999910G 1.0 + 99999910G - TEST-001 + TEST-003 24-06-2025 - Certificate One Telematics + + CERTIFICADO FISICA PRUEBAS F1 - Test invoice + Test invoice submitted by computer system on behalf of business - Test Recipient - 99999999A + Test Recipient Company + B12345674 @@ -59,10 +61,15 @@ class WSTest extends TestCase 21.00 121.00 + + + S + + - SSSS + CERTIFICADO FISICA PRUEBAS 99999910G - NombreSistemaInformatico + InvoiceNinja 77 1.0.03 383 @@ -70,7 +77,7 @@ class WSTest extends TestCase S S - 2025-06-24T22:34:00+01:00 + {$currentTimestamp} 01 PLACEHOLDER_HUELLA @@ -80,9 +87,17 @@ class WSTest extends TestCase XML; - // Calculate the correct hash for the XML content (excluding the signature) - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); + // 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); @@ -106,7 +121,8 @@ XML; 'verify' => false, 'timeout' => 30, ]) - ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $soapXml); + ->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); @@ -124,787 +140,33 @@ XML; } /** - * Extract the XML content that should be used for hash calculation - * This excludes the signature and focuses on the business data + * Calculate Verifactu hash using AEAT's specified format + * Based on AEAT response showing the exact format they use */ - private function getXmlForHashCalculation(string $fullXml): string - { - $doc = new \DOMDocument(); - $doc->loadXML($fullXml); + 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}"; - // Find the RegistroAlta element - $registroAlta = $doc->getElementsByTagNameNS('https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd', 'RegistroAlta')->item(0); + \Log::info('Hash input string: ' . $hashInput); - if (!$registroAlta) { - throw new \Exception('RegistroAlta element not found'); - } - - // Create a new document with just the RegistroAlta content - $hashDoc = new \DOMDocument('1.0', 'UTF-8'); - $hashDoc->preserveWhiteSpace = false; - $hashDoc->formatOutput = false; - - // Import the RegistroAlta node - $importedNode = $hashDoc->importNode($registroAlta, true); - $hashDoc->appendChild($importedNode); - - // Return the XML string for hash calculation - return $hashDoc->saveXML(); + // Calculate SHA256 hash and return in uppercase + return strtoupper(hash('sha256', $hashInput)); } - public function test_send_brand_new_invoice_to_verifactu() - { - // Create a completely new invoice with fresh data - $soapXml = << - - - - - - - NUEVA EMPRESA TEST SL - 99999910G - - - - - 1.0 - - 99999910G - FAC-2025-001 - 24-06-2025 - - NUEVA EMPRESA TEST SL - F1 - Venta de servicios informáticos - - - CLIENTE TEST SA - B12345678 - - - - - 01 - S1 - 21 - 500.00 - 105.00 - - - 105.00 - 605.00 - - INVOICE NINJA - 99999910G - InvoiceNinja - 001 - 5.0 - 001 - S - N - N - - 2025-06-24T22:40:00+01:00 - 01 - PLACEHOLDER_HUELLA - - - - - -XML; - - // Calculate the correct hash for the XML content - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('Brand new invoice - Calculated hash: ' . $correctHash); - - // Try the Sello endpoint for certificate-based access - $endpoint = 'https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP'; - $certPath = storage_path('aeat-cert2.pem'); - $keyPath = storage_path('aeat-key2.pem'); - - // Sign the XML - $signingService = new \App\Services\EDocument\Standards\Verifactu\Signing\SigningService($soapXml, file_get_contents($keyPath), file_get_contents($certPath)); - $soapXml = $signingService->sign(); - - // Send the request - $response = Http::withHeaders([ - 'Content-Type' => 'text/xml; charset=utf-8', - 'SOAPAction' => '', - ]) - ->withOptions([ - 'cert' => $certPath, - 'ssl_key' => $keyPath, - 'verify' => false, - 'timeout' => 30, - ]) - ->post($endpoint, $soapXml); - - \Log::info('Brand new invoice request:'); - \Log::info($soapXml); - \Log::info('Brand new invoice response:'); - \Log::info('Response Status: ' . $response->status()); - \Log::info('Response Headers: ' . json_encode($response->headers())); - \Log::info('Response Body: ' . $response->body()); - - // Check if the response contains an error - $responseBody = $response->body(); - if (strpos($responseBody, 'Error interno en el servidor') !== false) { - \Log::error('SOAP response contains server error: ' . $responseBody); - $this->fail('SOAP response contains server error: ' . $responseBody); - } - - // Check if the response contains a success message - if (strpos($responseBody, 'RegFactuSistemaFacturacionResponse') !== false) { - \Log::info('SOAP response contains success message'); - } - - $this->assertTrue($response->successful()); - } - - public function test_send_official_example_to_verifactu() - { - // Use the exact structure from the official AEAT example - $soapXml = << - - - - - - - 99999910G - 99999910G - - - - - 1.0 - - 99999910G - 12345 - 24-06-2025 - - 99999910G - F1 - Test invoice following official example - - - B12345678 - B12345678 - - - - - 01 - S1 - 4 - 10.00 - 0.40 - - - 01 - S1 - 21 - 100.00 - 21.00 - - - 21.40 - 131.40 - - - 99999910G - 44 - 24-06-2025 - HuellaRegistroAnterior - - - - 99999910G - 99999910G - InvoiceNinja - 77 - 1.0.03 - 383 - N - S - S - - 2025-06-24T22:40:00+01:00 - 01 - PLACEHOLDER_HUELLA - - - - - -XML; - - // Calculate the correct hash for the XML content - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('Official example - Calculated hash: ' . $correctHash); - - // Try the standard Verifactu endpoint - $endpoint = 'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP'; - $certPath = storage_path('aeat-cert2.pem'); - $keyPath = storage_path('aeat-key2.pem'); - - // Sign the XML - $signingService = new \App\Services\EDocument\Standards\Verifactu\Signing\SigningService($soapXml, file_get_contents($keyPath), file_get_contents($certPath)); - $soapXml = $signingService->sign(); - - // Send the request - $response = Http::withHeaders([ - 'Content-Type' => 'text/xml; charset=utf-8', - 'SOAPAction' => '', - ]) - ->withOptions([ - 'cert' => $certPath, - 'ssl_key' => $keyPath, - 'verify' => false, - 'timeout' => 30, - ]) - ->post($endpoint, $soapXml); - - \Log::info('Official example request:'); - \Log::info($soapXml); - \Log::info('Official example response:'); - \Log::info('Response Status: ' . $response->status()); - \Log::info('Response Headers: ' . json_encode($response->headers())); - \Log::info('Response Body: ' . $response->body()); - - // Check if the response contains an error - $responseBody = $response->body(); - if (strpos($responseBody, 'Error interno en el servidor') !== false) { - \Log::error('SOAP response contains server error: ' . $responseBody); - $this->fail('SOAP response contains server error: ' . $responseBody); - } - - // Check if the response contains a success message - if (strpos($responseBody, 'RegFactuSistemaFacturacionResponse') !== false) { - \Log::info('SOAP response contains success message'); - } - - $this->assertTrue($response->successful()); - } - - public function test_send_new_invoice_without_chaining() - { - // Create a completely new invoice without any chaining - $soapXml = << - - - - - - - - 99999910G - Empresa Ejemplo SL - - - - 99999910G - Mi Sistema - 1.0 - - - 2025 - 06 - - - - - - 99999910G - Empresa Ejemplo SL - - FAC001 - 25-06-2025 - - - F1 - 25-06-2025 - - - ES - 02 - 12345678A - - Cliente Ejemplo - 99999910G - - - Venta de productos - - RL - 100.00 - - 100.00 - 100.00 - - - - ES - 02 - 12345678A - - Cliente Ejemplo - - - - - - -XML; - - // Calculate the correct hash for this XML - $xmlForHash = str_replace('PLACEHOLDER_HUELLA', '', $soapXml); - $xmlForHash = preg_replace('/.*?<\/sum1:HuellaDigital>/s', '', $xmlForHash); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('New invoice without chaining - Calculated hash: ' . $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(); - - // Send the request - $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, - ]) - ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $soapXml); - - \Log::info('New invoice without chaining response:'); - \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()); - } - - public function test_send_official_structure_with_real_data() - { - // Use the exact structure from the official example but with real data - $soapXml = << - - - - - - - Empresa Test SL - 99999910G - - - - - 1.0 - - 99999910G - TEST-001 - 24-06-2025 - - Empresa Test SL - F1 - Venta de productos de prueba - - - Cliente Test SL - B12345678 - - - - Venta de productos de prueba - - RL - 100.00 - - 100.00 - 100.00 - - - - ES - 02 - B12345678 - - Cliente Test SL - - - S - - 2025-06-24T22:34:00+01:00 - 01 - PLACEHOLDER_HUELLA - - - - - -XML; - - // Calculate the correct hash for this XML - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('Official structure with real data - Calculated hash: ' . $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(); - - // Send the request - $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, - ]) - ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $soapXml); - - \Log::info('Official structure with real data response:'); - \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()); - } - - public function test_send_minimal_invoice_to_aeat() - { - // Create the most minimal invoice possible with only required fields - $soapXml = << - - - - - - - Test Company - 99999910G - - - - - 1.0 - - 99999910G - 001 - 01-01-2024 - - Test Company - F1 - Test - - Test - - RL - 10.00 - - 10.00 - - - S - - 2024-01-01T12:00:00+01:00 - 01 - PLACEHOLDER_HUELLA - - - - - -XML; - - // Calculate the correct hash for this XML - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('Minimal invoice - Calculated hash: ' . $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(); - - // Send the request - $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, - ]) - ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $soapXml); - - \Log::info('Minimal invoice response:'); - \Log::info('Response Status: ' . $response->status()); - \Log::info('Response Headers: ' . json_encode($response->headers())); - \Log::info('Response Body: ' . $response->body()); - - // Check if we got a different response - $responseBody = $response->body(); - if (strpos($responseBody, 'Error interno en el servidor') === false) { - \Log::info('SUCCESS: No server error found in response!'); - } else { - \Log::error('Still getting server error with minimal invoice'); - } - - $this->assertTrue($response->successful()); - } - - public function test_send_ultra_minimal_invoice_to_aeat() - { - // Ultra minimal invoice with current date and no optional fields - $currentDate = date('d-m-Y'); - $currentDateTime = date('Y-m-d\TH:i:sP'); - - $soapXml = << - - - - - - - Test - 99999910G - - - - - 1.0 - - 99999910G - 1 - $currentDate - - Test - F1 - Test - - Test - - RL - 1.00 - - 1.00 - - - S - - $currentDateTime - 01 - PLACEHOLDER_HUELLA - - - - - -XML; - - // Calculate the correct hash for this XML - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('Ultra minimal invoice - Calculated hash: ' . $correctHash); - \Log::info('Using date: ' . $currentDate . ' and datetime: ' . $currentDateTime); - - // 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(); - - // Send the request - $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, - ]) - ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $soapXml); - - \Log::info('Ultra minimal invoice response:'); - \Log::info('Response Status: ' . $response->status()); - \Log::info('Response Headers: ' . json_encode($response->headers())); - \Log::info('Response Body: ' . $response->body()); - - // Check if we got a different response - $responseBody = $response->body(); - if (strpos($responseBody, 'Error interno en el servidor') === false) { - \Log::info('SUCCESS: No server error found in ultra minimal response!'); - } else { - \Log::error('Still getting server error with ultra minimal invoice'); - } - - $this->assertTrue($response->successful()); - } - - public function test_send_invoice_without_encadenamiento() - { - // Try without Encadenamiento section - $currentDate = date('d-m-Y'); - $currentDateTime = date('Y-m-d\TH:i:sP'); - - $soapXml = << - - - - - - - Test - 99999910G - - - - - 1.0 - - 99999910G - 1 - $currentDate - - Test - F1 - Test - - Test - - RL - 1.00 - - 1.00 - - $currentDateTime - 01 - PLACEHOLDER_HUELLA - - - - - -XML; - - // Calculate the correct hash for this XML - $xmlForHash = $this->getXmlForHashCalculation($soapXml); - $correctHash = strtoupper(hash('sha256', $xmlForHash)); - - // Replace the placeholder with the correct hash - $soapXml = str_replace('PLACEHOLDER_HUELLA', $correctHash, $soapXml); - - \Log::info('Invoice without Encadenamiento - Calculated hash: ' . $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(); - - // Send the request - $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, - ]) - ->post('https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', $soapXml); - - \Log::info('Invoice without Encadenamiento response:'); - \Log::info('Response Status: ' . $response->status()); - \Log::info('Response Headers: ' . json_encode($response->headers())); - \Log::info('Response Body: ' . $response->body()); - - // Check if we got a different response - $responseBody = $response->body(); - if (strpos($responseBody, 'Error interno en el servidor') === false) { - \Log::info('SUCCESS: No server error found without Encadenamiento!'); - } else { - \Log::error('Still getting server error without Encadenamiento'); - } - - $this->assertTrue($response->successful()); - } } \ No newline at end of file