Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TIN check, Strict mode and Country check (idea for complementing this library) #14

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# EU VAT Number Validator
# EU VAT and TIN Number Validator

A simple and clean PHP class that validates EU VAT numbers against the central ec.europa.eu database (using the official europa API).
A simple and clean PHP class that validates EU VAT and TIN numbers against the central ec.europa.eu database (using the official europa API).

![EU VATIN validator; EU Flag](eu-flag.svg)


## The Problem

Validate VAT numbers might be difficult and if you use a validation pattern to check if the format is valid, you are never sure if the VAT registration number is still valid.
Validate VAT and TIN numbers might be difficult and if you use a validation pattern to check if the format is valid, you are never sure if the VAT registration number is still valid.

## The Solution

This [PHP VAT validator library](https://github.com/pH-7/eu-vat-validator) uses real-time data feeds from individual EU member states' VAT systems so you are sure of the validity of the number and avoid fraud with expired or wrong VAT numbers.
This [PHP VAT validator library](https://github.com/pH-7/eu-vat-validator) uses real-time data feeds from individual EU member states' VAT and TIN systems so you are sure of the validity of the number and avoid fraud with expired or wrong VAT numbers.

For example, this kind of validation can be very useful on online payment forms.

Expand Down Expand Up @@ -50,9 +50,9 @@ require 'src/autoloader.php';

```php
use PH7\Eu\Vat\Validator;
use PH7\Eu\Vat\Provider\Europa;
use PH7\Eu\Vat\Provider\EuropaVAT;

$oVatValidator = new Validator(new Europa, '0472429986', 'BE');
$oVatValidator = new Validator(new EuropaVAT, '0472429986', 'BE');

if ($oVatValidator->check()) {
$sRequestDate = $oVatValidator->getRequestDate();
Expand All @@ -72,8 +72,19 @@ if ($oVatValidator->check()) {

## Optimization (Suggestion)

Depending of the use of this library, it could be handy to cache the result specifically for each specified VAT number.
Depending of the use of this library, it could be handy to cache the result specifically for each specified VAT or TIN number.

## Strict mode

By default this librery clean VAT or TIN numbers before checking them (it cleans by deleting from VAT or TIN numbers: country Code and these special characters: '-', '_', '.', ',', ' ').

If you don't want, you can check numbers in strict mode, just by calling check function with option value TRUE (default value is FALSE). In the above example, you only need to change this line:
```php
if ($oVatValidator->check(true)) {
```
```php
if ($oVatValidator->check(strict: true)) {
```

## Requirements

Expand Down
98 changes: 89 additions & 9 deletions example.php
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
<?php
/**
* @author Pierre-Henry Soria <hi@pH7.me>
* @copyright (c) 2017-2023, Pierre-Henry Soria. All Rights Reserved.
* @author Pierre-Henry Soria <pierrehenrysoria@gmail.com>
* @copyright (c) 2017-2019, Pierre-Henry Soria. All Rights Reserved.
* @license GNU General Public License; <https://www.gnu.org/licenses/gpl-3.0.en.html>
*/

require 'src/autoloader.php';

use PH7\Eu\Vat\Provider\Europa;
use PH7\Eu\Vat\Provider\EuropaVAT;
use PH7\Eu\Vat\Validator;

$sEuVatNumber = '0472429986'; // EU VAT number
$sEuCountryCode = 'BE'; // EU two-letter country code
use PH7\Eu\Tin\Provider\EuropaTIN;
use PH7\Eu\Tin\ValidatorTIN;

$oVatValidator = new Validator(new Europa, $sEuVatNumber, $sEuCountryCode);
$oVatValidator = new Validator(new EuropaVAT, '0472429986', 'BE');

echo $oVatValidator->all() . '<br />';
echo 'Check: ' . ($oVatValidator->check() ? 'true' : 'false') . '<br />';

if ($oVatValidator->check()) {
$sRequestDate = $oVatValidator->getRequestDate();

// Optional - explicitly format the date to d-m-Y format
// Optional, format the date
$sFormattedRequestDate = (new DateTime)->format('d-m-Y');

echo 'Business Name: ' . $oVatValidator->getName() . '<br />';
Expand All @@ -27,5 +29,83 @@
echo 'Member State: ' . $oVatValidator->getCountryCode() . '<br />';
echo 'VAT Number: ' . $oVatValidator->getVatNumber() . '<br />';
} else {
echo 'Invalid VAT number';
echo 'Invalid VAT number' . '<br />';
}

echo '<br />';

$oVatValidatorInvalid = new Validator(new EuropaVAT, '047242998', 'BE');

echo $oVatValidatorInvalid->all() . '<br />';
echo 'Check: ' . ($oVatValidatorInvalid->check() ? 'true' : 'false') . '<br />';

if ($oVatValidatorInvalid->check()) {
$sRequestDate = $oVatValidatorInvalid->getRequestDate();
// Optional, format the date
$sFormattedRequestDate = (new DateTime)->format('d-m-Y');

echo 'Business Name: ' . $oVatValidatorInvalid->getName() . '<br />';
echo 'Address: ' . $oVatValidatorInvalid->getAddress() . '<br />';
echo 'Request Date: ' . $sFormattedRequestDate . '<br />';
echo 'Member State: ' . $oVatValidatorInvalid->getCountryCode() . '<br />';
echo 'VAT Number: ' . $oVatValidatorInvalid->getVatNumber() . '<br />';
} else {
echo 'Invalid VAT number' . '<br />';
}

echo '<br />';

$oTinValidator = new ValidatorTIN(new EuropaTIN, '78888888S', 'ES');

echo $oTinValidator->all() . '<br />';

if ($oTinValidator->check()) {
$sRequestDate = $oTinValidator->getRequestDate();
// Optional, format the date
$sFormattedRequestDate = (new DateTime)->format('d-m-Y');
echo 'Request Date: ' . $sFormattedRequestDate . '<br />';
echo 'TIN Number: ' . $oTinValidator->getTinNumber() . '<br />';

} else {
echo 'Invalid TIN number' . '<br />';
echo 'Structure: ' . ($oTinValidator->checkStructure() ? 'true' : 'false'). '<br />';
echo 'Syntax: ' . ($oTinValidator->checkSyntax() ? 'true' : 'false') . '<br />';
}

echo '<br />';

$oTinValidatorInvalid = new ValidatorTIN(new EuropaTIN, '78888888R', 'ES');

echo $oTinValidatorInvalid->all() . '<br />';

if ($oTinValidatorInvalid->check()) {
$sRequestDate = $oTinValidatorInvalid->getRequestDate();
// Optional, format the date
$sFormattedRequestDate = (new DateTime)->format('d-m-Y');
echo 'Request Date: ' . $sFormattedRequestDate . '<br />';
echo 'TIN Number: ' . $oTinValidatorInvalid->getTinNumber() . '<br />';

} else {
echo 'Invalid TIN number' . '<br />';
echo 'Structure: ' . ($oTinValidatorInvalid->checkStructure() ? 'true' : 'false') . '<br />';
echo 'Syntax: ' . ($oTinValidatorInvalid->checkSyntax() ? 'true' : 'false') . '<br />';
}

echo '<br />';

$oTinValidatorInvalid2 = new ValidatorTIN(new EuropaTIN, '7888S8888', 'ES');

echo $oTinValidatorInvalid2->all() . '<br />';

if ($oTinValidatorInvalid2->check()) {
$sRequestDate = $oTinValidatorInvalid2->getRequestDate();
// Optional, format the date
$sFormattedRequestDate = (new DateTime)->format('d-m-Y');
echo 'Request Date: ' . $sFormattedRequestDate . '<br />';
echo 'TIN Number: ' . $oTinValidatorInvalid2->getTinNumber() . '<br />';

} else {
echo 'Invalid TIN number' . '<br />';
echo 'Structure: ' . ($oTinValidatorInvalid2->checkStructure() ? 'true' : 'false') . '<br />';
echo 'Syntax: ' . ($oTinValidatorInvalid2->checkSyntax() ? 'true' : 'false') . '<br />';
}
13 changes: 13 additions & 0 deletions src/Tin/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php
/**
* @author Pierre-Henry Soria <pierrehenrysoria@gmail.com>
* @copyright (c) 2017-2019, Pierre-Henry Soria. All Rights Reserved.
* @license GNU General Public License; <https://www.gnu.org/licenses/gpl-3.0.en.html>
*/

namespace PH7\Eu\Tin;

class Exception extends \Exception
{

}
83 changes: 83 additions & 0 deletions src/Tin/Provider/EuropaTIN.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* @author Pierre-Henry Soria <pierrehenrysoria@gmail.com>
* @copyright (c) 2017-2022, Pierre-Henry Soria. All Rights Reserved.
* @license GNU General Public License; <https://www.gnu.org/licenses/gpl-3.0.en.html>
*/

declare(strict_types=1);

namespace PH7\Eu\Tin\Provider;

use PH7\Eu\Tin\Exception;
use SoapClient;
use SoapFault;
use stdClass;

class EuropaTIN implements Providable
{
protected const TIN_EU_COUNTRY_LIST = ['AT','BE','BG','CY','CZ','DE','DK','EE','EL','ES','FI','FR','HR','HU','IE','IT','LU','LV','LT','MT','NL','PL','PT','RO','SE','SI','SK'];
public const COUNTRY_NOT_VALID = 'Country not valid in Europa TIN Service: %s';

public const EU_TIN_API = 'https://ec.europa.eu';
public const EU_TIN_WSDL_ENDPOINT = '/taxation_customs/tin/services/checkTinService.wsdl';

private const IMPOSSIBLE_CONNECT_API_MESSAGE = 'Impossible to connect to the Europa TIN SOAP: %s';
private const IMPOSSIBLE_RETRIEVE_DATA_MESSAGE = 'Impossible to retrieve the TIN details: %s';

/** @var SoapClient */
private $oClient;

/**
* EuropaTIN Provider constructor
*
* @throws Exception
*/
public function __construct()
{
try {
$this->oClient = new SoapClient($this->getApiUrl());
} catch (SoapFault $oExcept) {
throw new Exception(
sprintf(self::IMPOSSIBLE_CONNECT_API_MESSAGE, $oExcept->faultstring),
0,
$oExcept
);
}
}

public function getApiUrl(): string
{
return static::EU_TIN_API . static::EU_TIN_WSDL_ENDPOINT;
}

/**
* Send the TIN number and country code to europa.eu API and get the data.
*
* @param int|string $sTinNumber The TIN number
* @param string $sCountryCode The country code
*
* @return stdClass The TIN number's details.
*
* @throws Exception
*/
public function getResource($sTinNumber, string $sCountryCode): stdClass
{
if (!in_array(strtoupper($sCountryCode), self::TIN_EU_COUNTRY_LIST)) {
throw new Exception(
sprintf(self::COUNTRY_NOT_VALID, strtoupper($sCountryCode))
);
}
try {
$aDetails = [
'countryCode' => strtoupper($sCountryCode),
'tinNumber' => $sTinNumber
];
return $this->oClient->checkTin($aDetails);
} catch (SoapFault $oExcept) {
throw new Exception(
sprintf(self::IMPOSSIBLE_RETRIEVE_DATA_MESSAGE, $oExcept->faultstring)
);
}
}
}
19 changes: 19 additions & 0 deletions src/Tin/Provider/Providable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
/**
* @author Pierre-Henry Soria <pierrehenrysoria@gmail.com>
* @copyright (c) 2017-2019, Pierre-Henry Soria. All Rights Reserved.
* @license GNU General Public License; <https://www.gnu.org/licenses/gpl-3.0.en.html>
*/

declare(strict_types=1);

namespace PH7\Eu\Tin\Provider;

use stdClass;

interface Providable
{
public function getApiUrl(): string;

public function getResource($sTinNumber, string $sCountryCode): stdClass;
}
15 changes: 15 additions & 0 deletions src/Tin/Validatable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* @author Pierre-Henry Soria <pierrehenrysoria@gmail.com>
* @copyright (c) 2017-2019, Pierre-Henry Soria. All Rights Reserved.
* @license GNU General Public License; <https://www.gnu.org/licenses/gpl-3.0.en.html>
*/

namespace PH7\Eu\Tin;

interface Validatable
{
public function check(): bool;

public function sanitize(): void;
}
Loading