Skip to content
Merged
6 changes: 3 additions & 3 deletions Modules/Invoices/Http/Traits/LogsApiRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ protected function logRequest(string $method, string $uri, array $options): void
return;
}

Log::info('API Request', [
Log::info('HTTP Request', [
'method' => $method,
'uri' => $uri,
'options' => $this->sanitizeForLogging($options),
Expand All @@ -81,7 +81,7 @@ protected function logResponse(string $method, string $uri, int $status, mixed $
return;
}

Log::info('API Response', [
Log::info('HTTP Response', [
'method' => $method,
'uri' => $uri,
'status' => $status,
Expand All @@ -102,7 +102,7 @@ protected function logResponse(string $method, string $uri, int $status, mixed $
*/
protected function logError(string $type, string $method, string $uri, string $message, array $context = []): void
{
Log::error("API {$type} Error", array_merge([
Log::error("HTTP {$type} Error", array_merge([
'method' => $method,
'uri' => $uri,
'message' => $message,
Expand Down
10 changes: 9 additions & 1 deletion Modules/Invoices/Peppol/Clients/BasePeppolClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,15 @@ public function getClient(): HttpClientExceptionHandler
*/
public function getRequestOptions(array $options = []): array
{
// Implement logic or return options as needed
// Merge authentication headers with any existing headers
// Auth headers are merged AFTER existing headers to ensure they take precedence
// and cannot be overridden by caller-provided headers for security
$authHeaders = $this->getAuthenticationHeaders();
$existingHeaders = $options['headers'] ?? [];

$options['headers'] = array_merge($existingHeaders, $authHeaders);
$options['timeout'] = $options['timeout'] ?? $this->getTimeout();

return $options;
}

Expand Down
7 changes: 4 additions & 3 deletions Modules/Invoices/Peppol/Enums/PeppolDocumentFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ public function description(): string
self::ZUGFERD_10 => 'German standard combining PDF with embedded XML invoice data.',
self::ZUGFERD_20 => 'Updated ZUGFeRD compatible with Factur-X. Uses CII format.',
self::OIOUBL => 'Danish UBL-based format with national extensions.',
self::EHF_30 => 'Norwegian EHF 3.0 format for Peppol network.',
self::PEPPOL_BIS_30 => 'Default Peppol format for most countries. Based on UBL.',
self::EHF_30 => 'Norwegian EHF 3.0 format for PEPPOL network.',
self::PEPPOL_BIS_30 => 'Default PEPPOL format for most countries. Based on UBL.',
};
}

Expand Down Expand Up @@ -220,7 +220,8 @@ public function isMandatoryFor(?string $countryCode): bool

return match ($this) {
self::FATTURAPA_12 => $country === 'IT',
self::FACTURAE_32 => $country === 'ES', // For public administration
// Note: FACTURAE_32 is only mandatory for Spanish public administration
// Not for all invoices in Spain, so we return false
default => false,
};
}
Expand Down
39 changes: 32 additions & 7 deletions Modules/Invoices/Peppol/FormatHandlers/BaseFormatHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Modules\Invoices\Models\Invoice;
use Modules\Invoices\Peppol\Enums\PeppolDocumentFormat;
use Modules\Invoices\Peppol\Enums\PeppolEndpointScheme;
use RuntimeException;

/**
* BaseFormatHandler - Abstract base class for invoice format handlers.
Expand All @@ -17,16 +18,32 @@ abstract class BaseFormatHandler implements InvoiceFormatHandlerInterface
/**
* The format this handler supports.
*
* @var PeppolDocumentFormat
* @var PeppolDocumentFormat|null
*/
protected PeppolDocumentFormat $format;
protected ?PeppolDocumentFormat $format = null;

/**
* Constructor.
*
* @param PeppolDocumentFormat|null $format The format this handler supports (optional)
*/
public function __construct(?PeppolDocumentFormat $format = null)
{
if ($format !== null) {
$this->format = $format;
}
}

/**
* Set the format for this handler.
*
* This method is called by the factory after instantiation.
*
* @param PeppolDocumentFormat $format The format this handler supports
*
* @return void
*/
public function __construct(PeppolDocumentFormat $format)
public function setFormat(PeppolDocumentFormat $format): void
{
$this->format = $format;
}
Expand All @@ -45,6 +62,10 @@ abstract protected function validateFormatSpecific(Invoice $invoice): array;
*/
public function getFormat(): PeppolDocumentFormat
{
if ($this->format === null) {
throw new RuntimeException('Format has not been set on this handler. Call setFormat() first.');
}

return $this->format;
}

Expand All @@ -53,18 +74,20 @@ public function getFormat(): PeppolDocumentFormat
*/
public function supports(Invoice $invoice): bool
{
$format = $this->getFormat();

// Check if customer's country matches format requirements
$customerCountry = $invoice->customer?->country_code ?? null;

// Mandatory formats must be used for their countries
if ($this->format->isMandatoryFor($customerCountry)) {
if ($format->isMandatoryFor($customerCountry)) {
return true;
}

// Check if format is suitable for customer's country
$suitableFormats = PeppolDocumentFormat::formatsForCountry($customerCountry);

return in_array($this->format, $suitableFormats, true);
return in_array($format, $suitableFormats, true);
}

/**
Expand Down Expand Up @@ -106,7 +129,9 @@ public function validate(Invoice $invoice): array
*/
public function getMimeType(): string
{
return $this->format->requiresPdfEmbedding()
$format = $this->getFormat();

return $format->requiresPdfEmbedding()
? 'application/pdf'
: 'application/xml';
}
Expand All @@ -116,7 +141,7 @@ public function getMimeType(): string
*/
public function getFileExtension(): string
{
return $this->format->extension();
return $this->getFormat()->extension();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ public static function create(PeppolDocumentFormat $format): InvoiceFormatHandle
throw new RuntimeException("No handler available for format: {$format->value}");
}

return app($handlerClass);
/** @var BaseFormatHandler $handler */
$handler = app($handlerClass);

// Set the format on the handler to ensure it matches what was requested
// This is especially important for handlers that can handle multiple formats (UBL, ZUGFeRD)
$handler->setFormat($format);

return $handler;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Mockery;
use Modules\Core\Tests\TestCase;
use Modules\Core\Tests\AbstractTestCase;
use Modules\Invoices\Http\Clients\ApiClient;
use Modules\Invoices\Http\Decorators\HttpClientExceptionHandler;
use Modules\Invoices\Http\RequestMethod;
Expand All @@ -22,7 +22,7 @@
* Uses HTTP fakes to simulate various scenarios.
*/
#[Group('peppol')]
class HttpClientExceptionHandlerTest extends TestCase
class HttpClientExceptionHandlerTest extends AbstractTestCase
{
protected HttpClientExceptionHandler $handler;

Expand Down Expand Up @@ -212,14 +212,6 @@ public function it_sanitizes_auth_credentials_in_logs(): void
}));
}

#[Test]
#[Group('http_client_failing')]
public function it_forwards_method_calls_to_wrapped_client(): void
{
// This test is not applicable since ApiClient only exposes request().
$this->assertTrue(true);
}

#[Test]
#[Group('http_client_failing')]
#[Group('failing')]
Expand Down Expand Up @@ -370,7 +362,7 @@ public function it_handles_http_exceptions(): void
'https://api.example.com/*' => Http::response(['error' => 'Not Found'], 404),
]);

/* act & assert */
/* Act & Assert */
$this->expectException(RequestException::class);
$this->handler->request(RequestMethod::GET, 'test');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Modules\Core\Tests\TestCase;
use Modules\Core\Tests\AbstractTestCase;
use Modules\Invoices\Http\Clients\ApiClient;
use Modules\Invoices\Http\Decorators\HttpClientExceptionHandler;
use Modules\Invoices\Peppol\Clients\EInvoiceBe\DocumentsClient;
Expand All @@ -18,7 +18,7 @@
* Verifies proper API integration and error handling.
*/
#[Group('peppol')]
class DocumentsClientTest extends TestCase
class DocumentsClientTest extends AbstractTestCase
{
protected DocumentsClient $client;

Expand Down
5 changes: 5 additions & 0 deletions pint_output.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@


No dirty files found.