Add company-name cross-check against VIES response
This commit is contained in:
@@ -7,6 +7,7 @@ use PH7\Eu\Vat\Provider\Europa;
|
||||
use PH7\Eu\Vat\Exception;
|
||||
use Websmid\VatCheck\Config\Config;
|
||||
use Websmid\VatCheck\Model\VatCache;
|
||||
use Websmid\VatCheck\Model\CompanyNameMatcher;
|
||||
use PrestaShopLogger;
|
||||
use Configuration;
|
||||
|
||||
@@ -16,12 +17,15 @@ class EuValidatorAdapter
|
||||
|
||||
protected $isoCode = '';
|
||||
|
||||
protected $company = '';
|
||||
|
||||
private $checkStatus;
|
||||
|
||||
public function __construct(string $vatNr, string $isoCode)
|
||||
public function __construct(string $vatNr, string $isoCode, string $company = '')
|
||||
{
|
||||
$this->vatNr = str_replace($isoCode, '', $vatNr);
|
||||
$this->isoCode = $isoCode;
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
public function checkAndStore()
|
||||
@@ -46,7 +50,7 @@ class EuValidatorAdapter
|
||||
$fullVat = $this->isoCode.$this->vatNr;
|
||||
$vatCache = VatCache::getOrCreateByVat($fullVat);
|
||||
$vatCache->vat_nr = $fullVat;
|
||||
$vatCache->valid = $euVatValidator->check();
|
||||
$vatCache->valid = $euVatValidator->check() && $this->isCompanyNameAcceptable($euVatValidator);
|
||||
|
||||
$vatCache->save();
|
||||
}
|
||||
@@ -54,6 +58,30 @@ class EuValidatorAdapter
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only rejects on a confirmed mismatch. Skips the check entirely when it's
|
||||
* disabled, when the address has no company on file, or when VIES didn't
|
||||
* return a usable name for this member state (not every country discloses
|
||||
* one) — there's nothing meaningful to compare against in those cases.
|
||||
*/
|
||||
private function isCompanyNameAcceptable(Validator $validator): bool
|
||||
{
|
||||
if (!Configuration::get(Config::VALIDATE_COMPANY_CONFIG_KEY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('' === trim($this->company)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$viesName = $validator->getName();
|
||||
if (!CompanyNameMatcher::hasUsableName($viesName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CompanyNameMatcher::matches($this->company, $viesName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide what to do when VIES itself can't be reached, per the configured
|
||||
* Config::OFFLINE_MODE_CONFIG_KEY policy. Returning true here tells
|
||||
|
||||
@@ -32,6 +32,10 @@ class Config
|
||||
self::OFFLINE_MODE_LAST_KNOWN_OR_VALID => 'Reuse the last known result, otherwise mark valid',
|
||||
];
|
||||
|
||||
// Configuration key toggling the company-name cross-check against VIES.
|
||||
CONST VALIDATE_COMPANY_CONFIG_KEY = 'WS_VATCHECK_VALIDATE_COMPANY';
|
||||
CONST VALIDATE_COMPANY_DEFAULT = false;
|
||||
|
||||
// Article 39bis W.BTW intra-community exemption notice, per invoice language.
|
||||
CONST INTRACOMMUNITY_EXEMPTION_TEXTS = [
|
||||
'nl' => 'Vrijgestelde intracommunautaire levering – artikel 39bis W.BTW',
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Websmid\VatCheck\Model;
|
||||
|
||||
class CompanyNameMatcher
|
||||
{
|
||||
// Common legal-entity suffixes to ignore across the EU/UK/US so e.g.
|
||||
// "Acme BV" matches VIES's "ACME B.V." or "Acme".
|
||||
private const LEGAL_SUFFIXES = [
|
||||
'inc', 'bv', 'gmbh', 'ltd', 'llc', 'corp', 'nv', 'sa', 'ag', 'oy', 'as', 'spa', 'srl',
|
||||
];
|
||||
|
||||
/**
|
||||
* VIES sometimes returns a placeholder like "---" instead of a real name
|
||||
* for member states that don't disclose company details. There is nothing
|
||||
* to compare against in that case.
|
||||
*/
|
||||
public static function hasUsableName(string $viesName): bool
|
||||
{
|
||||
return '' !== preg_replace('/[^a-zA-Z0-9]+/', '', $viesName);
|
||||
}
|
||||
|
||||
public static function matches(string $addressCompany, string $viesName): bool
|
||||
{
|
||||
return self::normalize($addressCompany) === self::normalize($viesName);
|
||||
}
|
||||
|
||||
private static function normalize(string $name): string
|
||||
{
|
||||
$name = strtolower($name);
|
||||
$name = preg_replace('/[.,\/#!$%\^&*;:{}=\-_`~()]/', '', $name);
|
||||
|
||||
foreach (self::LEGAL_SUFFIXES as $suffix) {
|
||||
$name = preg_replace('/\b'.$suffix.'\b/', '', $name);
|
||||
}
|
||||
|
||||
return trim(preg_replace('/\s+/', ' ', $name));
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,14 @@ trait SettingsForm
|
||||
{
|
||||
$mode = Tools::getValue(Config::OFFLINE_MODE_CONFIG_KEY);
|
||||
|
||||
if (!array_key_exists($mode, Config::OFFLINE_MODE_LABELS)) {
|
||||
return;
|
||||
if (array_key_exists($mode, Config::OFFLINE_MODE_LABELS)) {
|
||||
Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, $mode);
|
||||
}
|
||||
|
||||
Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, $mode);
|
||||
Configuration::updateValue(
|
||||
Config::VALIDATE_COMPANY_CONFIG_KEY,
|
||||
(bool) Tools::getValue(Config::VALIDATE_COMPANY_CONFIG_KEY)
|
||||
);
|
||||
}
|
||||
|
||||
private function renderSettingsForm()
|
||||
@@ -67,6 +70,13 @@ trait SettingsForm
|
||||
null,
|
||||
Config::OFFLINE_MODE_DEFAULT
|
||||
),
|
||||
Config::VALIDATE_COMPANY_CONFIG_KEY => (bool) Configuration::get(
|
||||
Config::VALIDATE_COMPANY_CONFIG_KEY,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Config::VALIDATE_COMPANY_DEFAULT
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -99,6 +109,29 @@ trait SettingsForm
|
||||
),
|
||||
'values' => $choices,
|
||||
],
|
||||
[
|
||||
'type' => 'switch',
|
||||
'label' => $this->trans('Also check the company name', [], 'Modules.Ws_VatCheck.Admin'),
|
||||
'name' => Config::VALIDATE_COMPANY_CONFIG_KEY,
|
||||
'is_bool' => true,
|
||||
'desc' => $this->trans(
|
||||
'Compare the address\'s company name against the name VIES returns for the VAT number, when available. Not every member state discloses one.',
|
||||
[],
|
||||
'Modules.Ws_VatCheck.Admin'
|
||||
),
|
||||
'values' => [
|
||||
[
|
||||
'id' => 'validate_company_on',
|
||||
'value' => true,
|
||||
'label' => $this->trans('Enabled', [], 'Admin.Global'),
|
||||
],
|
||||
[
|
||||
'id' => 'validate_company_off',
|
||||
'value' => false,
|
||||
'label' => $this->trans('Disabled', [], 'Admin.Global'),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'submit' => [
|
||||
'title' => $this->trans('Save', [], 'Admin.Actions'),
|
||||
|
||||
@@ -31,7 +31,15 @@ trait Install
|
||||
|
||||
private function registerConfiguration()
|
||||
{
|
||||
return Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, Config::OFFLINE_MODE_DEFAULT);
|
||||
if (!Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, Config::OFFLINE_MODE_DEFAULT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Configuration::updateValue(Config::VALIDATE_COMPANY_CONFIG_KEY, Config::VALIDATE_COMPANY_DEFAULT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function registerHooks()
|
||||
|
||||
@@ -36,7 +36,7 @@ class CacheTaxManager implements TaxManagerInterface
|
||||
}
|
||||
|
||||
$vatPrefix = VatNumber::$euIso[$isoCode];
|
||||
$euValidator = new EuValidatorAdapter($address->vat_number, $vatPrefix);
|
||||
$euValidator = new EuValidatorAdapter($address->vat_number, $vatPrefix, (string) $address->company);
|
||||
if( !$euValidator->checkAndStore() ){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,5 +8,9 @@ if (!defined('_PS_VERSION_')) {
|
||||
|
||||
function upgrade_module_1_0_3($module)
|
||||
{
|
||||
return Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, Config::OFFLINE_MODE_DEFAULT);
|
||||
if (!Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, Config::OFFLINE_MODE_DEFAULT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Configuration::updateValue(Config::VALIDATE_COMPANY_CONFIG_KEY, Config::VALIDATE_COMPANY_DEFAULT);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user