vat_number.inc in VAT Number 7
Validating a VAT by structure
VAT structure
http://ec.europa.eu/taxation_customs/vies/faq.html | official eu page, chosen for inline docs. http://en.wikipedia.org/wiki/VAT_identification_number http://www.advsofteng.com/vatid.html http://pages.ebay.co.uk/help/account/vat-id.html http://www.hmrc.gov.uk/vat/managing/international/esl/country-codes.htm
Countries
File
vat_number.incView source
<?php
/**
* @file
* vat_number.inc
*
* Validating a VAT by structure
*
* VAT structure
*
* http://ec.europa.eu/taxation_customs/vies/faq.html |
* official eu page, chosen for inline docs.
* http://en.wikipedia.org/wiki/VAT_identification_number
* http://www.advsofteng.com/vatid.html
* http://pages.ebay.co.uk/help/account/vat-id.html
* http://www.hmrc.gov.uk/vat/managing/international/esl/country-codes.htm
*
* Countries
*
* http://europa.eu/about-eu/countries/index_de.htm
*/
/*******************************************************************************
* HELPER FUNCTIONS for EU VAT processing, testing
******************************************************************************/
/**
* Pre check, to output good error messages that can help the user.
*
* @param integer $vat_number
* VAT number to test e.g. GB123 4567 89
*
* @return array
* with vat values
* county_code, for example DE ES FR
* vat_number, the rest after the country code
* vat_format, 0 meaning error, > 1 means format valid
*
*/
function _vat_number_check_vat_format($vat_number) {
$vat_infos = array();
$vat_infos = _vat_number_components($vat_number);
// Check for a country code.
switch ($vat_infos['country_code']) {
/* info on the regexes in general. /^(AT){0,1}U[0-9]{8}$/i'; --- we use {0,1} because eventually we want to provide the country later by a second field formatter so tahts no error, but the function needs to be rewriten for that*/
// Austria.
case 'AT':
// ATU99999999
// 1 block of 9 characters.
$format['AT'] = t("AT U + 8 characters");
$example['AT'] = "AT U12345678";
$regex = '/^(AT){0,1}U[0-9]{8}$/i';
break;
// Belgium.
case 'BE':
// BE0999999999
// 1 block of 10 digits.
$format['BE'] = t("BE 0 (zero) + 9 digits");
$example['BE'] = "BE 0123456789";
$regex = '/^(BE){0,1}[0]{1}[0-9]{9}$/i';
break;
// Bulgaria.
case 'BG':
// BG999999999 or BG9999999999
// 1 block of 9 digits or 1 block of 10 digits.
$format['BG'] = t("BG + 9 or 10 digits");
$example['BG'] = "BG 123456789, BG 1234567890";
$regex = '/^(BG){0,1}[0-9]{9,10}$/i';
break;
// Cyprus.
case 'CY':
// CY99999999L
// 1 block of 9 characters, ATTENTION L is a letter.
$format['CY'] = t("CY + 8 digits + 1 letter");
$example['CY'] = "CY 12345678A";
$regex = '/^(CY){0,1}[0-9]{8}[A-Z]{1}$/i';
break;
// Czech Republic.
case 'CZ':
// CZ99999999 or CZ999999999 or CZ9999999999
// 1 block of either 8, 9 or 10 digits.
$format['CZ'] = t("CZ + 8, 9 or 10 digits");
$example['CZ'] = "CZ 12345678, CZ 123456789, CZ 1234567890";
$regex = '/^(CZ){0,1}[0-9]{8,10}$/i';
break;
// Denmark.
case 'DK':
// DK99 99 99 99
// 4 blocks of 2 digits.
$format['DK'] = t("DK + 4 blocks of 2 digits");
$example['DK'] = "DK 12 34 56 78";
$regex = '/^(DK){0,1}([0-9]){8}$/i';
break;
// Estonia.
case 'EE':
// EE999999999
// 1 block of 9 digits.
$format['EE'] = t("EE + 1 block of 9 digits");
$example['EE'] = "EE 123456789";
$regex = '/^(EE){0,1}[0-9]{9}$/i';
break;
// Germany.
case 'DE':
// DE999999999
// 1 block of 9 digits.
$format['DE'] = t("DE + 1 block of 9 digits");
$example['DE'] = "DE 123456789";
$regex = '/^(DE){0,1}[0-9]{9}$/i';
break;
// Greece.
case 'EL':
// EL999999999
// 1 block of 9 digits.
// ATTENTION: The country code in the VAT ID number (EL) differs from the country's official ISO 3166-1 alpha-2 country code (GR)
$format['EL'] = t("EL + 1 block of 9 digits");
$example['EL'] = "EL 123456789";
$regex = '/^(EL){0,1}[0-9]{9}$/i';
break;
// Portugal.
case 'PT':
// PT999999999
// 1 block of 9 digits.
$format['PT'] = "PT + 1 block of 9 digits";
$example['PT'] = "PT 123456789";
$regex = '/^(PT){0,1}[0-9]{9}$/i';
break;
// France.
case 'FR':
// FRXX 999999999
// 1 block of 2 characters, 1 block of 9 digits,
// X is a letter or a digit.
$format['FR'] = t("FR + 1 block of 2 characters + 1 block of 9 digits");
$example['FR'] = "FR A0 123456789";
$regex = '/^(FR){0,1}[0-9A-Z]{2}[0-9]{9}$/i';
break;
// Finland.
case 'FI':
// FI99999999
// 1 block of 8 digits.
$format['FI'] = t("FI + 1 block of 8 digits");
$example['FI'] = "FI 12345678";
$regex = '/^(FI){0,1}[0-9]{8}$/i';
break;
// Croatia.
case 'HR':
// HR12345678901
// 1 block of 11 digits.
$format['HR'] = t("HR + 1 block of 11 digits");
$example['HR'] = "HR 12345678901";
$regex = '/^(HR){0,1}[0-9]{11}$/i';
break;
// Hungary.
case 'HU':
// HU99999999
// 1 block of 8 digits.
$format['HU'] = t("HU + 1 block of 8 digits");
$example['HU'] = "HU 12345678";
$regex = '/^(HU){0,1}[0-9]{8}$/i';
break;
// Luxembourg.
case 'LU':
// LU99999999
// 1 block of 8 digits.
$format['LU'] = t("LU + 1 block of 8 digits");
$example['LU'] = "LU 12345678";
$regex = '/^(LU){0,1}[0-9]{8}$/i';
break;
// Malta.
case 'MT':
// MT99999999
// 1 block of 8 digits.
$format['MT'] = t("MT + 1 block of 8 digits");
$example['MT'] = "MT 12345678";
$regex = '/^(MT){0,1}[0-9]{8}$/i';
break;
// Slovenia.
case 'SI':
// SI12345678
// 1 block of 8 digits.
$format['SI'] = t("SI + 1 block of 8 digits");
$example['SI'] = "SI 12345678";
$regex = '/^(SI){0,1}[0-9]{8}$/i';
break;
// Ireland.
case 'IE':
// IE9S99999L OR IE9S99999LL
// 1 block of 8 or 9 characters,
// S is a letter, a digit, "+" or "*";
// L is a letter.
$format['IE'] = t("IE + 1 block of 8 or 9 characters. The second character can be a letter, a digit, \"+\" or \"*\"; the last one or two is a letter.");
$example['IE'] = "IE 1X23456A";
$regex = '/^(IE){0,1}[0-9][0-9A-Z\\+\\*][0-9]{5}[A-Z]{1,2}$/i';
break;
// Italy.
case 'IT':
// IT99999999999
// 1 block of 11 digits.
$format['IT'] = t("IT + 1 block of 11 digits");
$example['IT'] = "IT 12345678901";
$regex = '/^(IT){0,1}[0-9]{11}$/i';
break;
// Latvia.
case 'LV':
// LV99999999999
// 1 block of 11 digits.
$format['LV'] = t("LV + 1 block of 11 digits");
$example['LV'] = "LV 12345678901";
$regex = '/^(LV){0,1}[0-9]{11}$/i';
break;
// Lithuania.
case 'LT':
// LT999999999 or LT999999999999
// 1 block of 9 digits, or 1 block of 12 digits.
$format['LT'] = t("LT + 1 block of 9 or 12 digits");
$example['LT'] = "LT 123456789, LT 123456789012";
$regex = '/^(LT){0,1}([0-9]{9}|[0-9]{12})$/i';
break;
// Netherlands.
case 'NL':
// NL999999999B99
// 1 block of 12 characters,
// The 10th position following the prefix is always "B".
$format['NL'] = t("NL + 1 block of 12 characters, where the 10th character is a \"B\"");
$example['NL'] = "NL 123456789B01";
$regex = '/^(NL){0,1}[0-9]{9}B[0-9]{2}$/i';
break;
// Poland.
case 'PL':
// PL9999999999
// 1 block of 10 digits.
$format['PL'] = t("PL + 1 block of 10 digits");
$example['PL'] = "PL 1234567890";
$regex = '/^(PL){0,1}[0-9]{10}$/i';
break;
// Slovakia.
case 'SK':
// SK9999999999
// 1 block of 10 digits.
$format['SK'] = t("SK + 1 block of 10 digits");
$example['SK'] = "SK 1234567890";
$regex = '/^(SK){0,1}[0-9]{10}$/i';
break;
// Romania.
case 'RO':
// RO999999999
// 1 block of minimum 2 digits and maximum 10 digits.
$format['RO'] = t("RO + 1 block of 2 to 10 digits");
$example['RO'] = "RO 1234567890";
$regex = '/^(RO){0,1}[0-9]{2,10}$/i';
break;
// Sweden.
case 'SE':
// SE999999999999
// 1 block of 12 digits.
$format['SE'] = t("SE + 1 block of 12 digits");
$example['SE'] = "SE 123456789012";
$regex = '/^(SE){0,1}[0-9]{12}$/i';
break;
// Spain.
case 'ES':
// ESX9999999X
// 1 block of 9 characters.
// The first and last character (X) may be a letter or a digit,
// but they may not both be digits.
$format['ES'] = t("ES + 1 block of 9 characters. The first and last character may be a letter.");
$example['ES'] = "ES A1234567B";
$regex = '/^(ES){0,1}([0-9A-Z][0-9]{7}[A-Z])|([A-Z][0-9]{7}[0-9A-Z])$/i';
break;
// United Kingdom.
case 'GB':
// GB999 9999 99 standard:
// - 9 digits (block of 3, block of 4, block of 2).
// GB999 9999 99 999 branch traders: 12 digits
// - (as for 9 digits, followed by a block of 3 digits).
// GBGD999 government departments:
// - the letters GD then 3 digits orom 000 to 499 (e.g. GBGD001).
// GBHA999 health authorities:
// - the letters HA then 3 digits from 500 to 999 (e.g. GBHA599).
$format['GB'] = t("GB + 9 or 12 digits; or GB GD + 3 digits; or GB HA + 3 digits");
$example['GB'] = "GB 999 9999 99, GB 999 9999 99 999, GB GD123, GB HA789";
$regex = '/^(GB){0,1}(([0-9]{9})|([0-9]{12})|((GD|HA)[0-9]{3}))$/i';
break;
default:
// No valid country code, return all invalid data.
$vat_infos['vat_format'] = 0;
$vat_infos['message'] = t("The country of the vat can not be detected. Please do not remove the language Prefix. Example: DE123456789 (where DE is the country prefix)");
return $vat_infos;
break;
}
// OK now check if the regex matched the supplied vat.
$vat_number = $vat_infos['country_code'] . $vat_infos['vat_number'];
$vat_infos['vat_format'] = preg_match($regex, $vat_number);
// Output a message with an info about wrong frong format if a
// country code was in front but regex validation does not match
// in case the user added too many numbers or forgot something else.
// Help hi mand display a message about formatting information.
$eu_countries = _vat_number_eu_countries();
// The country code in the VAT ID number for Greece is "EL" instead of the country's official ISO 3166-1 alpha-2 country code ("GR")
// => Replace the key "GR" in the $eu_countries array by "EL"
$eu_countries['EL'] = $eu_countries['GR'];
unset($eu_countries['GR']);
ksort($eu_countries);
// Valid EU country code.
$valid_eu = isset($eu_countries[$vat_infos['country_code']]);
if ($valid_eu && !$vat_infos['vat_format']) {
$vat_infos['message'] = t('You have entered a VAT ID number for the country %country, but it\'s format is incorrect.', array(
'%country' => $eu_countries[$vat_infos['country_code']],
)) . '<br />' . t('It must have the following format: %format', array(
'%format' => $format[$vat_infos['country_code']],
)) . '<br />' . t('Example:') . ' <strong>' . $example[$vat_infos['country_code']] . '</strong>';
}
return $vat_infos;
}
/**
* A list of valid countrys of the EU
*
* @return array
* a list of eu countrys, key is country code, value is readable name
*/
function _vat_number_eu_countries() {
// Necessary for country_get_list().
require_once DRUPAL_ROOT . '/includes/locale.inc';
$countries = country_get_list();
// ISO 3166-1 alpha-2 country codes
$eu_country_codes = array(
"AT",
"BE",
"BG",
"CY",
"CZ",
"DE",
"DK",
"EE",
"ES",
"FI",
"FR",
"GB",
"GR",
"HR",
"HU",
"IE",
"IT",
"LT",
"LU",
"LV",
"MT",
"NL",
"PL",
"PT",
"RO",
"SE",
"SI",
"SK",
);
// Merge in country names from country_get_list().
foreach ($eu_country_codes as $key => $value) {
$eu_countries[$value] = $countries[$value];
}
return $eu_countries;
}
/**
* Check a VAT number. Return error message on error, FALSE on success.
*/
function _vat_number_validate_vat($vat_number, $skip_validation_on_service_failure = FALSE) {
$vat_infos = _vat_number_components($vat_number);
$client = _vat_number_connect_vies_database();
if ($client) {
$params = array(
'countryCode' => $vat_infos['country_code'],
'vatNumber' => $vat_infos['vat_number'],
);
try {
$r = $client
->checkVat($params);
return $r->valid == TRUE ? TRUE : FALSE;
} catch (SoapFault $e) {
watchdog('vat_number', $e->faultstring, NULL, WATCHDOG_ERROR);
if ($skip_validation_on_service_failure) {
// Something is wrong, but that's fine. Pass the validation..
return TRUE;
}
}
}
elseif ($skip_validation_on_service_failure) {
// We couldn't connect but we are allowed to pass the validation.
return TRUE;
}
else {
// No $client and can't skip.
drupal_set_message(t('The connection to the European VAT database server failed and the VAT could not be validated, please contact us about this problem.'), 'error');
return FALSE;
}
}
/**
* Try to connect with the VIES database. Catch PHP warnings if connection with
* the host is not possible.
*/
function _vat_number_connect_vies_database() {
if (!class_exists('SoapClient')) {
drupal_set_message(t('The PHP SOAP extension is not installed on this server. The VAT ID could not be validated.'), 'error');
return FALSE;
}
try {
// Initialize the SoapClient options.
$options = array(
'exceptions' => TRUE,
);
// The VIES load balance servers require the User-Agent HTTP header, but PHP
// versions 5.3 before 5.3.11 and version 5.4.0 contain a bug where the
// SoapClient class ignores the user_agent option. In that case we must use
// the PHP configuration to get it to send the User-Agent HTTP header.
if (version_compare(PHP_VERSION, '5.3.11', '<') || version_compare(PHP_VERSION, '5.4.0', '=')) {
// Set the PHP configuration option if it isn't already set.
if (!ini_get('user_agent')) {
ini_set('user_agent', 'PHP');
}
}
else {
// Set the SoapClient option, as the bug is not present.
$options += array(
'user_agent' => 'PHP',
);
}
return @new SoapClient('http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl', $options);
} catch (SoapFault $e) {
// Connection to host not possible, europe.eu down?
watchdog('vat_number', $e->faultstring, NULL, WATCHDOG_ERROR);
}
}
/**
* Splitting up a VAT to country prefix and number and clean it from spaces etc.
*/
function _vat_number_components($vat_number) {
// Some countries like DK use spaces in the formatting.
// Maybe someone does that too for readability or uses dots.
// We remove all dots, spaces and dashes because they are not
// important for the validation operations and we do NOT regex spaces.
$vatid = preg_replace('/[ .-]/', '', $vat_number);
// First two letters are always country code.
$vat_infos['country_code'] = drupal_strtoupper(drupal_substr($vatid, 0, 2));
$vat_infos['vat_number'] = drupal_strtoupper(drupal_substr($vatid, 2));
return $vat_infos;
}
Functions
Name![]() |
Description |
---|---|
_vat_number_check_vat_format | Pre check, to output good error messages that can help the user. |
_vat_number_components | Splitting up a VAT to country prefix and number and clean it from spaces etc. |
_vat_number_connect_vies_database | Try to connect with the VIES database. Catch PHP warnings if connection with the host is not possible. |
_vat_number_eu_countries | A list of valid countrys of the EU |
_vat_number_validate_vat | Check a VAT number. Return error message on error, FALSE on success. |