Compare commits
7
Commits
28d91eb69b
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
065c60b2a3 | ||
|
|
6f7362c42a | ||
|
|
8b54b42991 | ||
|
|
fdc6c4eb56 | ||
|
|
89fdc24210 | ||
|
|
451817f2e1 | ||
|
|
4c181ea0a7 |
@@ -1 +1,2 @@
|
|||||||
vendor/
|
vendor/
|
||||||
|
*.txt
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
ws_vatcheck — planned improvements
|
|
||||||
adapted from a comparison against the "vatchecker" module (Inform-All & Keraweb), 2026-09-11
|
|
||||||
cache TTL (1 year) explicitly kept as-is, not on this list.
|
|
||||||
|
|
||||||
[ ] 1. Configurable VIES-offline fallback policy
|
|
||||||
- Add a setting (e.g. VATCHECK_OFFLINE_MODE) with explicit modes instead of an
|
|
||||||
implicit fail-closed default:
|
|
||||||
- always mark invalid (current implicit behavior)
|
|
||||||
- always mark valid
|
|
||||||
- reuse last cached result, fall back to invalid if none
|
|
||||||
- reuse last cached result, fall back to valid if none
|
|
||||||
- Directly related to the GR/EL bug fixed this session: the fallback behavior
|
|
||||||
should be an explicit, chosen policy, not an accidental side effect of
|
|
||||||
whether an exception happens to get caught.
|
|
||||||
|
|
||||||
[ ] 2. Log every VIES check failure
|
|
||||||
- Currently EuValidatorAdapter::checkAndStore() has no logging at all on
|
|
||||||
failure (this silence was a big part of why the GR/EL bug took so long to
|
|
||||||
diagnose). Add PrestaShopLogger::addLog(...) (or equivalent) whenever the
|
|
||||||
Europa/Validator call throws, including the VAT number, country code, and
|
|
||||||
the fault message.
|
|
||||||
|
|
||||||
[ ] 3. Company-name cross-check against VIES response
|
|
||||||
- When VIES returns a company name for a validated VAT number, optionally
|
|
||||||
compare it (normalized: lowercase, strip punctuation/legal-entity suffixes
|
|
||||||
like bv/gmbh/ltd/nv/sa) against the address's company field. Make this
|
|
||||||
togglable, since not all member states return a name.
|
|
||||||
|
|
||||||
[ ] 4. Configurable EU country allow-list
|
|
||||||
- Replace hardcoded VatNumber::$euIso-driven behavior with an admin-configurable
|
|
||||||
list of which EU countries are allowed to order VAT-free, similar to
|
|
||||||
vatchecker's VATCHECKER_EU_COUNTRIES setting.
|
|
||||||
|
|
||||||
[ ] 5. Per-product VAT exclusion
|
|
||||||
- Some products (e.g. certain digital services) may need to always charge VAT
|
|
||||||
regardless of customer VAT-exempt status. Add a product-level flag/table
|
|
||||||
(mirroring vatchecker_excluded_products) and consult it before applying the
|
|
||||||
0% tax manager for a given product line.
|
|
||||||
|
|
||||||
[ ] 6. GB -> XI (Northern Ireland) mapping
|
|
||||||
- VatNumber::$euIso has no entry at all for the UK/Northern Ireland. If this
|
|
||||||
shop (or any future ws_vatcheck deployment) ships to Northern Ireland with an
|
|
||||||
XI-prefixed VAT number, it will hit the same class of key/value bug already
|
|
||||||
fixed for Greece. Add the mapping and cover it with the same key-based checks.
|
|
||||||
|
|
||||||
[ ] 7. Front-end AJAX live VAT validation
|
|
||||||
- Add an AJAX front controller + JS so the address form validates the VAT
|
|
||||||
number as the customer types, instead of only on form submit
|
|
||||||
(hookActionValidateCustomerAddressForm). Mirrors vatchecker's ajax.php +
|
|
||||||
front.js/front.css pattern. Improves UX and surfaces bad VAT numbers earlier.
|
|
||||||
|
|
||||||
[ ] 8. Configurable address-priority strategy
|
|
||||||
- Add a setting for which address's VAT number is authoritative when invoice
|
|
||||||
and delivery addresses differ (shipping only / invoice only / prioritize
|
|
||||||
invoice / prioritize shipping), instead of relying on implicit PS tax-address
|
|
||||||
resolution. Mirrors vatchecker's VATCHECKER_ADDRESS_SELECT.
|
|
||||||
|
|
||||||
Explicitly deferred / not adopting:
|
|
||||||
- Shortening the 1-year cache TTL (Config::CACHE_VALIDITY) — decided to leave as-is.
|
|
||||||
- Class overrides (Carrier/Product) as used by vatchecker — do not port this pattern;
|
|
||||||
it conflicts with other modules that override the same core classes and is fragile
|
|
||||||
across PS core upgrades. If shipping/product tax rule assignment ever needs
|
|
||||||
behavior beyond what the taxManager hook covers, investigate a hook-based solution
|
|
||||||
first before reaching for an override.
|
|
||||||
- Legacy $this->l() / translations/{iso}.php system — ws_vatcheck already uses
|
|
||||||
$this->trans() correctly per house convention for PS >= 1.7.6.0.
|
|
||||||
@@ -7,6 +7,9 @@ use PH7\Eu\Vat\Provider\Europa;
|
|||||||
use PH7\Eu\Vat\Exception;
|
use PH7\Eu\Vat\Exception;
|
||||||
use Websmid\VatCheck\Config\Config;
|
use Websmid\VatCheck\Config\Config;
|
||||||
use Websmid\VatCheck\Model\VatCache;
|
use Websmid\VatCheck\Model\VatCache;
|
||||||
|
use Websmid\VatCheck\Model\CompanyNameMatcher;
|
||||||
|
use PrestaShopLogger;
|
||||||
|
use Configuration;
|
||||||
|
|
||||||
class EuValidatorAdapter
|
class EuValidatorAdapter
|
||||||
{
|
{
|
||||||
@@ -14,12 +17,15 @@ class EuValidatorAdapter
|
|||||||
|
|
||||||
protected $isoCode = '';
|
protected $isoCode = '';
|
||||||
|
|
||||||
|
protected $company = '';
|
||||||
|
|
||||||
private $checkStatus;
|
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->vatNr = str_replace($isoCode, '', $vatNr);
|
||||||
$this->isoCode = $isoCode;
|
$this->isoCode = $isoCode;
|
||||||
|
$this->company = $company;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkAndStore()
|
public function checkAndStore()
|
||||||
@@ -27,18 +33,98 @@ class EuValidatorAdapter
|
|||||||
try {
|
try {
|
||||||
$euVatValidator = new Validator(new Europa, $this->vatNr, $this->isoCode);
|
$euVatValidator = new Validator(new Europa, $this->vatNr, $this->isoCode);
|
||||||
} catch(Exception $e){
|
} catch(Exception $e){
|
||||||
return false;
|
PrestaShopLogger::addLog(
|
||||||
|
sprintf(
|
||||||
|
'ws_vatcheck: VIES check failed for VAT number %s%s: %s',
|
||||||
|
$this->isoCode,
|
||||||
|
$this->vatNr,
|
||||||
|
$e->getMessage()
|
||||||
|
),
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->resolveOfflineFallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
if( $euVatValidator instanceof Validator ){
|
if( $euVatValidator instanceof Validator ){
|
||||||
$fullVat = $this->isoCode.$this->vatNr;
|
$fullVat = $this->isoCode.$this->vatNr;
|
||||||
$vatCache = VatCache::getOrCreateByVat($fullVat);
|
$vatCache = VatCache::getOrCreateByVat($fullVat);
|
||||||
$vatCache->vat_nr = $fullVat;
|
$vatCache->vat_nr = $fullVat;
|
||||||
$vatCache->valid = $euVatValidator->check();
|
$vatCache->valid = $euVatValidator->check() && $this->isCompanyNameAcceptable($euVatValidator);
|
||||||
|
|
||||||
$vatCache->save();
|
$vatCache->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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
|
||||||
|
* CacheTaxManager to go ahead and read VatCache::checkVatIsValid() next, so
|
||||||
|
* the "reuse last known" modes just need to confirm a prior record exists
|
||||||
|
* (checkVatIsValid() will then return whatever it already holds); the
|
||||||
|
* "always valid" modes without a prior record need us to actually write one.
|
||||||
|
*/
|
||||||
|
private function resolveOfflineFallback(): bool
|
||||||
|
{
|
||||||
|
$mode = Configuration::get(Config::OFFLINE_MODE_CONFIG_KEY) ?: Config::OFFLINE_MODE_DEFAULT;
|
||||||
|
$fullVat = $this->isoCode.$this->vatNr;
|
||||||
|
|
||||||
|
switch ($mode) {
|
||||||
|
case Config::OFFLINE_MODE_VALID:
|
||||||
|
$this->storeOfflineResult($fullVat, true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Config::OFFLINE_MODE_LAST_KNOWN_OR_INVALID:
|
||||||
|
return VatCache::hasEverBeenChecked($fullVat);
|
||||||
|
|
||||||
|
case Config::OFFLINE_MODE_LAST_KNOWN_OR_VALID:
|
||||||
|
if (VatCache::hasEverBeenChecked($fullVat)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->storeOfflineResult($fullVat, true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Config::OFFLINE_MODE_INVALID:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function storeOfflineResult(string $fullVat, bool $valid): void
|
||||||
|
{
|
||||||
|
$vatCache = VatCache::getOrCreateByVat($fullVat);
|
||||||
|
$vatCache->vat_nr = $fullVat;
|
||||||
|
$vatCache->valid = $valid;
|
||||||
|
|
||||||
|
$vatCache->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,33 @@ class Config
|
|||||||
|
|
||||||
CONST CACHE_VALIDITY = 1;
|
CONST CACHE_VALIDITY = 1;
|
||||||
|
|
||||||
|
// Configuration key holding the chosen offline-fallback mode.
|
||||||
|
CONST OFFLINE_MODE_CONFIG_KEY = 'WS_VATCHECK_OFFLINE_MODE';
|
||||||
|
|
||||||
|
// What to decide when the VIES service can't be reached.
|
||||||
|
CONST OFFLINE_MODE_INVALID = 'invalid';
|
||||||
|
CONST OFFLINE_MODE_VALID = 'valid';
|
||||||
|
CONST OFFLINE_MODE_LAST_KNOWN_OR_INVALID = 'last_known_or_invalid';
|
||||||
|
CONST OFFLINE_MODE_LAST_KNOWN_OR_VALID = 'last_known_or_valid';
|
||||||
|
|
||||||
|
CONST OFFLINE_MODE_DEFAULT = self::OFFLINE_MODE_INVALID;
|
||||||
|
|
||||||
|
CONST OFFLINE_MODE_LABELS = [
|
||||||
|
self::OFFLINE_MODE_INVALID => 'Always mark VAT as invalid',
|
||||||
|
self::OFFLINE_MODE_VALID => 'Always mark VAT as valid',
|
||||||
|
self::OFFLINE_MODE_LAST_KNOWN_OR_INVALID => 'Reuse the last known result, otherwise mark invalid',
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Configuration key holding the admin-selected list of EU countries allowed
|
||||||
|
// to order VAT-free (JSON-encoded array of ISO codes). Absent/empty means
|
||||||
|
// "all countries VatNumber::$euIso supports" — see EuCountryAllowList.
|
||||||
|
CONST EU_COUNTRIES_CONFIG_KEY = 'WS_VATCHECK_EU_COUNTRIES';
|
||||||
|
|
||||||
// Article 39bis W.BTW intra-community exemption notice, per invoice language.
|
// Article 39bis W.BTW intra-community exemption notice, per invoice language.
|
||||||
CONST INTRACOMMUNITY_EXEMPTION_TEXTS = [
|
CONST INTRACOMMUNITY_EXEMPTION_TEXTS = [
|
||||||
'nl' => 'Vrijgestelde intracommunautaire levering – artikel 39bis W.BTW',
|
'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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Websmid\VatCheck\Model;
|
||||||
|
|
||||||
|
use Websmid\VatCheck\Config\Config;
|
||||||
|
use Configuration;
|
||||||
|
|
||||||
|
class EuCountryAllowList
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Every ISO code VatNumber knows a VAT prefix/format for — the maximum
|
||||||
|
* possible set an admin could enable.
|
||||||
|
*/
|
||||||
|
public static function getAllSupportedIsoCodes(): array
|
||||||
|
{
|
||||||
|
return array_keys(VatNumber::$euIso);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls back to "all supported countries" when nothing has been configured
|
||||||
|
* yet, so a fresh install/upgrade doesn't silently narrow existing behavior.
|
||||||
|
* Intersects against the supported set so a stale stored code can never
|
||||||
|
* make it back out.
|
||||||
|
*/
|
||||||
|
public static function getEnabledIsoCodes(): array
|
||||||
|
{
|
||||||
|
$stored = json_decode((string) Configuration::get(Config::EU_COUNTRIES_CONFIG_KEY), true);
|
||||||
|
|
||||||
|
if (empty($stored) || !is_array($stored)) {
|
||||||
|
return self::getAllSupportedIsoCodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_intersect($stored, self::getAllSupportedIsoCodes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isEnabled(string $isoCode): bool
|
||||||
|
{
|
||||||
|
return in_array($isoCode, self::getEnabledIsoCodes(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,16 @@ class VatCache extends ObjectModel
|
|||||||
return (bool)Db::getInstance()->getValue('SELECT `valid` FROM `'._DB_PREFIX_.'vat_cache` WHERE `vat_nr` = "'.pSQL($vatNr).'" ORDER BY `id_vat` DESC');
|
return (bool)Db::getInstance()->getValue('SELECT `valid` FROM `'._DB_PREFIX_.'vat_cache` WHERE `vat_nr` = "'.pSQL($vatNr).'" ORDER BY `id_vat` DESC');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlike checkVatIsCached(), this ignores Config::CACHE_VALIDITY entirely —
|
||||||
|
* used by the offline-fallback "reuse last known result" modes, which want
|
||||||
|
* whatever was last recorded regardless of how stale it is.
|
||||||
|
*/
|
||||||
|
public static function hasEverBeenChecked(string $vatNr)
|
||||||
|
{
|
||||||
|
return (bool)Db::getInstance()->getValue('SELECT `id_vat` FROM `'._DB_PREFIX_.'vat_cache` WHERE `vat_nr` = "'.pSQL($vatNr).'" ORDER BY `id_vat` DESC');
|
||||||
|
}
|
||||||
|
|
||||||
public static function getOrCreateByVat(string $vatNr)
|
public static function getOrCreateByVat(string $vatNr)
|
||||||
{
|
{
|
||||||
$className = get_called_class();
|
$className = get_called_class();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class VatNumber
|
|||||||
'FR' => 'FR',
|
'FR' => 'FR',
|
||||||
'FX' => 'FR',
|
'FX' => 'FR',
|
||||||
'DE' => 'DE',
|
'DE' => 'DE',
|
||||||
|
'GB' => 'XI',
|
||||||
'GR' => 'EL',
|
'GR' => 'EL',
|
||||||
'IE' => 'IE',
|
'IE' => 'IE',
|
||||||
'IT' => 'IT',
|
'IT' => 'IT',
|
||||||
@@ -53,6 +54,7 @@ class VatNumber
|
|||||||
'DE' => '\d{9}',
|
'DE' => '\d{9}',
|
||||||
'DK' => '(\d{2} ?){3}\d{2}',
|
'DK' => '(\d{2} ?){3}\d{2}',
|
||||||
'EE' => '\d{9}',
|
'EE' => '\d{9}',
|
||||||
|
'GB' => '\d{9}(\d{3})?|[A-Z]{2}\d{3}',
|
||||||
'GR' => '\d{9}',
|
'GR' => '\d{9}',
|
||||||
'ES' => '[A-Z]\d{7}[A-Z]|\d{8}[A-Z]|[A-Z]\d{8}',
|
'ES' => '[A-Z]\d{7}[A-Z]|\d{8}[A-Z]|[A-Z]\d{8}',
|
||||||
'FI' => '\d{8}',
|
'FI' => '\d{8}',
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Websmid\VatCheck\Module\Admin;
|
||||||
|
|
||||||
|
use Websmid\VatCheck\Config\Config;
|
||||||
|
use Websmid\VatCheck\Model\EuCountryAllowList;
|
||||||
|
use Configuration;
|
||||||
|
use Tools;
|
||||||
|
use HelperForm;
|
||||||
|
use Context;
|
||||||
|
use Country;
|
||||||
|
|
||||||
|
trait SettingsForm
|
||||||
|
{
|
||||||
|
public function getContent()
|
||||||
|
{
|
||||||
|
if ((bool) Tools::isSubmit('submitWsVatCheckSettings')) {
|
||||||
|
$this->postProcessSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->renderSettingsForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function postProcessSettings()
|
||||||
|
{
|
||||||
|
$mode = Tools::getValue(Config::OFFLINE_MODE_CONFIG_KEY);
|
||||||
|
|
||||||
|
if (array_key_exists($mode, Config::OFFLINE_MODE_LABELS)) {
|
||||||
|
Configuration::updateValue(Config::OFFLINE_MODE_CONFIG_KEY, $mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration::updateValue(
|
||||||
|
Config::VALIDATE_COMPANY_CONFIG_KEY,
|
||||||
|
(bool) Tools::getValue(Config::VALIDATE_COMPANY_CONFIG_KEY)
|
||||||
|
);
|
||||||
|
|
||||||
|
$enabledIsoCodes = [];
|
||||||
|
foreach (EuCountryAllowList::getAllSupportedIsoCodes() as $isoCode) {
|
||||||
|
if (Tools::getValue(Config::EU_COUNTRIES_CONFIG_KEY . '_' . $isoCode)) {
|
||||||
|
$enabledIsoCodes[] = $isoCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration::updateValue(Config::EU_COUNTRIES_CONFIG_KEY, json_encode($enabledIsoCodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderSettingsForm()
|
||||||
|
{
|
||||||
|
$helper = new HelperForm();
|
||||||
|
|
||||||
|
$helper->show_toolbar = false;
|
||||||
|
$helper->table = $this->table;
|
||||||
|
$helper->module = $this;
|
||||||
|
$helper->default_form_language = Context::getContext()->language->id;
|
||||||
|
$helper->allow_employee_form_lang = (int) Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG', 0);
|
||||||
|
|
||||||
|
$helper->identifier = $this->identifier;
|
||||||
|
$helper->submit_action = 'submitWsVatCheckSettings';
|
||||||
|
$helper->currentIndex = Context::getContext()->link->getAdminLink('AdminModules', false)
|
||||||
|
. '&configure=' . $this->name
|
||||||
|
. '&tab_module=' . $this->tab
|
||||||
|
. '&module_name=' . $this->name;
|
||||||
|
$helper->token = Tools::getAdminTokenLite('AdminModules');
|
||||||
|
|
||||||
|
$helper->tpl_vars = [
|
||||||
|
'fields_value' => $this->getSettingsFormValues(),
|
||||||
|
'languages' => Context::getContext()->controller->getLanguages(),
|
||||||
|
'id_language' => Context::getContext()->language->id,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $helper->generateForm([$this->getSettingsFormStructure()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSettingsFormValues()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Config::OFFLINE_MODE_CONFIG_KEY => Configuration::get(
|
||||||
|
Config::OFFLINE_MODE_CONFIG_KEY,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
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
|
||||||
|
),
|
||||||
|
] + $this->getEnabledCountriesFormValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getEnabledCountriesFormValues()
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
foreach (EuCountryAllowList::getEnabledIsoCodes() as $isoCode) {
|
||||||
|
$values[Config::EU_COUNTRIES_CONFIG_KEY . '_' . $isoCode] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCountryChoices()
|
||||||
|
{
|
||||||
|
$supported = EuCountryAllowList::getAllSupportedIsoCodes();
|
||||||
|
$choices = [];
|
||||||
|
|
||||||
|
foreach (Country::getCountries(Context::getContext()->language->id) as $country) {
|
||||||
|
if (in_array($country['iso_code'], $supported, true)) {
|
||||||
|
$choices[] = [
|
||||||
|
'id' => $country['iso_code'],
|
||||||
|
'name' => $country['name'] . ' (' . $country['iso_code'] . ')',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSettingsFormStructure()
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
foreach (Config::OFFLINE_MODE_LABELS as $value => $label) {
|
||||||
|
$choices[] = [
|
||||||
|
'id' => $value,
|
||||||
|
'value' => $value,
|
||||||
|
'label' => $this->trans($label, [], 'Modules.Ws_VatCheck.Admin'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'form' => [
|
||||||
|
'legend' => [
|
||||||
|
'title' => $this->trans('VAT Check settings', [], 'Modules.Ws_VatCheck.Admin'),
|
||||||
|
'icon' => 'icon-cogs',
|
||||||
|
],
|
||||||
|
'input' => [
|
||||||
|
[
|
||||||
|
'type' => 'radio',
|
||||||
|
'label' => $this->trans('When VIES is unreachable', [], 'Modules.Ws_VatCheck.Admin'),
|
||||||
|
'name' => Config::OFFLINE_MODE_CONFIG_KEY,
|
||||||
|
'desc' => $this->trans(
|
||||||
|
'What to decide about a VAT number when the EU VIES service can\'t be reached to check it.',
|
||||||
|
[],
|
||||||
|
'Modules.Ws_VatCheck.Admin'
|
||||||
|
),
|
||||||
|
'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'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => $this->trans('Enabled EU countries', [], 'Modules.Ws_VatCheck.Admin'),
|
||||||
|
'name' => Config::EU_COUNTRIES_CONFIG_KEY,
|
||||||
|
'multiple' => true,
|
||||||
|
'desc' => $this->trans(
|
||||||
|
'Countries a customer can order VAT-free from with a valid VAT number.',
|
||||||
|
[],
|
||||||
|
'Modules.Ws_VatCheck.Admin'
|
||||||
|
),
|
||||||
|
'values' => [
|
||||||
|
'query' => $this->getCountryChoices(),
|
||||||
|
'id' => 'id',
|
||||||
|
'name' => 'name',
|
||||||
|
'value' => 'id',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'submit' => [
|
||||||
|
'title' => $this->trans('Save', [], 'Admin.Actions'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace Websmid\VatCheck\Module\Front;
|
|||||||
|
|
||||||
use Websmid\VatCheck\TaxManager\CacheTaxManager;
|
use Websmid\VatCheck\TaxManager\CacheTaxManager;
|
||||||
use Websmid\VatCheck\Model\VatNumber;
|
use Websmid\VatCheck\Model\VatNumber;
|
||||||
|
use Websmid\VatCheck\Model\EuCountryAllowList;
|
||||||
use Websmid\VatCheck\Config\Config;
|
use Websmid\VatCheck\Config\Config;
|
||||||
use Address;
|
use Address;
|
||||||
use Configuration;
|
use Configuration;
|
||||||
@@ -27,7 +28,7 @@ trait FrontHooks
|
|||||||
$countryIso = (string)Country::getIsoById((string)$form->getValue('id_country'));
|
$countryIso = (string)Country::getIsoById((string)$form->getValue('id_country'));
|
||||||
$vatNumber = new VatNumber($vatNumber, $countryIso);
|
$vatNumber = new VatNumber($vatNumber, $countryIso);
|
||||||
|
|
||||||
if( $vatNumber->isNotEuIso() ){
|
if( $vatNumber->isNotEuIso() || !EuCountryAllowList::isEnabled($countryIso) ){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
namespace Websmid\VatCheck\Module;
|
namespace Websmid\VatCheck\Module;
|
||||||
|
|
||||||
use Websmid\VatCheck\Config\Config;
|
use Websmid\VatCheck\Config\Config;
|
||||||
|
use Websmid\VatCheck\Model\EuCountryAllowList;
|
||||||
use Db;
|
use Db;
|
||||||
|
use Configuration;
|
||||||
|
|
||||||
trait Install
|
trait Install
|
||||||
{
|
{
|
||||||
@@ -21,6 +23,30 @@ trait Install
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$this->registerConfiguration()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registerConfiguration()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Configuration::updateValue(
|
||||||
|
Config::EU_COUNTRIES_CONFIG_KEY,
|
||||||
|
json_encode(EuCountryAllowList::getAllSupportedIsoCodes())
|
||||||
|
)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use Configuration;
|
|||||||
use Context;
|
use Context;
|
||||||
use Websmid\VatCheck\Model\VatCache;
|
use Websmid\VatCheck\Model\VatCache;
|
||||||
use Websmid\VatCheck\Model\VatNumber;
|
use Websmid\VatCheck\Model\VatNumber;
|
||||||
|
use Websmid\VatCheck\Model\EuCountryAllowList;
|
||||||
use Websmid\VatCheck\Adapter\EuValidatorAdapter;
|
use Websmid\VatCheck\Adapter\EuValidatorAdapter;
|
||||||
|
|
||||||
class CacheTaxManager implements TaxManagerInterface
|
class CacheTaxManager implements TaxManagerInterface
|
||||||
@@ -27,7 +28,7 @@ class CacheTaxManager implements TaxManagerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$isoCode = Country::getIsoById((int)$address->id_country);
|
$isoCode = Country::getIsoById((int)$address->id_country);
|
||||||
if( !array_key_exists($isoCode, VatNumber::$euIso) ){
|
if( !EuCountryAllowList::isEnabled($isoCode) ){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ class CacheTaxManager implements TaxManagerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$vatPrefix = VatNumber::$euIso[$isoCode];
|
$vatPrefix = VatNumber::$euIso[$isoCode];
|
||||||
$euValidator = new EuValidatorAdapter($address->vat_number, $vatPrefix);
|
$euValidator = new EuValidatorAdapter($address->vat_number, $vatPrefix, (string) $address->company);
|
||||||
if( !$euValidator->checkAndStore() ){
|
if( !$euValidator->checkAndStore() ){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Websmid\VatCheck\Config\Config;
|
||||||
|
use Websmid\VatCheck\Model\EuCountryAllowList;
|
||||||
|
|
||||||
|
if (!defined('_PS_VERSION_')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function upgrade_module_1_0_3($module)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All countries enabled by default, so upgrading doesn't silently narrow
|
||||||
|
// exemption eligibility for a shop already relying on the old behavior.
|
||||||
|
return Configuration::updateValue(
|
||||||
|
Config::EU_COUNTRIES_CONFIG_KEY,
|
||||||
|
json_encode(EuCountryAllowList::getAllSupportedIsoCodes())
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
use Websmid\VatCheck\Module\Install;
|
use Websmid\VatCheck\Module\Install;
|
||||||
use Websmid\VatCheck\Module\Front\FrontHooks;
|
use Websmid\VatCheck\Module\Front\FrontHooks;
|
||||||
use Websmid\VatCheck\Module\Admin\AdminHooks;
|
use Websmid\VatCheck\Module\Admin\AdminHooks;
|
||||||
|
use Websmid\VatCheck\Module\Admin\SettingsForm;
|
||||||
|
|
||||||
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
|
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
@@ -35,6 +36,7 @@ class Ws_VatCheck extends \Module
|
|||||||
{
|
{
|
||||||
use Install;
|
use Install;
|
||||||
use AdminHooks;
|
use AdminHooks;
|
||||||
|
use SettingsForm;
|
||||||
use FrontHooks;
|
use FrontHooks;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|||||||
Reference in New Issue
Block a user