CluPilotCloud/app/Services/Tax/VatIdShape.php

55 lines
1.9 KiB
PHP

<?php
namespace App\Services\Tax;
use App\Models\Customer;
use App\Services\Billing\TaxTreatment;
/**
* Whether a string is even worth asking VIES about.
*
* Shared by the real verifier and the fake, deliberately. A fake that answers
* where the real one would not have asked is not a simplification, it is a
* different product — and every test written against it proves something about
* that product instead of about this one.
*
* Two conditions, and the second is less obvious than it looks. Free text
* normalises into a well-formed VAT number more easily than one would think:
* "not a number" loses its spaces and becomes NOTANUMBER, which the shape rule
* alone reads as a perfectly good Norwegian number. VIES is the EU's register and
* holds nothing else, and reverse charge is an intra-EU rule in any case, so a
* prefix outside the union is answered here rather than spent on a request.
*/
final class VatIdShape
{
/** @return bool false when no member state could hold a number like this */
public static function verifiable(string $vatId): bool
{
$normalised = Customer::normaliseVatId($vatId);
if (preg_match('/^([A-Z]{2})([0-9A-Z]{8,12})$/', $normalised, $parts) !== 1) {
return false;
}
// Against TaxTreatment's list rather than a second copy of it: two lists
// of member states is how one of them ends up a year out of date.
return in_array($parts[1], TaxTreatment::EU_MEMBER_STATES, true);
}
/**
* The two halves VIES wants, or null when there is nothing to ask.
*
* @return array{0: string, 1: string}|null
*/
public static function split(string $vatId): ?array
{
$normalised = Customer::normaliseVatId($vatId);
if (! self::verifiable($normalised)) {
return null;
}
return [substr($normalised, 0, 2), substr($normalised, 2)];
}
}