CluPilotCloud/app/Services/Billing/TaxTreatment.php

59 lines
1.9 KiB
PHP

<?php
namespace App\Services\Billing;
use App\Models\Customer;
/**
* Which VAT applies to a customer, and why.
*
* Only the two clear-cut cases are decided here:
*
* - a customer with a VAT ID registered in another EU country pays no VAT to
* us; the liability shifts to them (reverse charge), and the invoice has to
* say so;
* - everyone else is charged the seller's domestic rate.
*
* Deliberately NOT handled: cross-border sales to private individuals, which
* are taxed at the customer's own country's rate under the OSS scheme. Doing
* that needs a maintained rate table per member state and a tax adviser's sign
* off, not a guess in a config file — so those customers fall back to the
* domestic rate, which over-collects rather than under-collects.
*/
final readonly class TaxTreatment
{
private function __construct(
public float $rate,
public bool $reverseCharge,
) {}
public static function for(?Customer $customer): self
{
$domestic = (float) config('provisioning.tax.rate_percent', 0) / 100;
$vatId = strtoupper(preg_replace('/\s+/', '', (string) $customer?->vat_id) ?? '');
if ($vatId === '') {
return new self($domestic, false);
}
// A VAT ID starts with its country's code. Same country as ours means a
// domestic business sale, which is taxed normally.
$sellerCountry = strtoupper((string) config('provisioning.tax.seller_country', 'AT'));
$customerCountry = substr($vatId, 0, 2);
return $customerCountry !== '' && $customerCountry !== $sellerCountry
? new self(0.0, true)
: new self($domestic, false);
}
public function grossCents(int $netCents): int
{
return (int) round($netCents * (1 + $this->rate));
}
public function percentLabel(): string
{
return rtrim(rtrim(number_format($this->rate * 100, 1, ',', '.'), '0'), ',');
}
}