presseportale/app/Services/Billing/VatResolver.php
2026-06-12 14:36:18 +00:00

67 lines
2.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services\Billing;
use App\Enums\VatTreatment;
/**
* Bestimmt die USt-Behandlung einer Rechnung anhand der Rechnungsadresse
* (Entscheidung 12.06.2026):
*
* - Deutschland → grundsätzlich immer mit Steuer (`billing.vat_rate`)
* - EU-Ausland → nur mit gültiger USt-ID befreit (Reverse Charge),
* sonst mit Steuer
* - Drittland → grundsätzlich steuerbefreit
*
* „Gültig" heißt aktuell: USt-ID vorhanden und formal plausibel
* (Ländercode + Kennziffer). Eine echte VIES-Validierung ist als
* Folgeschritt vorgesehen (siehe Phase-9-Plan).
*/
class VatResolver
{
public function resolve(?string $countryCode, ?string $vatId = null): VatTreatment
{
$countryCode = strtoupper((string) $countryCode);
if ($countryCode === '' || $countryCode === 'DE') {
return VatTreatment::Domestic;
}
if (! in_array($countryCode, (array) config('billing.eu_country_codes', []), true)) {
return VatTreatment::ThirdCountry;
}
return $this->isPlausibleVatId($vatId, $countryCode)
? VatTreatment::ReverseCharge
: VatTreatment::EuConsumer;
}
public function rateFor(VatTreatment $treatment): float
{
return $treatment->isTaxExempt() ? 0.0 : (float) config('billing.vat_rate', 0.19);
}
public function taxCentsFor(int $netCents, VatTreatment $treatment): int
{
return (int) round($netCents * $this->rateFor($treatment));
}
/**
* Formale Plausibilität: beginnt mit dem Ländercode der Adresse und
* trägt danach 213 alphanumerische Zeichen (EU-Formatrahmen).
* Public, damit der VatIdValidationService dieselbe Definition nutzt.
*/
public function isPlausibleVatId(?string $vatId, string $countryCode): bool
{
$vatId = strtoupper(preg_replace('/\s+/', '', (string) $vatId) ?? '');
if ($vatId === '') {
return false;
}
// Griechenland nutzt das Präfix EL statt GR.
$expectedPrefix = $countryCode === 'GR' ? 'EL' : $countryCode;
return (bool) preg_match('/^'.preg_quote($expectedPrefix, '/').'[A-Z0-9]{2,13}$/', $vatId);
}
}