AeliaWCEU_VAT_AssistantEU_VAT_Validation

Handles the validation of EU VAT numbers using the VIES service.

Defined (1)

The class is defined in the following location(s).

/src/lib/classes/tools/eu_vat_validation.php  
  1. class EU_VAT_Validation extends \Aelia\WC\Base_Class { 
  2. /** 
  3. * An associative array of country code => EU VAT prefix pairs. 
  4. * @var array 
  5. */ 
  6. protected static $vat_country_prefixes; 
  7.  
  8. /** 
  9. * The errors generated by the class. 
  10. * @var array 
  11. */ 
  12. protected $errors = array(); 
  13. /** 
  14. * The VAT prefix that will be passed for validation. 
  15. * @var string 
  16. */ 
  17. protected $vat_prefix; 
  18. /** 
  19. * The VAT number that will be passed for validation. 
  20. * @var string 
  21. */ 
  22. protected $vat_number; 
  23.  
  24. // @var bool Indicates if debug mode is active. 
  25. protected $debug_mode; 
  26.  
  27. /** 
  28. * Constructor. 
  29. */ 
  30. public function __construct() { 
  31. parent::__construct(); 
  32. $this->logger = new Logger(Definitions::PLUGIN_SLUG); 
  33. $this->text_domain = Definitions::TEXT_DOMAIN; 
  34. $this->debug_mode = WC_Aelia_EU_VAT_Assistant::instance()->debug_mode(); 
  35.  
  36. /** 
  37. * Factory method. 
  38. */ 
  39. public static function factory() { 
  40. return new static(); 
  41.  
  42. /** 
  43. * Returns a list of errors occurred during the validation of a VAT number. 
  44. * @return array 
  45. */ 
  46. public function get_errors() { 
  47. return $this->errors; 
  48.  
  49. /** 
  50. * Returns sn associative array of country code => EU VAT prefix pairs. 
  51. * @return array 
  52. */ 
  53. protected static function get_vat_country_prefixes() { 
  54. if(empty(self::$vat_country_prefixes)) { 
  55. self::$vat_country_prefixes = array(); 
  56. foreach(WC_Aelia_EU_VAT_Assistant::instance()->get_eu_vat_countries() as $country_code) { 
  57. self::$vat_country_prefixes[$country_code] = $country_code; 
  58.  
  59. // Correct vat prefixes that don't match the country code and add some 
  60. // extra ones 
  61. // Greece 
  62. self::$vat_country_prefixes['GR'] = 'EL'; 
  63. // Isle of Man 
  64. self::$vat_country_prefixes['IM'] = 'GB'; 
  65. // Monaco 
  66. self::$vat_country_prefixes['MC'] = 'FR'; 
  67.  
  68. return apply_filters('wc_aelia_euva_vat_country_prefixes', self::$vat_country_prefixes); 
  69.  
  70. /** 
  71. * Parses a VAT number, removing special characters and the country prefix, if 
  72. * any. 
  73. */ 
  74. public function parse_vat_number($vat_number) { 
  75. // Remove special characters 
  76. $vat_number = strtoupper(str_replace(array(' ', '-', '_', '.'), '', $vat_number)); 
  77.  
  78. // Remove country code if set at the begining 
  79. $prefix = substr($vat_number, 0, 2); 
  80. if(in_array($prefix, array_values(self::get_vat_country_prefixes()))) { 
  81. $vat_number = substr($vat_number, 2); 
  82. if(empty($vat_number)) { 
  83. return false; 
  84. return $vat_number; 
  85.  
  86. /** 
  87. * Returns the VAT prefix used by a specific country. 
  88. * @param string country A country code. 
  89. * @return string|false 
  90. */ 
  91. public function get_vat_prefix($country) { 
  92. $country_prefixes = self::get_vat_country_prefixes(); 
  93. return get_value($country, $country_prefixes, false); 
  94.  
  95. /** 
  96. * Caches the validation result of a VAT number for a limited period of time. 
  97. * This will improve performances when customers will place new orders in a 
  98. * short timeframe, by reducing the amount of calls to the VIES service. 
  99. * @param string vat_prefix The VAT prefix. 
  100. * @param string vat_number The VAT number. 
  101. * @param array result The validation result. 
  102. */ 
  103. protected function cache_validation_result($vat_prefix, $vat_number, $result) { 
  104. set_transient(Definitions::TRANSIENT_EU_NUMBER_VALIDATION_RESULT . $vat_prefix . $vat_number,  
  105. $result, 1 * HOUR_IN_SECONDS); 
  106.  
  107. /** 
  108. * Returns the cached result of a VAT number validation, if it exists. 
  109. * @param string vat_prefix The VAT prefix. 
  110. * @param string vat_number The VAT number. 
  111. * @return array|bool An array with the validatin result, or false if a cached 
  112. * result was not found. 
  113. */ 
  114. protected function get_cached_validation_result($vat_prefix, $vat_number) { 
  115. // In debug mode, behave as if nothing was cached 
  116. if($this->debug_mode) { 
  117. return false; 
  118. return get_transient(Definitions::TRANSIENT_EU_NUMBER_VALIDATION_RESULT . $vat_prefix . $vat_number); 
  119.  
  120. /** 
  121. * Validates the argument passed for validation, transforming a countr code 
  122. * into a VAT prefix and checking the VAT number before it's used for a VIES 
  123. * request. 
  124. * @param string country A country code. It will be used to determine the VAT 
  125. * number prefix. 
  126. * @param string vat_number A VAT number. 
  127. * @return bool 
  128. */ 
  129. protected function validate_request_arguments($country, $vat_number) { 
  130. // Some preliminary formal validation, to prevent unnecessary requests with 
  131. // clearly invalid data 
  132. $this->vat_number = $this->parse_vat_number($vat_number); 
  133. if($this->vat_number == false) { 
  134. $this->errors[] = sprintf(__('An empty or invalid VAT number was passed for validation. ' . 
  135. 'The VAT number should contain several digits, without the ' . 
  136. 'country prefix. Received VAT number: "%s".',  
  137. $this->text_domain),  
  138. $vat_number); 
  139.  
  140. $this->vat_prefix = $this->get_vat_prefix($country); 
  141. if(empty($this->vat_prefix)) { 
  142. $this->errors[] = sprintf(__('A VAT prefix could not be found for the specified country. ' . 
  143. 'Received country code: "%s".',  
  144. $this->text_domain),  
  145. $country); 
  146.  
  147. return empty($this->errors); 
  148.  
  149. /** 
  150. * Checks if a cached response is valid. In some older versions, an incorrect 
  151. * response was cached and, when returned, caused the plugin to consider invalid 
  152. * numbers that were actually valid. 
  153. * @param mixed $response The cached response. 
  154. * @return bool 
  155. */ 
  156. protected function valid_cached_response($response) { 
  157. return ($response != false) && 
  158. is_array($response) && 
  159. (get_value('valid', $response) == 'true'); 
  160.  
  161. /** 
  162. * Validates a VAT number. 
  163. * @param string country The country code to which the VAT number belongs. 
  164. * @param string vat_number The VAT number to validate. 
  165. * @return array|bool An array with the validation response returned by the 
  166. * VIES service, or false when the request could not be sent for some reason. 
  167. * @link http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl 
  168. */ 
  169. public function validate_vat_number($country, $vat_number) { 
  170. $this->errors = array(); 
  171.  
  172. if(!$this->validate_request_arguments($country, $vat_number)) { 
  173. return false; 
  174.  
  175. // Return a cached response, if one exists. Faster than sending a SOAP request. 
  176. $cached_response = $this->get_cached_validation_result($this->vat_prefix, $this->vat_number); 
  177. if($this->valid_cached_response($cached_response)) { 
  178. return $cached_response; 
  179.  
  180. // Debug 
  181. //var_dump($country, $vat_number, $this->vat_prefix, $this->vat_number);die(); 
  182.  
  183. // Cache the WSDL 
  184. $wsdl = get_transient('VIES_WSDL'); 
  185. if(empty($wsdl) || $this->debug_mode) { 
  186. $wsdl = new wsdl('http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl', '', '', '', '', 5); 
  187. // Cache the WSDL for one minute. Sometimes VIES returns an invalid WSDL,  
  188. // caching it for too long could cause the whole validation system to fail 
  189. set_transient('VIES_WSDL', $wsdl, 60); 
  190.  
  191. // Create SOAP client 
  192. $client = new nusoap_client($wsdl, 'wsdl'); 
  193. // Ensure that UTF-8 encoding is used, so that the client won't crash when 
  194. // "odd" characters are used 
  195. $client->decode_utf8 = false; 
  196. $client->soap_defencoding = 'UTF-8'; 
  197. $client->setUseCurl(true); 
  198. // Check if any error occurred initialising the SOAP client. We won't be able 
  199. // to continue, in such case. 
  200. $error = $client->getError(); 
  201. if($error) { 
  202. $this->errors[] = sprintf(__('An error occurred initialising SOAP client. Error message: "%s".',  
  203. $this->text_domain),  
  204. $error); 
  205. return false; 
  206.  
  207. $response = $client->call('checkVat', array( 
  208. 'countryCode' => $this->vat_prefix,  
  209. 'vatNumber' => $this->vat_number,  
  210. )); 
  211.  
  212. if(is_array($response)) { 
  213. $result = array( 
  214. 'valid' => ($response['valid'] === 'true'),  
  215. 'company_name' => get_arr_value('name', $response, ''),  
  216. 'company_address' => get_arr_value('address', $response, ''),  
  217. 'errors' => array(get_arr_value('FaultString', $response, '')),  
  218. 'raw_response' => $response,  
  219. ); 
  220. else { 
  221. $result = array( 
  222. 'valid' => null,  
  223. 'company_name' => null,  
  224. 'company_address' => null,  
  225. 'errors' => $this->get_errors(),  
  226. 'raw_response' => null,  
  227. ); 
  228.  
  229. // Cache response for valid VAT numbers 
  230. if(($result['valid'] === 'true') && !$this->debug_mode) { 
  231. $this->cache_validation_result($this->vat_prefix, $this->vat_number, $result); 
  232. return $result;