CluPilotCloud/app/Actions/VerifyCustomerVatId.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();
}
}