AeliaWCEU_VAT_AssistantWC_Aelia_EU_VAT_Assistant

EU VAT Assistant plugin.

Defined (1)

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

/src/plugin-main.php  
  1. class WC_Aelia_EU_VAT_Assistant extends Aelia_Plugin { 
  2. public static $version = '1.7.8.170421'; 
  3.  
  4. public static $plugin_slug = Definitions::PLUGIN_SLUG; 
  5. public static $text_domain = Definitions::TEXT_DOMAIN; 
  6. public static $plugin_name = 'WooCommerce EU VAT Assistant'; 
  7.  
  8. /** 
  9. * A list of countries to which sales are allowed. The list is altered by the 
  10. * plugin if the admin populated the list of countries to which the sale is 
  11. * not allowed. 
  12. * @var array 
  13. */ 
  14. protected $allowed_sale_countries; 
  15.  
  16. // @var string The country to which a VAT number applies (if any). 
  17. protected $vat_country; 
  18. // @var string The VAT number entered by the customer at checkout. 
  19. protected $vat_number; 
  20. // @var bool Indicates if the VAT number was validated. 
  21. protected $vat_number_validated; 
  22. // @var EU_VAT_Validation The instance of the EU VAT Number validator. 
  23. protected $_eu_vat_validation; 
  24.  
  25. /** Shop's base country. Used to determine if a VAT exemption can be applied 
  26. * (usually, exemption cannot be applied to customers located in shop's base 
  27. * country). 
  28. * @var string 
  29. * @since 1.4.12.150923 
  30. */ 
  31. protected $shop_base_country; 
  32.  
  33. /** 
  34. * Initialises and returns the plugin instance. 
  35. * @return Aelia\WC\EU_VAT_Assistant\WC_Aelia_EU_VAT_Assistant 
  36. */ 
  37. public static function factory() { 
  38. // Load Composer autoloader 
  39. require_once(__DIR__ . '/vendor/autoload.php'); 
  40.  
  41. // Example on how to initialise a settings controller and a messages controller 
  42. $settings_page_renderer = new Settings_Renderer(); 
  43. $settings_controller = new Settings(Settings::SETTINGS_KEY,  
  44. self::$text_domain,  
  45. $settings_page_renderer); 
  46. $messages_controller = new Messages(self::$text_domain); 
  47.  
  48. $plugin_instance = new self($settings_controller, $messages_controller); 
  49. return $plugin_instance; 
  50.  
  51. /** 
  52. * Constructor. 
  53. * @param \Aelia\WC\Settings settings_controller The controller that will handle 
  54. * the plugin settings. 
  55. * @param \Aelia\WC\Messages messages_controller The controller that will handle 
  56. * the messages produced by the plugin. 
  57. */ 
  58. public function __construct($settings_controller = null,  
  59. $messages_controller = null) { 
  60. // Load Composer autoloader 
  61. require_once(__DIR__ . '/vendor/autoload.php'); 
  62.  
  63. parent::__construct($settings_controller, $messages_controller); 
  64.  
  65. // Instantiate the logger specific to this plugin 
  66. $this->logger = new Logger(Definitions::PLUGIN_SLUG); 
  67.  
  68. // The commented line below is needed for Codestyling Localization plugin to 
  69. // understand what text domain is used by this plugin 
  70. //load_plugin_textdomain('wc-aelia-eu-vat-assistant', false, $this->path('languages') . '/'); 
  71.  
  72. /** 
  73. * Indicates if debug mode is active. 
  74. * @return bool 
  75. */ 
  76. public function debug_mode() { 
  77. return self::settings()->get(Settings::FIELD_DEBUG_MODE); 
  78.  
  79. /** 
  80. * Returns an instance of the EU VAT numbers validator. 
  81. * @return EU_VAT_Validation 
  82. * @since 1.3.20.150330 
  83. */ 
  84. protected function eu_vat_validation() { 
  85. if(empty($this->_eu_vat_validation)) { 
  86. // Instantiate the the EU VAT numbers validator 
  87. $this->_eu_vat_validation = EU_VAT_Validation::factory(); 
  88. return $this->_eu_vat_validation; 
  89.  
  90. /** 
  91. * Returns the country corresponding to visitor's IP address. 
  92. * @return string 
  93. */ 
  94. protected function get_ip_address_country() { 
  95. if(empty($this->ip_address_country)) { 
  96. $this->ip_address_country = apply_filters('wc_aelia_eu_vat_assistant_ip_address_country', IP2Location::factory()->get_visitor_country()); 
  97. return $this->ip_address_country; 
  98.  
  99. /** 
  100. * Returns an array with all the tax types in use in the European Union, together 
  101. * with their descriptions. 
  102. * @return array 
  103. */ 
  104. public function get_eu_vat_rate_types() { 
  105. return array( 
  106. 'standard_rate' => __('Standard rates', self::$text_domain),  
  107. 'reduced_rate' => __('Reduced rates', self::$text_domain),  
  108. 'reduced_rate_alt' => __('Reduced rates (alternative)', self::$text_domain),  
  109. 'super_reduced_rate' => __('Super reduced rates', self::$text_domain),  
  110. 'parking_rate' => __('Parking rates', self::$text_domain),  
  111. ); 
  112.  
  113. /** 
  114. * Returns a list of countries to which EU VAT applies. This method takes into 
  115. * account countries such as Monaco and Isle of Man, which are not returned as 
  116. * part of EU countries by WooCommerce. 
  117. * @return array 
  118. */ 
  119. public function get_eu_vat_countries() { 
  120. if(empty($this->eu_vat_countries)) { 
  121. $this->eu_vat_countries = $this->wc()->countries->get_european_union_countries(); 
  122. // Add countries that are not strictly EU countries, but to which EU VAT rules apply 
  123. $this->eu_vat_countries[] = 'MC'; 
  124. $this->eu_vat_countries[] = 'IM'; 
  125. return apply_filters('wc_aelia_eu_vat_assistant_eu_vat_countries', $this->eu_vat_countries); 
  126.  
  127. /** 
  128. * Indicates if the plugin has been configured. 
  129. * @return bool 
  130. */ 
  131. public function plugin_configured() { 
  132. $vat_currency = $this->settings_controller()->get(Settings::FIELD_VAT_CURRENCY); 
  133. return !empty($vat_currency); 
  134.  
  135. /** 
  136. * Checks if the VAT rates retrieved by the EU VAT Assistant are valid. Rates 
  137. * are valid when, for each country, they contain at least a standard rate 
  138. * (invalid rates often have a "null" object associated to them). 
  139. * @param array vat_rates An array containing the VAT rates for all EU countries. 
  140. * @return bool 
  141. */ 
  142. protected function valid_eu_vat_rates($vat_rates) { 
  143. foreach($vat_rates as $country_code => $rates) { 
  144. if(empty($rates['standard_rate']) || 
  145. !is_numeric($rates['standard_rate'])) { 
  146. return false; 
  147. return true; 
  148.  
  149. /** 
  150. * Retrieves the EU VAT rats from https://euvatrates.com website. 
  151. * @return array|null An array with the details of VAT rates, or null on failure. 
  152. * @link https://euvatrates.com 
  153. */ 
  154. public function get_eu_vat_rates() { 
  155. $vat_rates = get_transient(Definitions::TRANSIENT_EU_VAT_RATES); 
  156. if(!empty($vat_rates) && is_array($vat_rates)) { 
  157. return $vat_rates; 
  158.  
  159. $eu_vat_url = 'http://euvatrates.com/rates.json'; 
  160. $eu_vat_response = wp_remote_get($eu_vat_url, array( 
  161. 'timeout' => 5,  
  162. )); 
  163. if(is_wp_error($eu_vat_response)) { 
  164. $this->log(sprintf(__('Could not fetch EU VAT rates from remote site. Error(s): "%s". Remote site: "%s".', self::$text_domain),  
  165. implode(', ', $eu_vat_response->get_error_messages()),  
  166. $eu_vat_url)); 
  167. return null; 
  168.  
  169. // Ensure that the VAT rates are in the correct format 
  170. $vat_rates = json_decode(get_value('body', $eu_vat_response), true); 
  171. if($vat_rates === null) { 
  172. $this->log(sprintf(__('Unexpected response returned by EU VAT rates site. Returned data: "%s". Remote site: "%s".', self::$text_domain),  
  173. get_value('body', $eu_vat_response),  
  174. $eu_vat_url)); 
  175. return null; 
  176. // Add rates for countries that use other countries' tax rates 
  177. // Monaco uses French VAT 
  178. $vat_rates['rates']['MC'] = $vat_rates['rates']['FR']; 
  179. $vat_rates['rates']['MC']['country'] = 'Monaco'; 
  180.  
  181. // Isle of Man uses UK's VAT 
  182. $vat_rates['rates']['IM'] = $vat_rates['rates']['UK']; 
  183. $vat_rates['rates']['IM']['country'] = 'Isle of Man'; 
  184.  
  185. // Fix the country codes received from the feed. Some country codes are 
  186. // actually the VAT country code. We need the ISO Code instead. 
  187. $country_codes_to_fix = array( 
  188. 'EL' => 'GR',  
  189. 'UK' => 'GB',  
  190. ); 
  191. foreach($country_codes_to_fix as $code => $correct_code) { 
  192. $vat_rates['rates'][$correct_code] = $vat_rates['rates'][$code]; 
  193. unset($vat_rates['rates'][$code]); 
  194.  
  195. /** Fix the VAT rates for countries that don't have a reduced VAT rate. For 
  196. * those countries, the standard rate should be used as the "reduced" rate. 
  197. * @since 1.4.15.151029 
  198. */ 
  199. foreach($vat_rates['rates'] as $country_code => $rates) { 
  200. if(!is_numeric($rates['reduced_rate'])) { 
  201. $rates['reduced_rate'] = $rates['standard_rate']; 
  202. $vat_rates['rates'][$country_code] = $rates; 
  203.  
  204. ksort($vat_rates['rates']); 
  205.  
  206. // Debug 
  207. //var_dump($vat_rates);die(); 
  208.  
  209. // Ensure that the VAT rates are valid before caching them 
  210. if($this->valid_eu_vat_rates($vat_rates['rates'])) { 
  211. // Cache the VAT rates, to prevent unnecessary calls to the remote site 
  212. set_transient(Definitions::TRANSIENT_EU_VAT_RATES, $vat_rates, 2 * HOUR_IN_SECONDS); 
  213. return $vat_rates; 
  214.  
  215. /** 
  216. * Stores the list of countries to which sales are allowed, before the plugin 
  217. * alters it. 
  218. */ 
  219. protected function store_allowed_sale_countries() { 
  220. $this->allowed_sale_countries = wc()->countries->get_allowed_countries(); 
  221.  
  222. /** 
  223. * Returns the list of countries to which sales are not allowed. 
  224. * @return array 
  225. */ 
  226. protected function get_sale_disallowed_countries() { 
  227. return $this->_settings_controller->get(Settings::FIELD_SALE_DISALLOWED_COUNTRIES, array()); 
  228.  
  229. /** 
  230. * Performs operation when woocommerce has been loaded. 
  231. */ 
  232. public function woocommerce_loaded() { 
  233. if(!is_admin()) { 
  234. // Add logic to filter the countries to which sales are allowed 
  235. $this->store_allowed_sale_countries(); 
  236. $restricted_countries = $this->get_sale_disallowed_countries(); 
  237. if(!empty($restricted_countries)) { 
  238. add_filter('pre_option_woocommerce_allowed_countries', array($this, 'pre_option_woocommerce_allowed_countries'), 10, 1); 
  239. add_filter('woocommerce_countries_allowed_countries', array($this, 'woocommerce_countries_allowed_countries'), 10, 1); 
  240.  
  241. // Store shop base country for later use. This is necessary to prevent a 
  242. // conflict with the Tax Display plugin, which may override the base country 
  243. // at checkout 
  244. $this->shop_base_country = $this->wc()->countries->get_base_country(); 
  245.  
  246. /** 
  247. * Sets the hooks required by the plugin. 
  248. */ 
  249. protected function set_hooks() { 
  250. parent::set_hooks(); 
  251.  
  252. if(is_admin() && !$this->plugin_configured()) { 
  253. add_action('admin_notices', array($this, 'settings_notice')); 
  254. // Don't set any hook until the plugin has been configured 
  255. return; 
  256.  
  257. if(!is_admin() || self::doing_ajax()) { 
  258. // Set hooks that should be used on frontend only 
  259. Frontend_Integration::init(); 
  260.  
  261. if(is_admin()) { 
  262. // Set hooks that should be used on backend only 
  263. $this->set_admin_hooks(); 
  264.  
  265. // Order actions 
  266. add_action('woocommerce_checkout_update_order_meta', array($this, 'woocommerce_checkout_update_order_meta'), 20, 2); 
  267. add_action('woocommerce_checkout_update_user_meta', array($this, 'woocommerce_checkout_update_user_meta'), 20, 2); 
  268. add_action('woocommerce_checkout_billing', array($this, 'woocommerce_checkout_billing'), 40, 1); 
  269. // Update VAT data for subscription renewals 
  270. add_action('woocommerce_subscriptions_renewal_order_created', array($this, 'woocommerce_subscriptions_renewal_order_created'), 20, 2); 
  271.  
  272. // Checkout hooks 
  273. add_action('woocommerce_checkout_update_order_review', array($this, 'woocommerce_checkout_update_order_review')); 
  274. add_action('woocommerce_checkout_process', array($this, 'woocommerce_checkout_process')); 
  275.  
  276. // Ajax hooks 
  277. add_action('wp_ajax_validate_eu_vat_number', array($this, 'wp_ajax_validate_eu_vat_number')); 
  278. add_action('wp_ajax_nopriv_validate_eu_vat_number', array($this, 'wp_ajax_validate_eu_vat_number')); 
  279. add_action('wp_ajax_collect_order_vat_info', array($this, 'wp_ajax_collect_order_vat_info')); 
  280.  
  281. // Cron hooks 
  282. // Add hooks to automatically update exchange rates 
  283. add_action($this->_settings_controller->exchange_rates_update_hook(),  
  284. array($this->_settings_controller, 'scheduled_update_exchange_rates')); 
  285.  
  286. // Integration with Currency Switcher 
  287. add_action('wc_aelia_currencyswitcher_settings_saved', array($this, 'wc_aelia_currencyswitcher_settings_saved')); 
  288.  
  289. // UI 
  290. add_action('add_meta_boxes', array($this, 'add_meta_boxes')); 
  291. add_filter('woocommerce_order_formatted_billing_address', array($this, 'woocommerce_order_formatted_billing_address'), 10, 2); 
  292. add_filter('woocommerce_formatted_address_replacements', array($this, 'woocommerce_formatted_address_replacements'), 10, 2); 
  293. add_filter('woocommerce_localisation_address_formats', array($this, 'woocommerce_localisation_address_formats'), 10, 1); 
  294. // Order Edit page - Add the VAT number to the billing fields 
  295. // @since 1.6.2.160315 
  296. add_filter('woocommerce_admin_billing_fields', array('\Aelia\WC\EU_VAT_Assistant\Orders_Integration', 'woocommerce_admin_billing_fields'), 10, 1); 
  297.  
  298. // Hooks to be called by 3rd party 
  299. // Allow 3rd parties to convert a value from one currency to another 
  300. add_filter('wc_aelia_eu_vat_assistant_convert', array($this, 'convert'), 10, 4); 
  301. add_filter('wc_aelia_eu_vat_assistant_get_order_exchange_rate', array($this, 'wc_aelia_eu_vat_assistant_get_order_exchange_rate'), 10, 2); 
  302. add_filter('wc_aelia_eu_vat_assistant_get_setting', array($this, 'wc_aelia_eu_vat_assistant_get_setting'), 10, 2); 
  303.  
  304. // Reports 
  305. ReportsManager::init(); 
  306.  
  307. protected function set_admin_hooks() { 
  308. Tax_Settings_Integration::set_hooks(); 
  309. Products_Integration::init(); 
  310.  
  311. /** 
  312. * Determines if one of plugin's admin pages is being rendered. Override it 
  313. * if plugin implements pages in the Admin section. 
  314. * @return bool 
  315. */ 
  316. protected function rendering_plugin_admin_page() { 
  317. $screen = get_current_screen(); 
  318. $page_id = is_object($screen) ? $screen->id : ''; 
  319.  
  320. return ($page_id == 'woocommerce_page_' . Definitions::MENU_SLUG); 
  321.  
  322. /** 
  323. * Registers the script and style files needed by the admin pages of the 
  324. * plugin. Extend in descendant plugins. 
  325. */ 
  326. protected function register_plugin_admin_scripts() { 
  327. // Scripts 
  328. wp_register_script('chosen',  
  329. '//cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.jquery.min.js',  
  330. array('jquery'),  
  331. null,  
  332. true); 
  333.  
  334. // Styles 
  335. wp_register_style('chosen',  
  336. '//cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.min.css',  
  337. array(),  
  338. null,  
  339. 'all'); 
  340. // WordPress already includes jQuery UI script, but no CSS for it. Therefore,  
  341. // we need to load it from an external source 
  342. wp_register_style('jquery-ui',  
  343. '//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css',  
  344. array(),  
  345. null,  
  346. 'all'); 
  347.  
  348. wp_enqueue_style('jquery-ui'); 
  349. wp_enqueue_style('chosen'); 
  350.  
  351. wp_enqueue_script('jquery-ui-tabs'); 
  352. wp_enqueue_script('jquery-ui-sortable'); 
  353. wp_enqueue_script('chosen'); 
  354.  
  355. parent::register_plugin_admin_scripts(); 
  356.  
  357. /** 
  358. * Loads the scripts required in the Admin section. 
  359. */ 
  360. public function load_admin_scripts() { 
  361. parent::load_admin_scripts(); 
  362. $this->localize_admin_scripts(); 
  363.  
  364. /** 
  365. * Loads the settings that will be used by the admin scripts. 
  366. */ 
  367. protected function localize_admin_scripts() { 
  368. // Prepare parameters for common admin scripts 
  369. $admin_scripts_params = array(); 
  370.  
  371. // Prepare parameters for Tax Settings pages 
  372. if((get_value('page', $_GET) == 'wc-settings') && 
  373. (get_value('tab', $_GET) == 'tax')) { 
  374. $admin_scripts_params = Tax_Settings_Integration::localize_admin_scripts($admin_scripts_params); 
  375.  
  376. // Prepare parameters for Orders pages 
  377. // @since 1.6.1.160201 
  378. if(Orders_Integration::editing_order()) { 
  379. $admin_scripts_params = Orders_Integration::localize_admin_scripts($admin_scripts_params); 
  380.  
  381. // Add localization parameters for EU VAT plugin settings page 
  382. if($this->rendering_plugin_admin_page()) { 
  383. $admin_scripts_params = array_merge($admin_scripts_params, array( 
  384. 'eu_vat_countries' => $this->get_eu_vat_countries(),  
  385. 'user_interface' => array( 
  386. 'add_eu_countries_trigger' => __('Add European Union countries', self::$text_domain),  
  387. ),  
  388. )); 
  389.  
  390. wp_localize_script(static::$plugin_slug . '-admin-common',  
  391. 'aelia_eu_vat_assistant_admin_params',  
  392. $admin_scripts_params); 
  393.  
  394. /** 
  395. * Loads Styles and JavaScript for the frontend. Extend as needed in 
  396. * descendant classes. 
  397. */ 
  398. public function load_frontend_scripts() { 
  399. // Enqueue the required Frontend stylesheets 
  400. wp_enqueue_style(static::$plugin_slug . '-frontend'); 
  401.  
  402. // JavaScript 
  403. wp_enqueue_script(static::$plugin_slug . '-frontend'); 
  404.  
  405. $this->localize_frontend_scripts(); 
  406.  
  407. /** 
  408. * Loads the settings that will be used by the frontend scripts. 
  409. */ 
  410. protected function localize_frontend_scripts() { 
  411. // Prepare parameters for common admin scripts 
  412.  
  413. // Build the list of countries for which the EU VAT field will be displayed 
  414. // at checkout 
  415. $eu_vat_countries = $this->get_eu_vat_countries(); 
  416.  
  417. $frontend_scripts_params = array( 
  418. 'tax_based_on' => get_option('woocommerce_tax_based_on'),  
  419. 'eu_vat_countries' => $eu_vat_countries,  
  420. 'ajax_url' => admin_url('admin-ajax.php', 'relative'),  
  421. 'show_self_cert_field' => self::settings()->get(Settings::FIELD_SHOW_SELF_CERTIFICATION_FIELD),  
  422. 'eu_vat_field_required' => self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED),  
  423. 'hide_self_cert_field_with_valid_vat' => self::settings()->get(Settings::FIELD_HIDE_SELF_CERTIFICATION_FIELD_VALID_VAT_NUMBER),  
  424. 'ip_address_country' => $this->get_ip_address_country(),  
  425. 'use_shipping_as_evidence' => self::settings()->get(Settings::FIELD_USE_SHIPPING_ADDRESS_AS_EVIDENCE),  
  426. 'user_interface' => array( 
  427. 'self_certification_field_title' => __(self::settings()->get(Settings::FIELD_SELF_CERTIFICATION_FIELD_TITLE), self::$text_domain),  
  428. ),  
  429. 'show_eu_vat_number_for_base_country' => (bool)self::settings()->get(Settings::FIELD_SHOW_EU_VAT_FIELD_IF_CUSTOMER_IN_BASE_COUNTRY),  
  430. 'shop_base_country' => $this->shop_base_country,  
  431. ); 
  432.  
  433. wp_localize_script(static::$plugin_slug . '-frontend',  
  434. 'aelia_eu_vat_assistant_params',  
  435. $frontend_scripts_params); 
  436.  
  437. /** 
  438. * Returns a list of the report IDs introduced by this plugin. 
  439. * @return array A list of report IDs. 
  440. * @since 1.5.8.160112 
  441. */ 
  442. protected function get_available_reports() { 
  443. return array( 
  444. 'eu_vat_by_country_report',  
  445. 'vies_report',  
  446. 'intrastat_report',  
  447. 'sales_summary_report',  
  448. ); 
  449.  
  450. /** 
  451. * Registers the script and style files required in the backend (even outside 
  452. * of plugin's pages). Extend in descendant plugins. 
  453. */ 
  454. protected function register_common_admin_scripts() { 
  455. parent::register_common_admin_scripts(); 
  456.  
  457. // The admin styles of this plugin are required throughout the WooCommerce 
  458. // Administration, not just on the plugin settings page 
  459. wp_register_style(static::$plugin_slug . '-admin',  
  460. $this->url('plugin') . '/design/css/admin.css',  
  461. array(),  
  462. null,  
  463. 'all'); 
  464. wp_enqueue_style(static::$plugin_slug . '-admin'); 
  465.  
  466. // Load common JavaScript for the Admin section 
  467. wp_enqueue_script(static::$plugin_slug . '-admin-common',  
  468. $this->url('js') . '/admin/admin-common.js',  
  469. array('jquery'),  
  470. null,  
  471. true); 
  472.  
  473. if((get_value('page', $_GET) == 'wc-reports') && 
  474. in_array(get_value('report', $_GET), $this->get_available_reports())) { 
  475. // Load JavaScript for reports 
  476. wp_enqueue_script(static::$plugin_slug . '-jquery-bbq',  
  477. $this->url('js') . '/admin/jquery.ba-bbq.min.js',  
  478. array('jquery'),  
  479. null,  
  480. true); 
  481. wp_enqueue_script(static::$plugin_slug . '-admin-reports',  
  482. $this->url('js') . '/admin/admin-reports.js',  
  483. array('jquery'),  
  484. null,  
  485. true); 
  486.  
  487. /** 
  488. * Adds an error to WooCommerce, so that it can be displayed to the customer. 
  489. * @param string error_message The error message to display. 
  490. */ 
  491. protected function add_woocommerce_error($error_message) { 
  492. wc_add_notice($error_message, 'error'); 
  493.  
  494. /** 
  495. * Converts an amount from a Currency to another. 
  496. * @param float amount The amount to convert. 
  497. * @param string from_currency The source Currency. 
  498. * @param string to_currency The destination Currency. 
  499. * @param int price_decimals The amount of decimals to use when rounding the 
  500. * converted result. 
  501. * @return float The amount converted in the destination currency. 
  502. */ 
  503. public function convert($amount, $from_currency, $to_currency, $price_decimals = null) { 
  504. // No need to try converting an amount that is not numeric. This can happen 
  505. // quite easily, as "no value" is passed as an empty string 
  506. if(!is_numeric($amount)) { 
  507. return $amount; 
  508.  
  509. // No need to spend time converting a currency to itself 
  510. if($from_currency == $to_currency) { 
  511. return $amount; 
  512.  
  513. if(empty($price_decimals)) { 
  514. $price_decimals = absint(get_option('woocommerce_price_num_decimals')); 
  515.  
  516. // Retrieve exchange rates from the configuration 
  517. $exchange_rates = $this->_settings_controller->get_exchange_rates(); 
  518. //var_dump($exchange_rates); 
  519. try { 
  520. $error_message_template = __('Currency conversion - %s currency not valid or exchange rate ' . 
  521. 'not found for: "%s". Please make sure that the EU VAT assistant '. 
  522. 'plugin is configured correctly and that an Exchange ' . 
  523. 'Rate has been specified for each of the available currencies.',  
  524. self::$text_domain); 
  525. $from_currency_rate = get_value($from_currency, $exchange_rates, null); 
  526. if(empty($from_currency_rate)) { 
  527. $error_message = sprintf($error_message_template,  
  528. __('Source', self::$text_domain),  
  529. $from_currency); 
  530. $this->log($error_message, false); 
  531. if($this->debug_mode()) { 
  532. throw new InvalidArgumentException($error_message); 
  533.  
  534. $to_currency_rate = get_value($to_currency, $exchange_rates, null); 
  535. if(empty($to_currency_rate)) { 
  536. $error_message = sprintf($error_message_template,  
  537. __('Destination', self::$text_domain),  
  538. $from_currency); 
  539. $this->log($error_message, false); 
  540. if($this->debug_mode()) { 
  541. throw new InvalidArgumentException($error_message); 
  542.  
  543. $exchange_rate = $to_currency_rate / $from_currency_rate; 
  544. catch(Exception $e) { 
  545. $full_message = $e->getMessage() . 
  546. sprintf(__('Stack trace: %s', Definitions::TEXT_DOMAIN),  
  547. $e->getTraceAsString()); 
  548. $this->log($full_message, false); 
  549. trigger_error($full_message, E_USER_ERROR); 
  550. return round($amount * $exchange_rate, $price_decimals); 
  551.  
  552. /** 
  553. * Returns the exchange rate used to calculate the VAT for an order in the 
  554. * currency that has to be used for VAT returns. 
  555. * @param int order_id The ID of the order from which to retrieve the exchange 
  556. * rate. 
  557. * @return float|false The exchange rate, if present, or false if it was not 
  558. * saved against the order. 
  559. */ 
  560. public function get_order_vat_exchange_rate($order_id) { 
  561. $order = new Order($order_id); 
  562. $vat_exchange_rate = $order->get_vat_data('vat_currency_exchange_rate'); 
  563. return $vat_exchange_rate; 
  564.  
  565. /** 
  566. * Returns a full VAT number, inclusive of country prefix. 
  567. * @param string country A country code. 
  568. * @param string vat_number A VAT number. 
  569. * @param string A full VAT number, with the country prefix (e.g. IE1234567X) 
  570. * @since 1.3.20.150330 
  571. */ 
  572. public function get_full_vat_number($country, $vat_number) { 
  573. $vat_number = $this->eu_vat_validation()->parse_vat_number($vat_number); 
  574. // If an invalid VAT number was passed, return an empty string 
  575. if(!$vat_number) { 
  576. return false; 
  577. $vat_prefix = $this->eu_vat_validation()->get_vat_prefix($country); 
  578. return $vat_prefix . $vat_number; 
  579.  
  580. /** 
  581. * Saves the evidence used to determine the VAT rate to apply. 
  582. * @param Aelia_Order order The order to process. 
  583. */ 
  584. protected function save_eu_vat_data($order_id) { 
  585. // Save the VAT number details 
  586. $order_vat_number = $this->get_full_vat_number($this->vat_country, $this->vat_number); 
  587. if($order_vat_number == false) { 
  588. $this->log(sprintf(__('Order ID "%s". Invalid VAT Number parsed: "%s".', self::$text_domain),  
  589. $order_id,  
  590. $order_vat_number), true); 
  591. $order_vat_number = ''; 
  592. update_post_meta($order_id, 'vat_number', $order_vat_number); 
  593. update_post_meta($order_id, '_vat_country', $this->vat_country); 
  594. update_post_meta($order_id, '_vat_number_validated', $this->vat_number_validated); 
  595. // Store customer's self-certification flag 
  596. if(get_value(Definitions::ARG_LOCATION_SELF_CERTIFICATION, $_POST, 0)) { 
  597. $location_self_certified = Definitions::YES; 
  598. else { 
  599. $location_self_certified = Definitions::NO; 
  600. update_post_meta($order_id, '_customer_location_self_certified', $location_self_certified); 
  601.  
  602. // Use plugins' internal Order class, which implements convenience methods 
  603. // to handle EU VAT reguirements 
  604. $order = new Order($order_id); 
  605. // Generate and store details about order VAT 
  606. $order->update_vat_data(); 
  607. // Save EU VAT compliance evidence 
  608. $order->store_vat_evidence(); 
  609.  
  610. /** 
  611. * Renders the EU VAT field using the appropriate view. 
  612. */ 
  613. protected function render_eu_vat_field() { 
  614. wc_get_template('checkout-eu-vat-field.php', array(), 'woocommerce/eu-vat-assistant', $this->path('views') . '/frontend/'); 
  615.  
  616. /** 
  617. * Renders the self-certification field using the appropriate view. 
  618. */ 
  619. protected function render_self_certification_field() { 
  620. wc_get_template('checkout-self-certification-field.php', array(), 'woocommerce/eu-vat-assistant', $this->path('views') . '/frontend/'); 
  621.  
  622. /** 
  623. * Validates an EU VAT number. 
  624. * @param string country A country code. 
  625. * @param string vat_number A VAT number. 
  626. * @return array An array with the result of the validation. 
  627. */ 
  628. protected function validate_eu_vat_number($country, $vat_number) { 
  629. $validation_result = $this->eu_vat_validation()->validate_vat_number($country, $vat_number); 
  630. // TODO Refactor the communication with the EU_VAT_Validation class to make it easier to replace it with other classes 
  631.  
  632. // A validation result of "null" indicates a failure in sending the SOAP 
  633. // request, therefore we can't process it 
  634. if($validation_result['valid'] !== null) { 
  635. $vat_number_valid = false; 
  636.  
  637. // Extract the error message, if any 
  638. $validation_error = $validation_result['errors'][0]; 
  639. if(!empty($validation_error)) { 
  640. // If server was busy, we may have to accept the VAT number as valid,  
  641. // depending on plugin settings 
  642. if(strcasecmp($validation_error, 'SERVER_BUSY') == 0) { 
  643. $validation_result['valid'] = self::settings()->get(Settings::FIELD_ACCEPT_VAT_NUMBER_WHEN_VALIDATION_SERVER_BUSY, true); 
  644.  
  645. $this->log(sprintf(__('EU VAT validation response (JSON): "%s".',  
  646. self::$text_domain),  
  647. json_encode($validation_result)),  
  648. false); 
  649.  
  650. //var_dump($validation_result);die(); 
  651.  
  652. return apply_filters('wc_aelia_euva_eu_vat_number_raw_validation_result', $validation_result, $country, $vat_number); 
  653.  
  654. /** 
  655. * Determines if a country is part of the EU. 
  656. * @param string country The country code to check. 
  657. * @return bool 
  658. */ 
  659. public function is_eu_country($country) { 
  660. return in_array($country, $this->get_eu_vat_countries()); 
  661.  
  662. /** 
  663. * Determines if there is enough evidence about customer's location to satisfy 
  664. * EU requirements. The method collects all the country codes that can be derived 
  665. * from the data posted at checkout, and looks for one that occurs twice or 
  666. * more. As per EU regulations, we only need two matching pieces of evidence. 
  667. * @param array posted_data The data posted at checkout. 
  668. * @return string|bool The code of the country with the most entries, if one 
  669. * with at least two occurrences is found, or false if no country appears more 
  670. * than once. 
  671. */ 
  672. protected function sufficient_location_evidence($posted_data) { 
  673. // Collect all the countries we can get from the posted data 
  674. $countries = array(); 
  675.  
  676. $countries[] = $billing_country = get_value('billing_country', $posted_data); 
  677. $countries[] = $this->get_ip_address_country(); 
  678.  
  679. // Take shipping country as evidence only if explicitly told so 
  680. if(self::settings()->get(Settings::FIELD_USE_SHIPPING_ADDRESS_AS_EVIDENCE)) { 
  681. if(get_value('ship_to_different_address', $posted_data)) { 
  682. $countries[] = get_value('shipping_country', $posted_data); 
  683. else { 
  684. // If shipping to the same address as billing, add the billing country again 
  685. // as a further proof of location 
  686. $countries[] = $billing_country; 
  687.  
  688. $countries = array_filter(apply_filters('wc_aelia_eu_vat_assistant_location_evidence_countries', $countries)); 
  689.  
  690. // Calculate how many times each country appears in the list and sort the 
  691. // array by count, in descending order, so that the country with most matches 
  692. // is at the top 
  693. $country_count = array_count_values($countries); 
  694. arsort($country_count); 
  695. reset($country_count); 
  696.  
  697. // We only need at least two matching entries in the array. If that is the 
  698. // case, return the country code (it may be useful later, and it's better than 
  699. // a simple "true") 
  700. $top_country = key($country_count); 
  701. if($country_count[$top_country] >= 2) { 
  702. return $top_country; 
  703. // If the top country doesn't have at least a count of two, then the other 
  704. // entries won't have it either. In such case, inform the caller that we 
  705. // don't have sufficient evidence 
  706. return false; 
  707.  
  708. /** 
  709. * Sets customer's VAT exemption, depending on his country and the VAT number 
  710. * he entered. 
  711. * @param string country The country against which the VAT exemption will be checked. 
  712. * @param string vat_number The VAT number entered by the customer. 
  713. * @return int A numeric result code indicating if the check succeeded, whether 
  714. * the customer is VAT exempt or not, or failed (i.e. when an EU customer 
  715. * entered an invalid EU VAT number). 
  716. */ 
  717. protected function set_customer_vat_exemption($customer_country, $vat_number) { 
  718. $this->log(sprintf(__('Setting customer VAT exemption. Customer country: "%s". Number: "%s".',  
  719. self::$text_domain),  
  720. $customer_country, $vat_number), false); 
  721. $result = Definitions::RES_OK; 
  722. // Assume that customer is not VAT exempt. This is a sensible default, as 
  723. // exemption applies only in specific cases, and only for EU customers. 
  724. // Customers outside the EU are not technically "VAT exempt". To them, a 
  725. // special "Zero VAT" rate applies 
  726. $customer_vat_exemption = false; 
  727. // Clear VAT information before validation 
  728. $this->vat_country = ''; 
  729. $this->vat_number = ''; 
  730. $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_NO_NUMBER; 
  731. $this->raw_vat_validation_response = array(); 
  732.  
  733. // If VAT number was hidden, customer cannot be made VAT exempt. We can skip 
  734. // the checks, in such case 
  735. if(self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED) != Settings::OPTION_EU_VAT_NUMBER_FIELD_HIDDEN) { 
  736. // No need to check the VAT number if either the country or the number are empty 
  737. if(!empty($customer_country) && !empty($vat_number)) { 
  738. // Store the VAT information. They will be used later, to update the order 
  739. // when it's finalised 
  740. $this->vat_country = $customer_country; 
  741. $this->vat_number = $vat_number; 
  742.  
  743. // Customer is based in the European Union, therefore EU VAT rules and validations apply 
  744. if($this->is_eu_country($customer_country)) { 
  745. // Validate the VAT number for EU customers 
  746. $this->raw_vat_validation_response = $this->validate_eu_vat_number($customer_country, $vat_number); 
  747.  
  748. // Debug 
  749. //var_dump($this->raw_vat_validation_response); 
  750.  
  751. // If the EU VAT number is valid, we must determine if the customer should 
  752. // be considered exempt from VAT 
  753. if($this->raw_vat_validation_response['valid'] == true) { 
  754. /** An EU customer will be considered exempt from VAT if: 
  755. * - He is located in a country different from shop's base country. 
  756. * - He is located in the same country as the shop, and option "remove VAT 
  757. * when customer in located in shop's base country" is enabled. 
  758. */ 
  759. if(($customer_country != $this->shop_base_country) || 
  760. self::settings()->get(Settings::FIELD_REMOVE_VAT_IF_CUSTOMER_IN_BASE_COUNTRY)) { 
  761. $customer_vat_exemption = true; 
  762. $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_VALID; 
  763. else { 
  764. // A "null" response means that nothing was returned by the server. 
  765. // In such case, log an error 
  766. if($this->raw_vat_validation_response['valid'] === null) { 
  767. $this->log(sprintf(__('EU VAT Number could not be validated due to errors in ' . 
  768. 'the communication with the remote service. Error details ' . 
  769. '(JSON): "%s".',  
  770. self::$text_domain),  
  771. json_encode($this->raw_vat_validation_response['errors']))); 
  772. $this->vat_number_validated = Definitions::VAT_NUMBER_COULD_NOT_BE_VALIDATED; 
  773. $result = Definitions::ERR_COULD_NOT_VALIDATE_VAT_NUMBER; 
  774. else { 
  775. $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_NOT_VALID; 
  776. $result = Definitions::ERR_INVALID_EU_VAT_NUMBER; 
  777. else { 
  778. $this->vat_number_validated = Definitions::VAT_NUMBER_VALIDATION_NON_EU; 
  779. else { 
  780. $this->log(__('EU VAT Number field was hidden by plugin configuration. Customer ' . 
  781. 'cannot be made VAT exempt.',  
  782. self::$text_domain)); 
  783. $this->log(sprintf(__('VAT exemption check completed. Customer exemption: %d. Result: %d.',  
  784. self::$text_domain),  
  785. (int)$customer_vat_exemption,  
  786. (int)$result)); 
  787.  
  788. $customer_vat_exemption = apply_filters('wc_aelia_eu_vat_assistant_customer_vat_exemption', $customer_vat_exemption, $this->vat_country, $this->vat_number, $this->vat_number_validated, $this->raw_vat_validation_response); 
  789.  
  790. $this->wc()->customer->set_is_vat_exempt($customer_vat_exemption); 
  791. return $result; 
  792.  
  793. /** 
  794. * Updates the order meta after it has been created, or updated, at checkout. 
  795. * @param int order_id The order just created, or updated. 
  796. * @param array posted_data The data posted to create the order. 
  797. */ 
  798. public function woocommerce_checkout_update_order_meta($order_id, $posted_data) { 
  799. $this->save_eu_vat_data($order_id); 
  800.  
  801. /** 
  802. * Updates the metadata of a renewal order created by the Subscriptions plugin. 
  803. * @param WC_Order $renewal_order The renewal order. 
  804. * @param WC_Order $original_order The original order used to create the renewal order. 
  805. */ 
  806. public function woocommerce_subscriptions_renewal_order_created($renewal_order, $original_order) { 
  807. // If we are processing checkout, no action needs to be taken. Handler for 
  808. // "woocommerce_checkout_update_order_meta" hook will take care of saving the 
  809. // necessary data 
  810. if(is_checkout()) { 
  811. return; 
  812.  
  813. // Use the internal Order classes 
  814. $original_order = new Order(aelia_get_order_id($original_order)); 
  815. $renewal_order = new Order(aelia_get_order_id($renewal_order)); 
  816.  
  817. // Copy the VAT evidence from the original subscription order. A renewal order 
  818. // is created without customer's intervention, therefore 
  819. $original_order_vat_info = array( 
  820. Order::META_EU_VAT_EVIDENCE => $original_order->eu_vat_evidence,  
  821. 'vat_number' => $original_order->get_customer_vat_number(),  
  822. '_vat_country' => $original_order->vat_country,  
  823. '_vat_number_validated' => $original_order->vat_number_validated,  
  824. '_customer_location_self_certified' => $original_order->customer_location_self_certified,  
  825. ); 
  826. $original_order_vat_info = apply_filters('wc_aelia_eu_vat_assistant_subscription_original_order_vat_info', $original_order_vat_info, $original_order, $renewal_order); 
  827. foreach($original_order_vat_info as $meta_key => $value) { 
  828. $renewal_order->set_meta($meta_key, $value) ; 
  829. // Generate and store details about order VAT 
  830. $renewal_order->update_vat_data(); 
  831.  
  832. /** 
  833. * Updates the customer meta at checkout. 
  834. * @param int user_id The user ID who placed the order. 
  835. * @param array posted_data The data posted to create the order. 
  836. */ 
  837. public function woocommerce_checkout_update_user_meta($user_id, $posted_data) { 
  838. update_user_meta($user_id, 'vat_number', $this->get_full_vat_number($this->vat_country, $this->vat_number)); 
  839. update_user_meta($user_id, '_vat_country', $this->vat_country); 
  840. update_user_meta($user_id, '_vat_number_validated', $this->vat_number_validated); 
  841.  
  842. /** 
  843. * Renders the EU VAT field on the checkout form. 
  844. */ 
  845. public function woocommerce_checkout_billing() { 
  846. // Render EU VAT Number element, unless it's hidden 
  847. if(self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED) != Settings::OPTION_EU_VAT_NUMBER_FIELD_HIDDEN) { 
  848. $this->render_eu_vat_field(); 
  849.  
  850. // Render self-certification element, unless it's hidden 
  851. if(self::settings()->get(Settings::FIELD_SHOW_SELF_CERTIFICATION_FIELD) != Settings::OPTION_SELF_CERTIFICATION_FIELD_NO) { 
  852. $this->render_self_certification_field(); 
  853.  
  854. /** 
  855. * Ajax handler. Validates an EU VAT number using VIES service and outputs 
  856. * a JSON response with the validation result. 
  857. */ 
  858. public function wp_ajax_validate_eu_vat_number() { 
  859. // Validate EU VAT number and return the result as JSON 
  860. wp_send_json($this->validate_eu_vat_number( 
  861. get_value(Definitions::ARG_COUNTRY, $_GET),  
  862. get_value(Definitions::ARG_VAT_NUMBER, $_GET) 
  863. )); 
  864.  
  865. /** 
  866. * Performs VAT validation operations during order review. 
  867. * @param array form_data The data posted from the order review page. 
  868. */ 
  869. public function woocommerce_checkout_update_order_review($form_data) { 
  870. // $form_data contains an HTTP query string. Let's "explode" it into an 
  871. // array 
  872. parse_str($form_data, $posted_data); 
  873.  
  874. // Debug 
  875. //var_dump("POSTED DATA", $posted_data);die(); 
  876.  
  877. // Cannot continue without the billing country 
  878. if(empty($posted_data['billing_country'])) { 
  879. return; 
  880.  
  881. // Determine which country will be used to calculate taxes 
  882. switch(get_option('woocommerce_tax_based_on')) { 
  883. case 'billing' : 
  884. case 'base' : 
  885. $country = !empty($posted_data['billing_country']) ? $posted_data['billing_country'] : ''; 
  886. break; 
  887. case 'shipping' : 
  888. if(get_value('ship_to_different_address', $posted_data) && !empty($posted_data['shipping_country'])) { 
  889. $country = $posted_data['shipping_country']; 
  890. else { 
  891. $country = $posted_data['billing_country']; 
  892. break; 
  893.  
  894. // Check if customer is VAT exempt 
  895. $country = wc_clean($country); 
  896. if(empty($country)) { 
  897. $this->log(sprintf(__('Unexpected condition occurred: no customer country was posted ' . 
  898. 'to "checkout update order review" event. Full posted data ' . 
  899. '(JSON): "%s".',  
  900. self::$text_domain),  
  901. json_encode($posted_data)), false); 
  902. $vat_number = wc_clean(get_value('vat_number', $posted_data)); 
  903. $this->set_customer_vat_exemption($country, $vat_number); 
  904.  
  905. /** 
  906. * Indicates if a customers is VAT exempt. 
  907. * @return bool 
  908. * @since 1.7.5.170405 
  909. */ 
  910. protected function customer_is_vat_exempt() { 
  911. // Use the new WC 3.0 method, if available. If not, default to the object 
  912. // property 
  913. return method_exists($this->wc()->customer, 'get_is_vat_exempt') ? $this->wc()->customer->get_is_vat_exempt() : $this->wc()->customer->is_vat_exempt; 
  914.  
  915. /** 
  916. * Performs validations to make sure that either there is enough evidence to 
  917. * determine customer's location, or customer had self-certified his location. 
  918. * This method automatically adds a checkout error when needed. 
  919. * @return void 
  920. */ 
  921. protected function validate_self_certification() { 
  922. // If the self-certification element was forcibly hidden, it doesn't make 
  923. // sense to perform this validation 
  924. if(self::settings()->get(Settings::FIELD_SHOW_SELF_CERTIFICATION_FIELD) == Settings::OPTION_SELF_CERTIFICATION_FIELD_NO) { 
  925. return; 
  926.  
  927. /** We need to check the available gelocation evidence in two cases: 
  928. * - When customer is not VAT exempt. 
  929. * - Regardless of the VAT number, if option "hide self certification field 
  930. * with valid VAT number" is DISABLED. 
  931. * In all other cases, there must be sufficient evidence for the order to 
  932. * go through, or customer must self-certify. 
  933. */ 
  934. if(($this->customer_is_vat_exempt() == false) || 
  935. (self::settings()->get(Settings::FIELD_HIDE_SELF_CERTIFICATION_FIELD_VALID_VAT_NUMBER) == false)) { 
  936. // Check if we have sufficient evidence about customer's location 
  937. $sufficient_location_evidence_result = $this->sufficient_location_evidence($_POST); 
  938. if($sufficient_location_evidence_result == false) { 
  939. // Convenience variable, to better understand the check below 
  940. $ignore_insufficient_evidence = ($this->vat_number_validated == Definitions::VAT_NUMBER_VALIDATION_VALID); 
  941. /** If insufficient location evidence has been provided, check if self-certification 
  942. * is required to accept the order. If it is required, and it was not provided,  
  943. * stop the checkout and inform the customer. 
  944. */ 
  945. if(!$ignore_insufficient_evidence && 
  946. self::settings()->get(Settings::FIELD_SELF_CERTIFICATION_FIELD_REQUIRED_WHEN_CONFLICT) && 
  947. (get_value(Definitions::ARG_LOCATION_SELF_CERTIFICATION, $_POST) == false)) { 
  948. // Inform the customer that he must self-certify to proceed with the purchase 
  949. $error = __('Unfortunately, we could not collect sufficient information to confirm ' . 
  950. 'your location. To proceed with the order, please tick the box below the ' . 
  951. 'billing details to confirm that you will be using the product(s) in ' . 
  952. 'country you selected.',  
  953. self::$text_domain); 
  954. $this->add_woocommerce_error($error); 
  955.  
  956. /** 
  957. * Indicates if a valid EU VAT number is required to complete checkout. A VAT 
  958. * number is required in the following cases: 
  959. * - When the requirement setting is "always required". 
  960. * - When the requirement setting is "required for EU only" and customer 
  961. * selected a billing country that is part of the EU. 
  962. * @param string customer_country The country for which the check should be 
  963. * performed. 
  964. * @return bool 
  965. */ 
  966. protected function is_eu_vat_number_required($customer_country) { 
  967. $result = false; 
  968. /** If the VAT field is visible for shop's base country, or the customer is 
  969. * not in shop's base country, then we can check if it should be required. 
  970. */ 
  971. if(self::settings()->get(Settings::FIELD_SHOW_EU_VAT_FIELD_IF_CUSTOMER_IN_BASE_COUNTRY) || 
  972. ($customer_country != $this->shop_base_country)) { 
  973. $eu_vat_number_required = self::settings()->get(Settings::FIELD_EU_VAT_NUMBER_FIELD_REQUIRED); 
  974. /** The EU VAT field is required in one of the following cases 
  975. * 1- If the related option is set to "required". 
  976. * 2- If the option is set to "only if company name is filled" and the 
  977. * customer entered a company name. 
  978. * 3- If the option is set "only for EU customers" and the customer selected 
  979. * a EU country. 
  980. */ 
  981. $result = ($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED) || 
  982. // Check if option is "required only if company has been entered" 
  983. // and the company field is not empty 
  984. (($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED_IF_COMPANY_FILLED) && 
  985. !empty($_POST['billing_company']) && (trim($_POST['billing_company']) != '')) || 
  986. // Check if option is "required only if company has been entered 
  987. // and address is in the EU", and the company field is not empty 
  988. (($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED_IF_COMPANY_FILLED_EU_ONLY) && 
  989. $this->is_eu_country($customer_country) && 
  990. !empty($_POST['billing_company']) && (trim($_POST['billing_company']) != '')) || 
  991. // Check if option is "required only if company is in EU" and the 
  992. // company is in the EU 
  993. (($eu_vat_number_required == Settings::OPTION_EU_VAT_NUMBER_FIELD_REQUIRED_EU_ONLY) && $this->is_eu_country($customer_country)); 
  994.  
  995. return apply_filters('wc_aelia_euva_order_is_eu_vat_number_required', $result, $customer_country); 
  996.  
  997. /** 
  998. * Performs validations related to the EU VAT Number, to ensure that customer 
  999. * has entered all the information required to complete the checkout. 
  1000. * This method automatically adds a checkout error when needed. 
  1001. * @return void 
  1002. */ 
  1003. protected function validate_vat_exemption() { 
  1004. // Check if customer is VAT exempt and set him as such 
  1005. if(aelia_wc_version_is('>=', '2.7')) { 
  1006. $customer_country = $this->wc()->customer->get_billing_country(); 
  1007. else { 
  1008. $customer_country = $this->wc()->customer->get_country(); 
  1009.  
  1010. // If customer's country is empty on customer object, take it from the data 
  1011. // posted at checkout 
  1012. if(empty($customer_country)) { 
  1013. $this->log(__('Billing country on Customer object is empty. Retrieving ' . 
  1014. 'it from posted data.',  
  1015. self::$text_domain), false); 
  1016. $customer_country = get_value('billing_country', $_POST, ''); 
  1017.  
  1018. if(empty($customer_country)) { 
  1019. $this->log(sprintf(__('Unexpected condition occurred: no customer country was posted ' . 
  1020. 'during checkout. VAT exemption cannot be applied correctly. ' . 
  1021. 'Full posted data (JSON): "%s".',  
  1022. self::$text_domain),  
  1023. json_encode($_POST)), false); 
  1024.  
  1025. $vat_number = get_value(Definitions::ARG_VAT_NUMBER, $_POST, ''); 
  1026. // Check if customer is VAT exempt and set his exemption accordingly 
  1027. $exemption_check_result = $this->set_customer_vat_exemption($customer_country, $vat_number); 
  1028.  
  1029. // If VAT Number is required, but it was not entered or it's not valid,  
  1030. // display the appropriate error message and stop the checkout 
  1031. if($this->is_eu_vat_number_required($customer_country) && 
  1032. ($this->vat_number_validated != Definitions::VAT_NUMBER_VALIDATION_VALID)) { 
  1033. $error = sprintf(__('You must enter a valid EU VAT number to complete the purchase.', self::$text_domain), $vat_number); 
  1034. $this->add_woocommerce_error($error); 
  1035. return; 
  1036.  
  1037. // If VAT number is optional, check if customer may be allowed to complete 
  1038. // the purchase anyway 
  1039. if($exemption_check_result == Definitions::ERR_INVALID_EU_VAT_NUMBER && 
  1040. // If "store invalid VAT numbers" option is enabled, don't show any error 
  1041. // The VAT number will be recorded with the order, but the customer won't 
  1042. // be made exempt from VAT 
  1043. self::settings()->get(Settings::FIELD_STORE_INVALID_VAT_NUMBERS) == false) { 
  1044. // Show an error when VAT number validation fails at checkout 
  1045. $error = sprintf(__('VAT number "%s" is not valid for your country.', self::$text_domain), $vat_number); 
  1046. $this->add_woocommerce_error($error); 
  1047.  
  1048. /** 
  1049. * Performs VAT validation operations during checkout. 
  1050. * @param array posted_data The data posted from the order review page. 
  1051. */ 
  1052. public function woocommerce_checkout_process() { 
  1053. $this->validate_vat_exemption(); 
  1054. $this->validate_self_certification(); 
  1055.  
  1056. /** 
  1057. * Triggered when the Currency Switcher settings are updated. It updates 
  1058. * exchange rates automatically, so that they will include any new currency 
  1059. * that might have been added. 
  1060. */ 
  1061. public function wc_aelia_currencyswitcher_settings_saved() { 
  1062. self::settings()->scheduled_update_exchange_rates(); 
  1063.  
  1064. /** 
  1065. * Adds meta boxes to the admin interface. 
  1066. * @see add_meta_boxes(). 
  1067. */ 
  1068. public function add_meta_boxes() { 
  1069. add_meta_box('wc_aelia_eu_vat_assistant_order_vat_info_box',  
  1070. __('VAT information', self::$text_domain),  
  1071. array($this, 'render_order_vat_info_box'),  
  1072. 'shop_order',  
  1073. 'side',  
  1074. 'default'); 
  1075.  
  1076. /** 
  1077. * Renders the box with the VAT information associated to an order. 
  1078. * @param int order_id The ID of the target order. If empty, the ID of the 
  1079. * current post is taken. 
  1080. */ 
  1081. public function render_order_vat_info_box($order_id = null) { 
  1082. if(empty($order_id)) { 
  1083. global $post; 
  1084. $order_id = $post->ID; 
  1085.  
  1086. $order = new Order($order_id); 
  1087. include_once($this->path('views') . '/admin/order-vat-info-box.php'); 
  1088.  
  1089. /** 
  1090. * Handler for "wc_aelia_eu_vat_assistant_get_order_exchange_rate" hook. It 
  1091. * just acts as a wrapper for WC_Aelia_EU_VAT_Assistant::get_order_vat_exchange_rate() 
  1092. * method. 
  1093. * @param float default_rate The default exchange rate to return if the order 
  1094. * doesn't have any. 
  1095. * @param int order_id The ID of the order from which the exchange rate should 
  1096. * be retrieved. 
  1097. * @return float 
  1098. */ 
  1099. public function wc_aelia_eu_vat_assistant_get_order_exchange_rate($default_rate, $order_id) { 
  1100. $vat_exchange_rate = $this->get_order_vat_exchange_rate($order_id); 
  1101. if($vat_exchange_rate === false) { 
  1102. $vat_exchange_rate = $default_rate; 
  1103. return $vat_exchange_rate; 
  1104.  
  1105. /** 
  1106. * Retrieves the value of a plugin's setting. 
  1107. * @param mixed default_value The default value to return if the setting is 
  1108. * not found. 
  1109. * @param string setting_key The setting to retrieved. 
  1110. * @return mixed 
  1111. */ 
  1112. public function wc_aelia_eu_vat_assistant_get_setting($defaut_value, $setting_key) { 
  1113. return self::settings()->get($setting_key, $defaut_value); 
  1114.  
  1115. /** 
  1116. * Displays a notice when the plugin has not yet been configured. 
  1117. */ 
  1118. public function settings_notice() { 
  1119. ?> 
  1120. <div id="message" class="updated woocommerce-message"> 
  1121. <p><?php echo __('<strong>EU VAT Assistant</strong> is almost ready! Please go to ' . 
  1122. '<code>WooCommerce > EU VAT Assistant</code> settings page to complete the ' . 
  1123. 'configuration and start collecting the information required for ' . 
  1124. 'EU VAT compliance.', self::$text_domain); ?></p> 
  1125. <p class="submit"> 
  1126. <a href="<?php echo admin_url('admin.php?page=' . Definitions::MENU_SLUG); ?>" 
  1127. class="button-primary"><?php echo __('Go to EU VAT Assistant settings', self::$text_domain); ?></a> 
  1128. </p> 
  1129. </div> 
  1130. <?php 
  1131.  
  1132. /** 
  1133. * Adds elements to the billing address. 
  1134. * @param array address_parts The various parts of the address (name, company,  
  1135. * address, etc). 
  1136. * @param WC_Order order The order from which the address was taken. 
  1137. * @return array 
  1138. * @see \WC_Order::get_formatted_billing_address() 
  1139. */ 
  1140. public function woocommerce_order_formatted_billing_address($address_parts, $order) { 
  1141. $order = new Order(aelia_get_order_id($order)); 
  1142. $address_parts['vat_number'] = $order->get_customer_vat_number(); 
  1143. return $address_parts; 
  1144.  
  1145. /** 
  1146. * Adds tags that will be replaced with additional information on the address,  
  1147. * such as customers' VAT number. 
  1148. * @param array replacements The replacement tokens passed by WooCommerce. 
  1149. * @param array values The values from the billing address. 
  1150. * @return array 
  1151. * @see \WC_Countries::get_formatted_address() 
  1152. */ 
  1153. public function woocommerce_formatted_address_replacements($replacements, $values) { 
  1154. if(!empty($values['vat_number'])) { 
  1155. $replacements['{vat_number}'] = __('VAT #:', self::$text_domain) . ' ' . $values['vat_number']; 
  1156. else { 
  1157. $replacements['{vat_number}'] = ''; 
  1158. return $replacements; 
  1159.  
  1160. /** 
  1161. * Alters the address formats and adds new tokens, such as the VAT number. 
  1162. * @param array formats An array of address formats. 
  1163. * @return array 
  1164. * @see \WC_Countries::get_address_formats() 
  1165. */ 
  1166. public function woocommerce_localisation_address_formats($formats) { 
  1167. foreach($formats as $format_idx => $address_format) { 
  1168. $formats[$format_idx] .= "\n{vat_number}"; 
  1169. return $formats; 
  1170.  
  1171. /** 
  1172. * Overrides the value of the "woocommerce_allowed_countries" setting when 
  1173. * admin entered a list of countries to which sales are disallowed. 
  1174. * @param mixed value The original value of the parameter. 
  1175. * @return string 
  1176. */ 
  1177. public function pre_option_woocommerce_allowed_countries($value) { 
  1178. /** If we reach this point, it means that the Admin placed some restrictions 
  1179. * on the list of countries to which he wishes to sell. In such case, the 
  1180. * "allowed countries" option must be forced to "specific", so that the list 
  1181. * of available countries can be filtered. 
  1182. */ 
  1183. return 'specific'; 
  1184.  
  1185. /** 
  1186. * Filters the list of countries to which sale is allowed, taking into account 
  1187. * the ones explicitly disallowed by the administrator. 
  1188. * @param array countries A list of countries passed by WooCommerce. 
  1189. * @return array The list of countries, with the disallowed ones removed from 
  1190. * it. 
  1191. */ 
  1192. public function woocommerce_countries_allowed_countries($countries) { 
  1193. $allowed_sale_countries = $this->allowed_sale_countries; 
  1194. $disallowed_sale_countries = $this->get_sale_disallowed_countries(); 
  1195.  
  1196. if(!empty($disallowed_sale_countries)) { 
  1197. foreach($disallowed_sale_countries as $disallowed_country) { 
  1198. if(isset($allowed_sale_countries[$disallowed_country])) { 
  1199. unset($allowed_sale_countries[$disallowed_country]); 
  1200. // Debug 
  1201. //var_dump($allowed_sale_countries); 
  1202. return $allowed_sale_countries; 
  1203.  
  1204. /** 
  1205. * Ajax handler. Handles the request to collect and store the VAT data related 
  1206. * to an order. 
  1207. * @since 1.6.2.160210 
  1208. */ 
  1209. public function wp_ajax_collect_order_vat_info() { 
  1210. if(WC_Aelia_EU_VAT_Assistant::settings()->get(Settings::FIELD_COLLECT_VAT_DATA_FOR_MANUAL_ORDERS, false) && 
  1211. Orders_Integration::validate_ajax_request('edit_shop_orders')) { 
  1212. $order_id = $_REQUEST[Definitions::ARG_COLLECT_ORDER_ID]; 
  1213. $this->collect_order_vat_info($order_id); 
  1214.  
  1215. ob_start(); 
  1216. $this->render_order_vat_info_box($order_id); 
  1217. $vat_info_box_html = ob_get_contents(); 
  1218. @ob_end_clean(); 
  1219.  
  1220. wp_send_json(array( 
  1221. 'result' => Definitions::RES_OK,  
  1222. 'vat_info_box_html' => $vat_info_box_html,  
  1223. )); 
  1224.  
  1225. /** 
  1226. * Collects and saves the VAT data related to an order. 
  1227. * @param int order_id An order ID. 
  1228. * @since 1.6.2.160210 
  1229. */ 
  1230. protected function collect_order_vat_info($order_id) { 
  1231. $order = new Order($order_id); 
  1232.  
  1233. $this->vat_number = $order->get_meta('vat_number', ''); 
  1234. $this->vat_country = $order->get_billing_country(); 
  1235. $this->vat_number_validated = Definitions::VAT_NUMBER_ENTERED_MANUALLY_NOT_VALIDATED; 
  1236.  
  1237. $this->save_eu_vat_data($order_id);