85 lines
2.9 KiB
PHP
85 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace App\Actions;
|
|
|
|
use App\Models\Customer;
|
|
use App\Services\Tax\VatIdCheck;
|
|
use App\Services\Tax\VatIdVerifier;
|
|
|
|
/**
|
|
* Ask the register about a customer's VAT number and record what it said.
|
|
*
|
|
* The one place a check becomes a stored verification, so the portal form, the
|
|
* console and the nightly re-check cannot disagree about what counts. It decides
|
|
* nothing about tax: TaxTreatment reads the stored verification and applies the
|
|
* reverse-charge rule. This only answers "is the number real".
|
|
*
|
|
* Deliberately not gated on the customer's recorded type. Whether a verified
|
|
* number earns reverse charge is TaxTreatment's question — it already refuses a
|
|
* customer who has recorded themselves as a private person — and a number that
|
|
* is registered is registered whoever holds it. Splitting that decision across
|
|
* two files is how the two would drift.
|
|
*/
|
|
class VerifyCustomerVatId
|
|
{
|
|
public function __construct(private VatIdVerifier $verifier) {}
|
|
|
|
/**
|
|
* Returns what the register said, so the caller can tell the customer.
|
|
*
|
|
* Null when there is no number to check at all — not an outcome, an absence,
|
|
* and a caller showing "invalid" for an empty field would be wrong.
|
|
*/
|
|
public function __invoke(Customer $customer): ?VatIdCheck
|
|
{
|
|
$vatId = $customer->normalisedVatId();
|
|
|
|
if ($vatId === '') {
|
|
// Removing the number removes the verification with it. Otherwise the
|
|
// stamp would sit on the record with nothing to compare against, and
|
|
// the next number typed in would look pre-verified for one save.
|
|
$this->clear($customer);
|
|
|
|
return null;
|
|
}
|
|
|
|
$check = $this->verifier->check($vatId);
|
|
|
|
// The whole reason VatIdCheck has three outcomes. An outage tells us
|
|
// nothing, so nothing is written: a verification that has been standing
|
|
// for months must not be cleared — and a customer's invoices changed —
|
|
// because a national register was down for the night.
|
|
if ($check->isUnavailable()) {
|
|
return $check;
|
|
}
|
|
|
|
if ($check->isInvalid()) {
|
|
$this->clear($customer);
|
|
|
|
return $check;
|
|
}
|
|
|
|
// Bound to the value that was checked, not to a flag. Customer::
|
|
// hasVerifiedVatId() compares the two, so editing the number afterwards
|
|
// cannot inherit this confirmation whichever code path does the editing.
|
|
$customer->forceFill([
|
|
'vat_id_verified_at' => now(),
|
|
'vat_id_verified_value' => $vatId,
|
|
])->save();
|
|
|
|
return $check;
|
|
}
|
|
|
|
private function clear(Customer $customer): void
|
|
{
|
|
if ($customer->vat_id_verified_at === null && $customer->vat_id_verified_value === null) {
|
|
return;
|
|
}
|
|
|
|
$customer->forceFill([
|
|
'vat_id_verified_at' => null,
|
|
'vat_id_verified_value' => null,
|
|
])->save();
|
|
}
|
|
}
|