AeliaWCEU_VAT_AssistantExchange_Rates_HMRC_Model

Retrieves the exchange rates from the HM Revenue and Customs (UK).

Defined (1)

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

/src/lib/classes/exchange_rates/aelia-wc-exchangerates-hmrc.php  
  1. class Exchange_Rates_HMRC_Model extends \Aelia\WC\ExchangeRatesModel { 
  2. /** 
  3. * URLs template to query HMRC. In January 2015, they apparently 
  4. * replaced all the dashes in the URL with underscores, without notice and 
  5. * without reason. 
  6. * @var string 
  7. */ 
  8. protected $hmrc_api_rates_urls = array( 
  9. // URL until the end of 2014 
  10. 'http://www.hmrc.gov.uk/softwaredevelopers/rates/exrates-monthly-%s.xml',  
  11. // URL introduced in February 2015 
  12. 'http://www.hmrc.gov.uk/softwaredevelopers/rates/excrates_monthly%s.xml',  
  13. // URL introduced in January 2015 
  14. 'http://www.hmrc.gov.uk/softwaredevelopers/rates/exrates_monthly_%s.xml',  
  15. ); 
  16.  
  17. /** 
  18. * Tranforms the exchange rates received from HMRC into an array of 
  19. * currency code => exchange rate pairs. 
  20. * @param string hmrc_rates The XML received from HMRC. 
  21. * @retur array 
  22. */ 
  23. protected function decode_rates($hmrc_rates) { 
  24. $exchange_rates = array(); 
  25.  
  26. foreach($hmrc_rates->exchangeRate as $rate) { 
  27. $exchange_rates[(string)$rate->currencyCode] = (float)$rate->rateNew; 
  28. // HMRC feed is based against GBP, but it doesn't contain such currency. We 
  29. // can safely add it manually, with an exchange rate of 1 
  30. $exchange_rates['GBP'] = 1; 
  31. return $exchange_rates; 
  32.  
  33. /** 
  34. * Fetches all exchange rates from HMRC API. 
  35. * @return object|bool An object containing the response from Open Exchange, or 
  36. * False in case of failure. 
  37. */ 
  38. private function fetch_all_rates() { 
  39. $errors = array( 
  40. self::ERR_ERROR_RETURNED => array(),  
  41. self::ERR_EXCEPTION_OCCURRED => array(),  
  42. ); 
  43. foreach($this->hmrc_api_rates_urls as $url_template) { 
  44. // The feed URL changes every month 
  45. $feed_url = sprintf($url_template, date('my')); 
  46. try { 
  47. $response = \Httpful\Request::get($feed_url) 
  48. ->expectsXml() 
  49. ->send(); 
  50.  
  51. // Debug 
  52. //var_dump("HMRC RATES RESPONSE:", $response); die(); 
  53. if($response->hasErrors()) { 
  54. // OpenExchangeRates sends error details in response body 
  55. if($response->hasBody()) { 
  56. $response_data = $response->body; 
  57.  
  58. $errors[self::ERR_ERROR_RETURNED][] = 
  59. sprintf(__('Error returned by HMRC. ' . 
  60. 'Feed URLs used for retrieval: %s. Error code: %s. Error message: %s - %s.',  
  61. Definitions::TEXT_DOMAIN),  
  62. '"' . implode('", "', $this->hmrc_api_rates_urls) . '"',  
  63. $response_data->status,  
  64. $response_data->message,  
  65. $response_data->description); 
  66. else { 
  67. // Return as soon as a good response is received 
  68. return $response->body; 
  69. catch(Exception $e) { 
  70. $errors[self::ERR_EXCEPTION_OCCURRED][] = 
  71. sprintf(__('Exception occurred while retrieving the exchange rates from HMRC. ' . 
  72. 'Feed URLs used for retrieval: %s. Error message: %s.',  
  73. Definitions::TEXT_DOMAIN),  
  74. '"' . implode('", "', $this->hmrc_api_rates_urls) . '"',  
  75. $e->getMessage()); 
  76. // If we reach this point, then it means that the model could not retrieve 
  77. // valid exchange rates from any of the URLs, so we can just add the errors 
  78. // and return "false". 
  79. foreach($errors as $error_type => $messages) { 
  80. foreach($messages as $error_message) { 
  81. $this->add_error($error_type, $error_message); 
  82. return false; 
  83.  
  84. /** 
  85. * Returns current exchange rates for the specified currency. 
  86. * @param string base_currency The base currency. 
  87. * @return array An array of Currency => Exchange Rate pairs. 
  88. */ 
  89. private function current_rates($base_currency) { 
  90. if(empty($this->_current_rates) || 
  91. $this->_base_currency != $base_currency) { 
  92.  
  93. $cache_key = md5(get_class($this)) . $base_currency; 
  94. // Try to get the cached rates for the specified base currency, if any 
  95. $this->_current_rates = $this->get_cached_exchange_rates($cache_key); 
  96. if(!empty($this->_current_rates)) { 
  97. return $this->_current_rates; 
  98.  
  99. // Fetch exchange rates 
  100. $hmrc_exchange_rates = $this->fetch_all_rates(); 
  101. if($hmrc_exchange_rates == false) { 
  102. return null; 
  103.  
  104. // Debug 
  105. //var_dump($hmrc_exchange_rates);die(); 
  106.  
  107. // HMRC rates are returned as JSON representation of an array of objects. 
  108. // We need to transform it into an array of currency => rate pairs 
  109. $exchange_rates = $this->decode_rates($hmrc_exchange_rates); 
  110.  
  111. // Debug 
  112. //var_dump($exchange_rates);die(); 
  113.  
  114. if(!is_array($exchange_rates)) { 
  115. $this->add_error(self::ERR_UNEXPECTED_ERROR_FETCHING_EXCHANGE_RATES,  
  116. __('An unexpected error occurred while fetching exchange rates ' . 
  117. 'from HMRC. The most common cause of this issue is the ' . 
  118. 'absence of PHP CURL extension. Please make sure that ' . 
  119. 'PHP CURL is installed and configured in your system.',  
  120. Definitions::TEXT_DOMAIN)); 
  121. return array(); 
  122.  
  123. // Since we didn't get the exchange rates related to the base currency,  
  124. // but in the default base currency used by OpenExchange, we need to 
  125. // recalculate them against the base currency we would like to use 
  126. $this->_current_rates = $this->rebase_rates($exchange_rates, $base_currency); 
  127. $this->_base_currency = $base_currency; 
  128.  
  129. // Cache the exchange rates 
  130. $this->cache_exchange_rates($cache_key, $this->_current_rates); 
  131. return $this->_current_rates; 
  132.  
  133. /** 
  134. * Recaculates the exchange rates using another base currency. This method 
  135. * is invoked because the rates fetched from HMRC are relative to BitCoin,  
  136. * but another currency is most likely is used by WooCommerce. 
  137. * @param array exchange_rates The exchange rates retrieved from HMRC. 
  138. * @param string base_currency The base currency against which the rates should 
  139. * be recalculated. 
  140. * @return array An array of currency => exchange rate pairs. 
  141. */ 
  142. private function rebase_rates(array $exchange_rates, $base_currency) { 
  143. $recalc_rate = get_value($base_currency, $exchange_rates); 
  144. //var_dump($base_currency, $exchange_rates); 
  145.  
  146. if(empty($recalc_rate)) { 
  147. $this->add_error(self::ERR_BASE_CURRENCY_NOT_FOUND,  
  148. sprintf(__('Could not rebase rates against base currency "%s". ' . 
  149. 'Currency not found in data returned by HMRC.',  
  150. Definitions::TEXT_DOMAIN),  
  151. $base_currency)); 
  152. return null; 
  153.  
  154. $result = array(); 
  155. foreach($exchange_rates as $currency => $rate) { 
  156. $result[$currency] = $rate / $recalc_rate; 
  157.  
  158. // Debug 
  159. //var_dump($result); die(); 
  160. return $result; 
  161.  
  162. /** 
  163. * Returns the exchange rate of a currency in respect to a base currency. 
  164. * @param string base_currency The code of the base currency. 
  165. * @param string currency The code of the currency for which to find the 
  166. * Exchange Rate. 
  167. * @return float 
  168. */ 
  169. protected function get_rate($base_currency, $currency) { 
  170. $current_rates = $this->current_rates($base_currency); 
  171. return get_value($currency, $current_rates); 
  172.  
  173. /** 
  174. * Class constructor. 
  175. * @param array An array of Settings that can be used to override the ones 
  176. * currently saved in the configuration. 
  177. * @return Exchange_Rates_HMRC_Model. 
  178. */ 
  179. public function __construct($settings = null) { 
  180. parent::__construct($settings);