diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..48b8bf9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+vendor/
diff --git a/IMPROVEMENTS.txt b/IMPROVEMENTS.txt
new file mode 100644
index 0000000..d6bae6c
--- /dev/null
+++ b/IMPROVEMENTS.txt
@@ -0,0 +1,66 @@
+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.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..b2c2a48
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "websmid/vatcheck",
+ "description": "Maakt het vrijstellen van BTW mogelijk via een gecachte vat check",
+ "authors": [
+ {
+ "name": "Johan van der Klis - De Websmid BV",
+ "email": "johan@dewebsmid.nl"
+ }
+ ],
+ "require": {
+ "ph-7/eu-vat-validator": "3.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Websmid\\VatCheck\\": "src/"
+ },
+ "classmap": [
+ "ws_vatcheck.php"
+ ],
+ "exclude-from-classmap": []
+ },
+ "config": {
+ "preferred-install": "dist",
+ "prepend-autoloader": false
+ },
+ "type": "prestashop-module",
+ "author": "Johan van der Klis - De Websmid BV",
+ "license": "MIT"
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..4d1805e
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,91 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "ccb92e4629eb4c104b85c939daa54a7c",
+ "packages": [
+ {
+ "name": "ph-7/eu-vat-validator",
+ "version": "3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pH-7/eu-vat-validator.git",
+ "reference": "03dc5305984ad71a71c363ab4cb1f691a5d9408b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pH-7/eu-vat-validator/zipball/03dc5305984ad71a71c363ab4cb1f691a5d9408b",
+ "reference": "03dc5305984ad71a71c363ab4cb1f691a5d9408b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-soap": "*",
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phake/phake": "^3.1",
+ "phpunit/phpunit": "^7.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PH7\\Eu\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Pierre-Henry Soria",
+ "email": "hi@ph7.me",
+ "homepage": "https://github.com/pH-7/eu-vat-validator",
+ "role": "Software Engineer"
+ }
+ ],
+ "description": "A simple and clean PHP class that validates EU VAT numbers against the central ec.europa.eu database (using the official europa API).",
+ "homepage": "https://pierrehenry.be",
+ "keywords": [
+ "TVA",
+ "eu vat",
+ "tva number",
+ "validate eu vat",
+ "validate vat number",
+ "validation",
+ "validator",
+ "vat",
+ "vat number",
+ "vat number validator",
+ "vat validator",
+ "vies"
+ ],
+ "support": {
+ "issues": "https://github.com/pH-7/eu-vat-validator/issues",
+ "source": "https://github.com/pH-7/eu-vat-validator/tree/3.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/pH-7",
+ "type": "github"
+ },
+ {
+ "url": "https://ko-fi.com/phenry",
+ "type": "ko_fi"
+ }
+ ],
+ "time": "2023-01-04T11:50:01+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.3.0"
+}
diff --git a/config.xml b/config.xml
new file mode 100644
index 0000000..dc19e99
--- /dev/null
+++ b/config.xml
@@ -0,0 +1,11 @@
+
+
+ ws_vatcheck
+
+
+
+
+
+ 0
+ 0
+
\ No newline at end of file
diff --git a/config/services.yml b/config/services.yml
new file mode 100644
index 0000000..6b21f91
--- /dev/null
+++ b/config/services.yml
@@ -0,0 +1,8 @@
+services:
+ Websmid\VatCheck\Command\CleanCommand:
+ class: Websmid\VatCheck\Command\CleanCommand
+ tags: ['console.command']
+
+ Websmid\VatCheck\Command\BulkCheckCommand:
+ class: Websmid\VatCheck\Command\BulkCheckCommand
+ tags: ['console.command']
\ No newline at end of file
diff --git a/config_nl.xml b/config_nl.xml
new file mode 100644
index 0000000..dc19e99
--- /dev/null
+++ b/config_nl.xml
@@ -0,0 +1,11 @@
+
+
+ ws_vatcheck
+
+
+
+
+
+ 0
+ 0
+
\ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..dedd77e
--- /dev/null
+++ b/index.php
@@ -0,0 +1,35 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+
+header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
+
+header("Cache-Control: no-store, no-cache, must-revalidate");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+
+header("Location: ../");
+exit;
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..d0df3a7
Binary files /dev/null and b/logo.png differ
diff --git a/src/Adapter/EuValidatorAdapter.php b/src/Adapter/EuValidatorAdapter.php
new file mode 100644
index 0000000..e527e48
--- /dev/null
+++ b/src/Adapter/EuValidatorAdapter.php
@@ -0,0 +1,44 @@
+vatNr = str_replace($isoCode, '', $vatNr);
+ $this->isoCode = $isoCode;
+ }
+
+ public function checkAndStore()
+ {
+ try {
+ $euVatValidator = new Validator(new Europa, $this->vatNr, $this->isoCode);
+ } catch(Exception $e){
+ return false;
+ }
+
+ if( $euVatValidator instanceof Validator ){
+ $fullVat = $this->isoCode.$this->vatNr;
+ $vatCache = VatCache::getOrCreateByVat($fullVat);
+ $vatCache->vat_nr = $fullVat;
+ $vatCache->valid = $euVatValidator->check();
+
+ $vatCache->save();
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php
new file mode 100644
index 0000000..3b78fdb
--- /dev/null
+++ b/src/Command/BaseCommand.php
@@ -0,0 +1,47 @@
+logger)) {
+ $this->logger->log($message, $object, $id);
+ } else {
+ $now = (new \DateTime())->format('Y-m-d H:i:s');
+ $line = $now . ' --> ' . $message . PHP_EOL;
+ $this->logs[] = $line;
+ $this->output->write($line);
+ }
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->output = $output;
+ $this->input = $input;
+
+ set_time_limit(0);
+ ini_set('memory_limit', '-1');
+
+ $this->executeCommand();
+ }
+
+ public abstract function executeCommand();
+}
\ No newline at end of file
diff --git a/src/Command/BulkCheckCommand.php b/src/Command/BulkCheckCommand.php
new file mode 100644
index 0000000..833044d
--- /dev/null
+++ b/src/Command/BulkCheckCommand.php
@@ -0,0 +1,62 @@
+setName('vatcheck:bulkcheck');
+ }
+
+ public function executeCommand()
+ {
+ $this->log('Start process');
+ $this->logger = new LoggingService();
+ $this->logger->setConsole($this->output);
+ //$this->logger->enablePrestaLog();
+
+ $vatInfosToCheck = Db::getInstance()->executeS('
+ SELECT
+ `vat_number`, `id_country`
+ FROM
+ `'._DB_PREFIX_.'address`
+ WHERE
+ `vat_number` NOT IN(
+ SELECT
+ `vat_nr`
+ FROM
+ `'._DB_PREFIX_.'vat_cache`
+ ) AND `vat_number` != ""
+ ');
+
+ foreach($vatInfosToCheck as $vatInfo){
+
+ $isoCode = Country::getIsoById((int)$vatInfo['id_country']);
+ $vatNumber = new VatNumber($vatInfo['vat_number'], $isoCode);
+
+ if( $vatNumber->hasErrors() || $vatNumber->isNotEuIso() ){
+ continue;
+ }
+
+ $euValidator = new EuValidatorAdapter($vatNumber->getFormattedVat(), $isoCode);
+ $euValidator->checkAndStore();
+
+ $this->log('Iteration end, now ging to sleep 3s');
+ sleep(3);
+ }
+
+ return $this->log('Stored VATS checked!');
+ }
+}
\ No newline at end of file
diff --git a/src/Command/CleanCommand.php b/src/Command/CleanCommand.php
new file mode 100644
index 0000000..a866f3e
--- /dev/null
+++ b/src/Command/CleanCommand.php
@@ -0,0 +1,31 @@
+setName('vatcheck:cleanrecords');
+ }
+
+ public function executeCommand()
+ {
+ $this->log('Start process');
+ $this->logger = new LoggingService();
+ $this->logger->setConsole($this->output);
+ //$this->logger->enablePrestaLog();
+
+ VatCache::cleanOldRecords();
+
+ return $this->log('Records cleaned!');
+ }
+}
\ No newline at end of file
diff --git a/src/Config/Config.php b/src/Config/Config.php
new file mode 100644
index 0000000..3ae3daf
--- /dev/null
+++ b/src/Config/Config.php
@@ -0,0 +1,24 @@
+ 'Vrijgestelde intracommunautaire levering – artikel 39bis W.BTW',
+ 'fr' => 'Livraison intracommunautaire exonérée de TVA – article 39bis du Code TVA',
+ 'en' => 'Intra-Community supply exempt from VAT according to Article 39bis of the Belgian VAT Code.',
+ 'de' => 'Von der Mehrwertsteuer befreite innergemeinschaftliche Lieferung – Artikel 39bis des belgischen Mehrwertsteuergesetzbuches',
+ ];
+}
\ No newline at end of file
diff --git a/src/Console/WebInput.php b/src/Console/WebInput.php
new file mode 100644
index 0000000..6c9a8ec
--- /dev/null
+++ b/src/Console/WebInput.php
@@ -0,0 +1,89 @@
+cleaned) {
+ //ob_end_flush();
+ $this->cleaned = true;
+ }
+ echo $messages . '
';
+ $this->flush_buffers();
+ }
+
+ public function flush_buffers()
+ {
+ //$result = ob_get_contents();
+ //echo $result;
+ flush();
+ }
+
+ public function writeln($messages, $options = 0)
+ {
+ // TODO: Implement writeln() method.
+ }
+
+ public function setVerbosity($level)
+ {
+ // TODO: Implement setVerbosity() method.
+ }
+
+ public function getVerbosity()
+ {
+ // TODO: Implement getVerbosity() method.
+ }
+
+ public function isQuiet()
+ {
+ // TODO: Implement isQuiet() method.
+ }
+
+ public function isVerbose()
+ {
+ // TODO: Implement isVerbose() method.
+ }
+
+ public function isVeryVerbose()
+ {
+ // TODO: Implement isVeryVerbose() method.
+ }
+
+ public function isDebug()
+ {
+ // TODO: Implement isDebug() method.
+ }
+
+ public function setDecorated($decorated)
+ {
+ // TODO: Implement setDecorated() method.
+ }
+
+ public function isDecorated()
+ {
+ // TODO: Implement isDecorated() method.
+ }
+
+ public function setFormatter(OutputFormatterInterface $formatter)
+ {
+ // TODO: Implement setFormatter() method.
+ }
+
+ public function getFormatter()
+ {
+ // TODO: Implement getFormatter() method.
+ }
+}
\ No newline at end of file
diff --git a/src/Model/VatCache.php b/src/Model/VatCache.php
new file mode 100644
index 0000000..252f189
--- /dev/null
+++ b/src/Model/VatCache.php
@@ -0,0 +1,60 @@
+ 'vat_cache',
+ 'primary' => 'id_vat',
+ 'fields' => [
+ 'vat_nr' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true],
+ 'valid' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true],
+ 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
+ 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
+ ],
+ ];
+
+ public static function getIdByVat(string $vatNr)
+ {
+ return Db::getInstance()->getValue('SELECT `id_vat` FROM `'._DB_PREFIX_.'vat_cache` WHERE `vat_nr` = "'.pSQL($vatNr).'" AND `date_add` > DATE_SUB(NOW(), INTERVAL '.(int)Config::CACHE_VALIDITY.' YEAR)');
+ }
+
+ public static function checkVatIsCached(string $vatNr)
+ {
+ return (bool)self::getIdByVat($vatNr);
+ }
+
+ public static function checkVatIsValid(string $vatNr)
+ {
+ return (bool)Db::getInstance()->getValue('SELECT `valid` FROM `'._DB_PREFIX_.'vat_cache` WHERE `vat_nr` = "'.pSQL($vatNr).'" ORDER BY `id_vat` DESC');
+ }
+
+ public static function getOrCreateByVat(string $vatNr)
+ {
+ $className = get_called_class();
+ $idVat = $className::getIdByVat($vatNr);
+
+ return ( empty($idVat) ) ? new $className() : new $className($idVat);
+ }
+
+ public static function cleanOldRecords()
+ {
+ return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'vat_cache` WHERE `date_add` < DATE_SUB(NOW(), INTERVAL '.(int)Config::CACHE_VALIDITY.' YEAR)');
+ }
+}
\ No newline at end of file
diff --git a/src/Model/VatNumber.php b/src/Model/VatNumber.php
new file mode 100644
index 0000000..a96449f
--- /dev/null
+++ b/src/Model/VatNumber.php
@@ -0,0 +1,129 @@
+ 'AT',
+ 'BE' => 'BE',
+ 'DK' => 'DK',
+ 'FI' => 'FI',
+ 'FR' => 'FR',
+ 'FX' => 'FR',
+ 'DE' => 'DE',
+ 'GR' => 'EL',
+ 'IE' => 'IE',
+ 'IT' => 'IT',
+ 'LU' => 'LU',
+ 'NL' => 'NL',
+ 'PT' => 'PT',
+ 'ES' => 'ES',
+ 'SE' => 'SE',
+ 'CY' => 'CY',
+ 'EE' => 'EE',
+ 'HU' => 'HU',
+ 'LV' => 'LV',
+ 'LT' => 'LT',
+ 'MT' => 'MT',
+ 'PL' => 'PL',
+ 'SK' => 'SK',
+ 'CZ' => 'CZ',
+ 'SI' => 'SI',
+ 'RO' => 'RO',
+ 'BG' => 'BG',
+ 'HR' => 'HR',
+ ];
+
+ private $isoPattern = [
+ 'AT' => 'U[A-Z\d]{8}',
+ 'BE' => '[0|1]{1}\d{9}',
+ 'BG' => '\d{9,10}',
+ 'CY' => '\d{8}[A-Z]',
+ 'CZ' => '\d{8,10}',
+ 'DE' => '\d{9}',
+ 'DK' => '(\d{2} ?){3}\d{2}',
+ 'EE' => '\d{9}',
+ 'GR' => '\d{9}',
+ 'ES' => '[A-Z]\d{7}[A-Z]|\d{8}[A-Z]|[A-Z]\d{8}',
+ 'FI' => '\d{8}',
+ 'FR' => '([A-Z0-9]{2})\d{9}',
+ 'FX' => '([A-Z0-9]{2})\d{9}',
+ 'HR' => '\d{11}',
+ 'HU' => '\d{8}',
+ 'IE' => '[A-Z\d]{8}|[A-Z\d]{9}',
+ 'IT' => '\d{11}',
+ 'LT' => '(\d{9}|\d{12})',
+ 'LU' => '\d{8}',
+ 'LV' => '\d{11}',
+ 'MT' => '\d{8}',
+ 'NL' => '\d{9}B\d{2}',
+ 'PL' => '\d{10}',
+ 'PT' => '\d{9}',
+ 'RO' => '\d{2,10}',
+ 'SE' => '\d{12}',
+ 'SI' => '\d{8}',
+ 'SK' => '\d{10}',
+ ];
+
+ public function __construct(
+ string $rawVatNumber,
+ string $rawIsoCode
+ ) {
+ $this->vatNumber = $this->commonFormatVatNumber($rawVatNumber);
+
+ $this->isoCode = strtoupper($rawIsoCode);
+
+ $this->validateVatNr();
+ }
+
+ protected function validateVatNr()
+ {
+ $vatPrefix = self::$euIso[$this->isoCode] ?? $this->isoCode;
+
+ if( substr($this->vatNumber, 0, 2) != $vatPrefix ){
+ $this->errorMessage = Context::getContext()->getTranslator()->trans('ISO in VAT number doesn\'t seem to match selected country', [], 'Modules.Ws_VatCheck.Front');
+ }
+
+ $trimmedVat = str_replace($vatPrefix, '', $this->vatNumber);
+ $isoPattern = $this->isoPattern[$this->isoCode];
+
+ if( !preg_match('/^(?:'.$isoPattern.')$/', $trimmedVat) ){
+ $this->errorMessage = Context::getContext()->getTranslator()->trans('VAT number doesn\'t seem to be valid', [], 'Modules.Ws_VatCheck.Front');
+ }
+ }
+
+ public function getFormattedVat()
+ {
+ return $this->vatNumber;
+ }
+
+ public function hasErrors()
+ {
+ return !empty($this->errorMessage);
+ }
+
+ public function isNotEuIso()
+ {
+ return !array_key_exists($this->isoCode, self::$euIso);
+ }
+
+ private function commonFormatVatNumber(string $rawVatNumber)
+ {
+ return strtoupper(preg_replace('/[ .]/', '', $rawVatNumber));
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->errorMessage;
+ }
+}
\ No newline at end of file
diff --git a/src/Module/Admin/AdminHooks.php b/src/Module/Admin/AdminHooks.php
new file mode 100644
index 0000000..5572a2f
--- /dev/null
+++ b/src/Module/Admin/AdminHooks.php
@@ -0,0 +1,13 @@
+getValue('vat_number');
+ $vatNumber = str_replace(' ', '', $vatNumber);
+
+ if (empty($vatNumber)) {
+ return true;
+ }
+
+ $countryIso = (string)Country::getIsoById((string)$form->getValue('id_country'));
+ $vatNumber = new VatNumber($vatNumber, $countryIso);
+
+ if( $vatNumber->isNotEuIso() ){
+ return true;
+ }
+
+ $form->setValue('vat_number', $vatNumber->getFormattedVat());
+ if( $vatNumber->hasErrors() ){
+ $form->getField('vat_number')->addError($vatNumber->getErrorMessage());
+ }
+
+ return $vatNumber->hasErrors();
+ }
+
+ public function hookTaxManager($args)
+ {
+ if (call_user_func(['Websmid\VatCheck\TaxManager\CacheTaxManager', 'isAvailableForThisAddress'], $args['address'])) {
+ return new CacheTaxManager();
+ }
+
+ return false;
+ }
+
+ public function hookDisplayInvoiceLegalFreeText(array $params)
+ {
+ $order = $params['order'];
+ $address = new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
+
+ if (!CacheTaxManager::isAvailableForThisAddress($address)) {
+ return false;
+ }
+
+ $existingText = Configuration::get('PS_INVOICE_LEGAL_FREE_TEXT', (int) $order->id_lang, null, (int) $order->id_shop);
+
+ return trim($existingText . "\n\n" . $this->getIntraCommunityExemptionText((int) $order->id_lang));
+ }
+
+ private function getIntraCommunityExemptionText(int $idLang)
+ {
+ $isoCode = strtolower(Language::getIsoById($idLang));
+
+ return Config::INTRACOMMUNITY_EXEMPTION_TEXTS[$isoCode] ?? Config::INTRACOMMUNITY_EXEMPTION_TEXTS['en'];
+ }
+}
\ No newline at end of file
diff --git a/src/Module/Install.php b/src/Module/Install.php
new file mode 100644
index 0000000..46307bb
--- /dev/null
+++ b/src/Module/Install.php
@@ -0,0 +1,56 @@
+createTables()) {
+ return false;
+ }
+
+ if (!$this->registerHooks()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function registerHooks()
+ {
+ foreach (Config::HOOKS as $hook) {
+ if (!$this->registerHook($hook)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private function createTables()
+ {
+ $sqlToExec = [];
+ $sqlToExec[] = '
+ CREATE TABLE IF NOT EXISTS '._DB_PREFIX_.'vat_cache (
+ `id_vat` INTEGER(12) PRIMARY KEY AUTO_INCREMENT,
+ `vat_nr` VARCHAR(256),
+ `valid` TINYINT(1),
+ `date_add` DATE NOT NULL,
+ `date_upd` DATE NOT NULL
+ );';
+
+ foreach($sqlToExec as $sqlBlock){
+ Db::getInstance()->execute($sqlBlock);
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Service/LoggingService.php b/src/Service/LoggingService.php
new file mode 100644
index 0000000..2d635d6
--- /dev/null
+++ b/src/Service/LoggingService.php
@@ -0,0 +1,52 @@
+output = $output;
+ return $this;
+ }
+
+ public function enableEcho() : self
+ {
+ $this->echo = true;
+ return $this;
+ }
+
+ public function enablePrestaLog() : self
+ {
+ $this->presta = true;
+ return $this;
+ }
+
+ public function log($message, $object = null, $object_id = null)
+ {
+ $now = (new \DateTime())->format('Y-m-d H:i:s');
+ $line = $now . ' --> '.$message . PHP_EOL;
+ $this->logs[] = $line;
+ if (!is_null($this->output)) {
+ $this->output->write($line);
+ }
+
+ if ($this->echo) {
+ echo $line;
+ }
+
+ if ($this->presta) {
+ \PrestaShopLogger::addLog($message, 1, null, $object, $object_id, true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TaxManager/CacheTaxManager.php b/src/TaxManager/CacheTaxManager.php
new file mode 100644
index 0000000..5256ad0
--- /dev/null
+++ b/src/TaxManager/CacheTaxManager.php
@@ -0,0 +1,54 @@
+vat_number) ){
+ return false;
+ }
+
+ if( (int)$address->id_country == (int)Configuration::get('PS_COUNTRY_DEFAULT') ){
+ return false;
+ }
+
+ $isoCode = Country::getIsoById((int)$address->id_country);
+ if( !array_key_exists($isoCode, VatNumber::$euIso) ){
+ return false;
+ }
+
+ if( VatCache::checkVatIsCached($address->vat_number) ){
+ return VatCache::checkVatIsValid($address->vat_number);
+ }
+
+ $vatPrefix = VatNumber::$euIso[$isoCode];
+ $euValidator = new EuValidatorAdapter($address->vat_number, $vatPrefix);
+ if( !$euValidator->checkAndStore() ){
+ return false;
+ }
+
+ return VatCache::checkVatIsValid($address->vat_number);
+ }
+
+ public function getTaxCalculator()
+ {
+ $tax = new Tax();
+ $tax->rate = 0;
+
+ return new TaxCalculator([$tax]);
+ }
+}
diff --git a/upgrade/upgrade-1.0.2.php b/upgrade/upgrade-1.0.2.php
new file mode 100644
index 0000000..c541260
--- /dev/null
+++ b/upgrade/upgrade-1.0.2.php
@@ -0,0 +1,10 @@
+registerHook('displayInvoiceLegalFreeText');
+}
diff --git a/ws_vatcheck.php b/ws_vatcheck.php
new file mode 100644
index 0000000..d3d2f02
--- /dev/null
+++ b/ws_vatcheck.php
@@ -0,0 +1,54 @@
+
+ * @copyright 2007-2024 De Websmid BV
+ * @license Valid for 1 website (or project) for each purchase of license
+ * International Registered Trademark & Property of De Websmid BV
+ */
+
+use Websmid\VatCheck\Module\Install;
+use Websmid\VatCheck\Module\Front\FrontHooks;
+use Websmid\VatCheck\Module\Admin\AdminHooks;
+
+if (file_exists(__DIR__ . '/vendor/autoload.php')) {
+ require_once __DIR__ . '/vendor/autoload.php';
+}
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+class Ws_VatCheck extends \Module
+{
+ use Install;
+ use AdminHooks;
+ use FrontHooks;
+
+ public function __construct()
+ {
+ $this->name = 'ws_vatcheck';
+ $this->version = '1.0.3';
+ $this->author = 'Johan van der Klis | De Websmid BV';
+ $this->need_instance = 0;
+ $this->bootstrap = true;
+
+ parent::__construct();
+
+ $this->displayName = $this->trans('Vat Check', [], 'Modules.Ws_VatCheck.Admin');
+ $this->description = $this->trans('Gecachte vat check', [], 'Modules.Ws_VatCheck.Description');
+ $this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_];
+ }
+}