PageRenderTime 63ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/avalaratax/avalaratax.php

https://github.com/alexgodev/PrestaShop-1.4
PHP | 1441 lines | 1208 code | 116 blank | 117 comment | 189 complexity | 4577acf7e151949396104ffc1a1d9796 MD5 | raw file
Possible License(s): MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * 2007-2011 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Academic Free License (AFL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/afl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2011 PrestaShop SA
  23. * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. // Security
  27. if (!defined('_PS_VERSION_'))
  28. exit;
  29. spl_autoload_register('avalaraAutoload');
  30. class AvalaraTax extends Module
  31. {
  32. /**
  33. * @brief Constructor
  34. */
  35. public function __construct()
  36. {
  37. $this->name = 'avalaratax';
  38. $this->tab = 'billing_invoicing';
  39. $this->version = '3.1.1';
  40. $this->author = 'PrestaShop';
  41. parent::__construct();
  42. $this->displayName = $this->l('Avalara - AvaTax');
  43. $this->description = $this->l('Sales Tax is complicated. AvaTax makes it easy.');
  44. /** Backward compatibility */
  45. require(_PS_MODULE_DIR_.$this->name.'/backward_compatibility/backward.php');
  46. }
  47. /**
  48. * @brief Installation method
  49. */
  50. public function install()
  51. {
  52. Configuration::updateValue('AVALARATAX_URL', 'https://avatax.avalara.net');
  53. Configuration::updateValue('AVALARATAX_ADDRESS_VALIDATION', 1);
  54. Configuration::updateValue('AVALARATAX_TAX_CALCULATION', 1);
  55. Configuration::updateValue('AVALARATAX_TIMEOUT', 300);
  56. // Value possible : Development / Production
  57. Configuration::updateValue('AVALARATAX_MODE', 'Production');
  58. Configuration::updateValue('AVALARATAX_ADDRESS_NORMALIZATION', 1);
  59. Configuration::updateValue('AVALARATAX_COMMIT_ID', 5);
  60. Configuration::updateValue('AVALARATAX_CANCEL_ID', 6);
  61. Configuration::updateValue('AVALARATAX_REFUND_ID', 7);
  62. Configuration::updateValue('AVALARATAX_POST_ID', 2);
  63. Configuration::updateValue('AVALARATAX_STATE', 1);
  64. Configuration::updateValue('AVALARATAX_COUNTRY', 0);
  65. Configuration::updateValue('AVALARA_CACHE_MAX_LIMIT', 1); /* The values in cache will be refreshed every 1 minute by default */
  66. // Make sure Avalara Tables don't exist before installation
  67. Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'avalara_product_cache`;');
  68. Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'avalara_carrier_cache`;');
  69. Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'avalara_returned_products`;');
  70. Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'avalara_temp`;');
  71. if (!Db::getInstance()->Execute('
  72. CREATE TABLE `'._DB_PREFIX_.'avalara_product_cache` (
  73. `id_cache` int(10) unsigned NOT NULL auto_increment,
  74. `id_product` int(10) unsigned NOT NULL,
  75. `tax_rate` float(8, 2) unsigned NOT NULL,
  76. `region` varchar(2) NOT NULL,
  77. `id_address` int(10) unsigned NOT NULL,
  78. `update_date` datetime,
  79. PRIMARY KEY (`id_cache`),
  80. UNIQUE (`id_product`, `region`))
  81. ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8') ||
  82. !Db::getInstance()->Execute('
  83. CREATE TABLE `'._DB_PREFIX_.'avalara_carrier_cache` (
  84. `id_cache` int(10) unsigned NOT NULL auto_increment,
  85. `id_carrier` int(10) unsigned NOT NULL,
  86. `tax_rate` float(8, 2) unsigned NOT NULL,
  87. `amount` float(8, 2) unsigned NOT NULL,
  88. `update_date` datetime,
  89. `id_cart` int(10) unsigned NOT NULL,
  90. `cart_hash` varchar(32) DEFAULT NULL,
  91. PRIMARY KEY (`id_cache`),
  92. UNIQUE (`id_cart`))
  93. ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8')||
  94. !Db::getInstance()->Execute('
  95. CREATE TABLE `'._DB_PREFIX_.'avalara_returned_products` (
  96. `id_returned_product` int(10) unsigned NOT NULL auto_increment,
  97. `id_order` int(10) unsigned NOT NULL,
  98. `id_product` int(10) unsigned NOT NULL,
  99. `total` float(8, 2) unsigned NOT NULL,
  100. `quantity` int(10) unsigned NOT NULL,
  101. `name` varchar(255) NOT NULL,
  102. `description_short` varchar(255) NULL,
  103. `tax_code` varchar(255) NULL,
  104. PRIMARY KEY (`id_returned_product`))
  105. ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8')||
  106. !Db::getInstance()->Execute('
  107. CREATE TABLE `'._DB_PREFIX_.'avalara_temp` (
  108. `id_order` int(10) unsigned NOT NULL,
  109. `id_order_detail` int(10) unsigned NOT NULL)
  110. ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8')||
  111. !Db::getInstance()->Execute('
  112. CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'avalara_taxcodes` (
  113. `id_taxcode` int(10) unsigned NOT NULL auto_increment,
  114. `id_product` int(10) unsigned NOT NULL,
  115. `tax_code` varchar(30) NOT NULL,
  116. `taxable` int(2) unsigned NOT NULL DEFAULT 1,
  117. PRIMARY KEY (`id_taxcode`),
  118. UNIQUE (`id_product`))
  119. ENGINE='._MYSQL_ENGINE_.' DEFAULT CHARSET=utf8'))
  120. return false;
  121. if (!parent::install() || !$this->registerHook('leftColumn') || !$this->registerHook('updateOrderStatus') ||
  122. !$this->registerHook('cancelProduct') || !$this->registerHook('adminOrder') || !$this->registerHook('backOfficeTop') ||
  123. !$this->registerHook('header') || !$this->overrideFiles())
  124. return false;
  125. return true;
  126. }
  127. public function uninstall()
  128. {
  129. if (!$this->removeOverrideFiles() || !parent::uninstall() ||
  130. !Configuration::deleteByName('AVALARATAX_URL') ||
  131. !Configuration::deleteByName('AVALARATAX_ADDRESS_VALIDATION') ||
  132. !Configuration::deleteByName('AVALARATAX_TAX_CALCULATION') ||
  133. !Configuration::deleteByName('AVALARATAX_TIMEOUT') ||
  134. !Configuration::deleteByName('AVALARATAX_MODE') ||
  135. !Configuration::deleteByName('AVALARATAX_ACCOUNT_NUMBER') ||
  136. !Configuration::deleteByName('AVALARATAX_COMPANY_CODE') ||
  137. !Configuration::deleteByName('AVALARATAX_LICENSE_KEY') ||
  138. !Configuration::deleteByName('AVALARATAX_ADDRESS_NORMALIZATION') ||
  139. !Configuration::deleteByName('AVALARATAX_ADDRESS_LINE1') ||
  140. !Configuration::deleteByName('AVALARATAX_ADDRESS_LINE2') ||
  141. !Configuration::deleteByName('AVALARATAX_CITY') ||
  142. !Configuration::deleteByName('AVALARATAX_STATE') ||
  143. !Configuration::deleteByName('AVALARATAX_ZIP_CODE') ||
  144. !Configuration::deleteByName('AVALARATAX_COUNTRY') ||
  145. !Configuration::deleteByName('AVALARATAX_COMMIT_ID') ||
  146. !Configuration::deleteByName('AVALARATAX_CANCEL_ID') ||
  147. !Configuration::deleteByName('AVALARATAX_REFUND_ID') ||
  148. !Configuration::deleteByName('AVALARA_CACHE_MAX_LIMIT') ||
  149. !Configuration::deleteByName('AVALARATAX_POST_ID') ||
  150. !Db::getInstance()->Execute('DROP TABLE `'._DB_PREFIX_.'avalara_product_cache`') ||
  151. !Db::getInstance()->Execute('DROP TABLE `'._DB_PREFIX_.'avalara_carrier_cache`') ||
  152. !Db::getInstance()->Execute('DROP TABLE `'._DB_PREFIX_.'avalara_returned_products`') ||
  153. !Db::getInstance()->Execute('DROP TABLE `'._DB_PREFIX_.'avalara_temp`'))
  154. return false;
  155. // Do not remove taxcode table
  156. return true;
  157. }
  158. /**
  159. * @brief Describe the override schema
  160. */
  161. protected static function getOverrideInfo()
  162. {
  163. return array(
  164. 'Tax.php' => array(
  165. 'source' => 'override-14x/classes/Tax.php',
  166. 'dest' => 'override/classes/Tax.php',
  167. 'md5' => array(
  168. '1.1' => '5d9e318d673bfa723b02f14f952e0a7a',
  169. '2.3' => '86c900cd6fff286aa6a52df2ff72228a',
  170. '3.0.2' => 'c558c0b15877980134e301af34e42c3e',
  171. '3.1.1' => '38acdbc8b4d57d7e5110d589a78796bc',
  172. )
  173. ),
  174. 'Cart.php' => array(
  175. 'source' => 'override/classes/Cart.php',
  176. 'dest' => 'override/classes/Cart.php',
  177. 'md5' => array(
  178. '3.0.2' => 'e4b05425b6dc61f75aad434265f3cac8',
  179. '3.0.3' => 'f7388cb50fbfd300c9f81cc407b7be83',
  180. )
  181. ),
  182. 'AddressController.php' => array(
  183. 'source' => 'override/controllers/front/AddressController.php',
  184. 'dest' => 'override/controllers/AddressController.php',
  185. 'md5' => array(
  186. '1.1' => 'ebc4f31298395c4b113c7e2d7cc41b4a',
  187. '3.0.2' => 'ff3d9cb2956c35f4229d5277cb2e92e6',
  188. )
  189. ),
  190. 'AuthController.php' => array(
  191. 'source' => 'override/controllers/front/AuthController.php',
  192. 'dest' => 'override/controllers/AuthController.php',
  193. 'md5' => array(
  194. '1.1' => '7304d7af971b30f2dcd401b80bbdf805',
  195. '3.0.2' => '3eb86260a7c8d6cfa1d209fb3e8f8bd6',
  196. )
  197. ),
  198. );
  199. }
  200. protected function removeOverrideFiles()
  201. {
  202. /** In v1.5, we do not remove override files */
  203. if (version_compare(_PS_VERSION_, '1.5', '<'))
  204. foreach (self::getOverrideInfo() as $key => $params)
  205. {
  206. if (!file_exists(_PS_ROOT_DIR_.'/'.$params['dest']))
  207. continue;
  208. $md5 = md5_file(_PS_ROOT_DIR_.'/'.$params['dest']);
  209. $removed = false;
  210. foreach ($params['md5'] as $hash)
  211. if ($md5 == $hash)
  212. {
  213. if (unlink(_PS_ROOT_DIR_.'/'.$params['dest']))
  214. $removed = true;
  215. break;
  216. }
  217. if (!$removed)
  218. $this->_errors[] = $this->l('Error while removing override: ').$key;
  219. }
  220. return !isset($this->_errors) || !$this->_errors || !count($this->_errors);
  221. }
  222. protected function overrideFiles()
  223. {
  224. /** In v1.5, we do not copy the override files */
  225. if (version_compare(_PS_VERSION_, '1.5', '<') && $this->removeOverrideFiles())
  226. {
  227. /** Check if the override directories exists */
  228. if (!is_dir(_PS_ROOT_DIR_.'/override/classes/'))
  229. mkdir(_PS_ROOT_DIR_.'/override/classes/', 0777, true);
  230. if (!is_dir(_PS_ROOT_DIR_.'/override/controllers/'))
  231. mkdir(_PS_ROOT_DIR_.'/override/controllers/', 0777, true);
  232. foreach (self::getOverrideInfo() as $key => $params)
  233. if (file_exists(_PS_ROOT_DIR_.'/'.$params['dest']))
  234. $this->_errors[] = $this->l('This override file already exists, please merge it manually: ').$key;
  235. elseif (!copy(_PS_MODULE_DIR_.'avalaratax/'.$params['source'], _PS_ROOT_DIR_.'/'.$params['dest']))
  236. $this->_erroors[] = $this->l('Error while copying the override file: ').$key;
  237. }
  238. return !isset($this->_errors) || !$this->_errors || !count($this->_errors);
  239. }
  240. /******************************************************************/
  241. /** Hook Methods **************************************************/
  242. /******************************************************************/
  243. public function hookAdminOrder($params)
  244. {
  245. $this->purgeTempTable();
  246. }
  247. public function hookCancelProduct($params)
  248. {
  249. if (isset($_POST['cancelProduct']))
  250. {
  251. $order = new Order((int)$_POST['id_order']);
  252. if (!Validate::isLoadedObject($order))
  253. return false;
  254. if ($order->invoice_number)
  255. {
  256. // Get all the cancel product's IDs
  257. $cancelledIdsOrderDetail = array();
  258. foreach ($_POST['cancelQuantity'] as $idOrderDetail => $qty)
  259. if ($qty > 0)
  260. $cancelledIdsOrderDetail[] = (int)$idOrderDetail;
  261. $cancelledIdsOrderDetail = implode(', ', $cancelledIdsOrderDetail);
  262. // Fill temp table
  263. Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'avalara_temp (`id_order`, `id_order_detail`)
  264. VALUES ('.(int)$_POST['id_order'].', '.(int)$params['id_order_detail'].')');
  265. // Check if we are at the end of the loop
  266. $totalLoop = Db::getInstance()->ExecuteS('SELECT COUNT(`id_order`) as totalLines
  267. FROM `'._DB_PREFIX_.'avalara_temp`
  268. WHERE `id_order_detail` IN ('.pSQL($cancelledIdsOrderDetail).')');
  269. if ($totalLoop[0]['totalLines'] != count(array_filter($_POST['cancelQuantity'])))
  270. return false;
  271. // Clean the temp table because we are at the end of the loop
  272. $this->purgeTempTable();
  273. // Get details for cancelledIdsOrderDetail (Grab the info to post to Avalara in English.)
  274. $cancelledProdIdsDetails = Db::getInstance()->ExecuteS('SELECT od.`product_id` as id_product, od.`id_order_detail`, pl.`name`,
  275. pl.`description_short`, od.`product_price` as price, od.`reduction_percent`,
  276. od.`reduction_amount`, od.`product_quantity` as quantity, atc.`tax_code`
  277. FROM '._DB_PREFIX_.'order_detail od
  278. LEFT JOIN '._DB_PREFIX_.'product p ON (p.id_product = od.product_id)
  279. LEFT JOIN '._DB_PREFIX_.'product_lang pl ON (pl.id_product = p.id_product)
  280. LEFT JOIN '._DB_PREFIX_.'avalara_taxcodes atc ON (atc.id_product = p.id_product)
  281. WHERE pl.`id_lang` = 1 AND od.`id_order` = '.(int)$_POST['id_order'].'
  282. AND od.`id_order_detail` IN ('.pSQL($cancelledIdsOrderDetail).')');
  283. // Build the product list
  284. $products = array();
  285. foreach ($cancelledProdIdsDetails as $cancelProd)
  286. $products[] = array('id_product' => (int)$cancelProd['id_product'],
  287. 'quantity' => (int)$_POST['cancelQuantity'][$cancelProd['id_order_detail']],
  288. 'total' => pSQL($_POST['cancelQuantity'][$cancelProd['id_order_detail']] * ($cancelProd['price'] - ($cancelProd['price'] * ($cancelProd['reduction_percent'] / 100)) - $cancelProd['reduction_amount'])), // Including those product with discounts
  289. 'name' => pSQL(Tools::safeOutput($cancelProd['name'])),
  290. 'description_short' => pSQL(Tools::safeOutput($cancelProd['description_short']), true),
  291. 'tax_code' => pSQL(Tools::safeOutput($cancelProd['tax_code'])));
  292. // Send to Avalara
  293. $commitResult = $this->getTax($products, array('type' => 'ReturnInvoice', 'DocCode' => (int)$_POST['id_order']));
  294. if ($commitResult['ResultCode'] == 'Warning' || $commitResult['ResultCode'] == 'Error' || $commitResult['ResultCode'] == 'Exception')
  295. echo $this->_displayConfirmation($this->l('The following error was generated while cancelling the orders you selected. <br /> - '.
  296. Tools::safeOutput($commitResult['Messages']['Summary'])), 'error');
  297. else
  298. {
  299. $this->commitToAvalara(array('id_order' => (int)$_POST['id_order']));
  300. echo $this->_displayConfirmation($this->l('The products you selected were cancelled.'));
  301. }
  302. }
  303. }
  304. }
  305. protected function getDestinationAddress($id_order)
  306. {
  307. $order = new Order((int)$id_order);
  308. if (!Validate::isLoadedObject($order))
  309. return false;
  310. $address = new Address((int)$order->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
  311. if (!Validate::isLoadedObject($address))
  312. return false;
  313. $state = null;
  314. if (!empty($address->id_state))
  315. {
  316. $state = new State((int)$address->id_state);
  317. if (!Validate::isLoadedObject($state))
  318. return false;
  319. }
  320. return array($address, $state, $order);
  321. }
  322. public function hookUpdateOrderStatus($params)
  323. {
  324. list($params['address'], $params['state'], $params['order']) = self::getDestinationAddress((int)$params['id_order']);
  325. if ($params['newOrderStatus']->id == (int)Configuration::get('AVALARATAX_COMMIT_ID'))
  326. return $this->commitToAvalara($params);
  327. elseif ($params['newOrderStatus']->id == (int)Configuration::get('AVALARATAX_CANCEL_ID'))
  328. {
  329. $params['CancelCode'] = 'V';
  330. $this->cancelFromAvalara($params);
  331. return $this->cancelFromAvalara($params);
  332. }
  333. elseif ($params['newOrderStatus']->id == (int)Configuration::get('AVALARATAX_POST_ID'))
  334. return $this->postToAvalara($params);
  335. elseif ($params['newOrderStatus']->id == (int)Configuration::get('AVALARATAX_REFUND_ID'))
  336. return $this->commitToAvalara($params);
  337. return false;
  338. }
  339. public function hookBackOfficeTop()
  340. {
  341. if (Tools::isSubmit('submitAddproduct') || Tools::isSubmit('submitAddproductAndStay'))
  342. Db::getInstance()->Execute('REPLACE INTO `'._DB_PREFIX_.'avalara_taxcodes` (`id_product`, `tax_code`)
  343. VALUES ('.(isset($_GET['id_product']) ? (int)$_GET['id_product'] : 0).', \''.pSQL(Tools::safeOutput($_POST['tax_code'])).'\')');
  344. if ((isset($_GET['updateproduct']) || isset($_GET['addproduct'])) && isset($_GET['id_product']) && (int)$_GET['id_product'])
  345. {
  346. $r = Db::getInstance()->getRow('SELECT `tax_code`
  347. FROM `'._DB_PREFIX_.'avalara_taxcodes` atc
  348. WHERE atc.`id_product` = '.(int)Tools::getValue('id_product'));
  349. // JS for 1.4
  350. if (version_compare(_PS_VERSION_, '1.5', '<'))
  351. return '<script type="text/javascript">
  352. $(function() {
  353. // Add the Tax Code field
  354. $(\'<tr><td class="col-left">'.$this->l('Tax Code (Avalara)').':</td><td style="padding-bottom:5px;"><input type="text" style="width: 130px; margin-right: 5px;" value="'.
  355. ($r ? Tools::safeOutput($r['tax_code']) : '').'" name="tax_code" maxlength="13" size="55"></td></tr>\').appendTo(\'#product #step1 table:eq(0) tbody\');
  356. // override original tax rules
  357. $(\'span #id_tax_rules_group\').parent().html(\'Avalara\');
  358. });
  359. </script>';
  360. // JS for 1.5
  361. return '<script type="text/javascript">
  362. $(function() {
  363. var done = false;
  364. // Add the Tax Code field
  365. $(\'#link-Informations\').click(function() {
  366. if (done == false) {
  367. done = true;
  368. $(\'<tr><td class="col-left"><label for="tax_code">'.$this->l('Tax Code:').'</label></td><td style="padding-bottom:5px;"><input type="text" style="width: 130px; margin-right: 5px;" value="'.
  369. ($r ? Tools::safeOutput($r['tax_code']) : '').'" name="tax_code" maxlength="13" size="55"> <span class="small">(Avalara)</span></td></tr>\').appendTo(\'#step1 table:first tbody\');
  370. }
  371. });
  372. // override original tax rules
  373. $(\'#link-Prices\').click(function() {
  374. $(\'span #id_tax_rules_group\').parent().html(\'Avalara\');
  375. });
  376. });
  377. </script>';
  378. }
  379. elseif ((Tools::isSubmit('updatecarrier') || Tools::isSubmit('addcarrier')) && Tools::getValue('id_carrier'))
  380. return '<script type="text/javascript">
  381. $(function() {
  382. // override original tax rules
  383. $(\'div #id_tax_rules_group\').parent().html(\'<label class="t">Avalara</label>\');
  384. });
  385. </script>';
  386. if (Tools::getValue('tab') == 'AdminTaxes' || Tools::getValue('tab') == 'AdminTaxRulesGroup' || Tools::getValue('controller') == 'admintaxes' || Tools::getValue('controller') == 'admintaxrulesgroup')
  387. {
  388. // JS for 1.5
  389. if (version_compare(_PS_VERSION_, '1.5', '>'))
  390. return '<script type="text/javascript">
  391. $(function() {
  392. $(\'#content form\').hide();
  393. $(\'#desc-tax-new\').hide();
  394. $(\'#desc-tax-save\').hide();
  395. $(\'#content div:first\').append(\'<div class="warn">'.$this->l('Tax rules are overwritten by Avalara Tax Module.').'</div>\');
  396. });
  397. </script>';
  398. // JS for 1.4
  399. return '<script type="text/javascript">
  400. $(function() {
  401. if ($(\'#Taxes\').size() || $(\'#submitFiltertax_rules_group\').size())
  402. $(\'#content\').prepend(\'<div class="warn"><img src="../img/admin/warn2.png">'.
  403. $this->l('Tax rules are overwritten by Avalara Tax Module.').'</div>\');
  404. });
  405. </script>';
  406. }
  407. return '';
  408. }
  409. public function hookHeader()
  410. {
  411. if (!$this->context->cart || ((int)$this->context->cart->id_customer && !(int)$this->context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}))
  412. $id_address = (int)(Db::getInstance()->getValue('SELECT `id_address` FROM `'._DB_PREFIX_.'address` WHERE `id_customer` = '.(int)($this->context->cart->id_customer).' AND `deleted` = 0 ORDER BY `id_address`'));
  413. else
  414. $id_address = $this->context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
  415. if (!(int)$id_address)
  416. return ;
  417. return '<script type="text/javascript">
  418. function refresh_taxes(count)
  419. {
  420. $.ajax({
  421. type : \'POST\',
  422. url : \''.$this->_path.'\' + \'ajax.php\',
  423. data : {
  424. \'id_cart\': "'.(int)$this->context->cart->id.'",
  425. \'id_lang\': "'.(int)$this->context->cookie->id_lang.'",
  426. \'id_address\': "'.(int)$id_address.'",
  427. \'ajax\': "getProductTaxRate",
  428. \'token\': "'.md5(_COOKIE_KEY_.Configuration::get('PS_SHOP_NAME')).'",
  429. },
  430. dataType: \'json\',
  431. success : function(d) {
  432. if (d.hasError == false && d.cached_tax == true)
  433. $(\'#total_tax\').html($(\'body\').data(\'total_tax\'));
  434. else if (d.hasError == false && d.cached_tax == false)
  435. $(\'#total_tax\').html(d.total_tax);
  436. else
  437. $(\'#total_tax\').html(\''.$this->l('Error while calculating taxes').'\');
  438. }
  439. });
  440. }
  441. $(function() {
  442. /* ajax call to cache taxes product taxes that exist in the current cart */
  443. $(\'body\').data(\'total_tax\', $(\'#total_tax\').text());
  444. $(\'#total_tax\').html(\'<img src="img/loader.gif" alt="" />\');
  445. refresh_taxes(1);
  446. });
  447. </script>';
  448. }
  449. /******************************************************************/
  450. /** Main Form Methods *********************************************/
  451. /******************************************************************/
  452. public function getContent()
  453. {
  454. $buffer = '';
  455. if (version_compare(_PS_VERSION_,'1.5','>'))
  456. $this->context->controller->addJQueryPlugin('fancybox');
  457. else
  458. $buffer .= '<script type="text/javascript" src="'.__PS_BASE_URI__.'js/jquery/jquery.fancybox-1.3.4.js"></script>
  459. <link type="text/css" rel="stylesheet" href="'.__PS_BASE_URI__.'css/jquery.fancybox-1.3.4.css" />';
  460. if (Tools::isSubmit('SubmitAvalaraTaxSettings'))
  461. {
  462. Configuration::updateValue('AVALARATAX_ACCOUNT_NUMBER', Tools::getValue('avalaratax_account_number'));
  463. Configuration::updateValue('AVALARATAX_LICENSE_KEY', Tools::getValue('avalaratax_license_key'));
  464. Configuration::updateValue('AVALARATAX_URL', Tools::getValue('avalaratax_url'));
  465. Configuration::updateValue('AVALARATAX_COMPANY_CODE', Tools::getValue('avalaratax_company_code'));
  466. $buffer .= $this->_displayConfirmation();
  467. }
  468. elseif (Tools::isSubmit('SubmitAvalaraTaxOptions'))
  469. {
  470. Configuration::updateValue('AVALARATAX_ADDRESS_VALIDATION', Tools::getValue('avalaratax_address_validation'));
  471. Configuration::updateValue('AVALARATAX_TAX_CALCULATION', Tools::getValue('avalaratax_tax_calculation'));
  472. Configuration::updateValue('AVALARATAX_TIMEOUT', (int)Tools::getValue('avalaratax_timeout'));
  473. Configuration::updateValue('AVALARATAX_ADDRESS_NORMALIZATION', Tools::getValue('avalaratax_address_normalization'));
  474. Configuration::updateValue('AVALARATAX_TAX_OUTSIDE', Tools::getValue('avalaratax_tax_outside'));
  475. Configuration::updateValue('AVALARA_CACHE_MAX_LIMIT', Tools::getValue('avalara_cache_max_limit') < 1 ? 1 : Tools::getValue('avalara_cache_max_limit') > 23 ? 23 : Tools::getValue('avalara_cache_max_limit'));
  476. $buffer .= $this->_displayConfirmation();
  477. }
  478. elseif (Tools::isSubmit('SubmitAvalaraTestConnection'))
  479. $connectionTestResult = $this->_testConnection();
  480. elseif (Tools::isSubmit('SubmitAvalaraAddressOptions'))
  481. {
  482. /* Validate address*/
  483. $address = new Address();
  484. $address->address1 = Tools::getValue('avalaratax_address_line1');
  485. $address->address2 = Tools::getValue('avalaratax_address_line2');
  486. $address->city = Tools::getValue('avalaratax_city');
  487. $address->id_state = State::getIdByIso(Tools::getValue('avalaratax_state'));
  488. $address->id_country = Tools::getValue('avalaratax_country');
  489. $address->postcode = Tools::getValue('avalaratax_zip_code');
  490. $normalizedAddress = $this->validateAddress($address);
  491. if (isset($normalizedAddress['ResultCode']) && $normalizedAddress['ResultCode'] == 'Success')
  492. {
  493. $buffer .= $this->_displayConfirmation($this->l('The address you submitted has been validated.'));
  494. Configuration::updateValue('AVALARATAX_ADDRESS_LINE1', $normalizedAddress['Normalized']['Line1']);
  495. Configuration::updateValue('AVALARATAX_ADDRESS_LINE2', $normalizedAddress['Normalized']['Line2']);
  496. Configuration::updateValue('AVALARATAX_CITY', $normalizedAddress['Normalized']['City']);
  497. Configuration::updateValue('AVALARATAX_STATE', $normalizedAddress['Normalized']['Region']);
  498. Configuration::updateValue('AVALARATAX_COUNTRY', $normalizedAddress['Normalized']['Country']);
  499. Configuration::updateValue('AVALARATAX_ZIP_CODE', $normalizedAddress['Normalized']['PostalCode']);
  500. }
  501. else
  502. {
  503. $message = $this->l('The following error was generated while validating your address:');
  504. if (isset($normalizedAddress['Exception']['FaultString']))
  505. $message .= '<br /> - '.Tools::safeOutput($normalizedAddress['Exception']['FaultString']);
  506. if (isset($normalizedAddress['Messages']['Summary']))
  507. foreach ($normalizedAddress['Messages']['Summary'] as $summary)
  508. $message .= '<br /> - '.Tools::safeOutput($summary);
  509. $buffer .= $this->_displayConfirmation($message, 'error');
  510. Configuration::updateValue('AVALARATAX_ADDRESS_LINE1', Tools::getValue('avalaratax_address_line1'));
  511. Configuration::updateValue('AVALARATAX_ADDRESS_LINE2', Tools::getValue('avalaratax_address_line2'));
  512. Configuration::updateValue('AVALARATAX_CITY', Tools::getValue('avalaratax_city'));
  513. Configuration::updateValue('AVALARATAX_STATE', Tools::getValue('avalaratax_state'));
  514. Configuration::updateValue('AVALARATAX_ZIP_CODE', Tools::getValue('avalaratax_zip_code'));
  515. }
  516. }
  517. elseif (Tools::isSubmit('SubmitAvalaraTaxClearCache'))
  518. {
  519. Db::getInstance()->Execute('TRUNCATE TABLE `'._DB_PREFIX_.'avalara_product_cache`');
  520. Db::getInstance()->Execute('TRUNCATE TABLE `'._DB_PREFIX_.'avalara_carrier_cache`');
  521. $buffer .= $this->_displayConfirmation('Cache cleared!');
  522. }
  523. $confValues = Configuration::getMultiple(array(
  524. // Configuration
  525. 'AVALARATAX_ACCOUNT_NUMBER', 'AVALARATAX_LICENSE_KEY', 'AVALARATAX_URL', 'AVALARATAX_COMPANY_CODE',
  526. // Options
  527. 'AVALARATAX_ADDRESS_VALIDATION', 'AVALARATAX_TAX_CALCULATION', 'AVALARATAX_TIMEOUT',
  528. 'AVALARATAX_ADDRESS_NORMALIZATION', 'AVALARATAX_TAX_OUTSIDE', 'AVALARATAX_COMMIT_ID', 'AVALARATAX_CANCEL_ID',
  529. 'AVALARATAX_REFUND_ID', 'AVALARATAX_POST_ID', 'AVALARA_CACHE_MAX_LIMIT',
  530. // Default Address
  531. 'AVALARATAX_ADDRESS_LINE1', 'AVALARATAX_ADDRESS_LINE2', 'AVALARATAX_CITY', 'AVALARATAX_STATE',
  532. 'AVALARATAX_ZIP_CODE', 'AVALARATAX_COUNTRY'));
  533. $stateList = array();
  534. $stateList[] = array('id' => '0', 'name' => $this->l('Choose your state (if applicable)'), 'iso_code' => '--');
  535. foreach (State::getStates((int)$this->context->cookie->id_lang) as $state)
  536. $stateList[] = array('id' => $state['id_state'], 'name' => $state['name'], 'iso_code' => $state['iso_code']);
  537. $countryList = array();
  538. $countryList[] = array('id' => '0', 'name' => $this->l('Choose your country'), 'iso_code' => '--');
  539. foreach (Country::getCountries((int)$this->context->cookie->id_lang, false, null, false) as $country)
  540. $countryList[] = array('id' => $country['id_country'], 'name' => $country['name'], 'iso_code' => $country['iso_code']);
  541. $buffer .= '<link href="'.$this->_path.'css/avalara.css" rel="stylesheet" type="text/css">
  542. <script type="text/javascript">
  543. /* Fancybox */
  544. $(\'a.avalara-video-btn\').live(\'click\', function(){
  545. $.fancybox({
  546. \'type\' : \'iframe\',
  547. \'href\' : this.href.replace(new RegExp("watch\\?v=", "i"), \'embed\') + \'?rel=0&autoplay=1\',
  548. \'swf\': {\'allowfullscreen\':\'true\', \'wmode\':\'transparent\'},
  549. \'overlayShow\' : true,
  550. \'centerOnScroll\' : true,
  551. \'speedIn\' : 100,
  552. \'speedOut\' : 50,
  553. \'width\' : 853,
  554. \'height\' : 480
  555. });
  556. return false;
  557. });
  558. </script>
  559. <script type="text/javascript">
  560. $(document).ready(function(){
  561. var height1 = 0;
  562. var height = 0;
  563. $(\'.field-height1\').each(function(){
  564. if (height1 < $(this).height())
  565. height1 = $(this).height();
  566. });
  567. $(\'.field-height\').each(function(){
  568. if (height < $(this).height())
  569. height = $(this).height();
  570. });
  571. $(\'.field-height1\').css({\'height\' : $(\'.field-height1\').css(\'height\', height1+\'px\')});
  572. $(\'.field-height\').css({\'height\' : $(\'.field-height\').css(\'height\', height+\'px\')});
  573. updateAvalaraTaxState($(\'#avalaratax_country\').val());
  574. $(\'#avalaratax_country\').change(function(){
  575. updateAvalaraTaxState($(this).val());
  576. });
  577. });
  578. function updateAvalaraTaxState(iso_code)
  579. {
  580. var default_state = "'.$confValues['AVALARATAX_STATE'].'";
  581. $(\'#avalaratax_state\').html(\'\');
  582. $.ajax({
  583. type : \'GET\',
  584. url : \'../modules/avalaratax/states.php?country_iso_code=\'+iso_code,
  585. dataType: \'JSON\',
  586. success: function(data)
  587. {
  588. if (data != 0)
  589. {
  590. $.each(data[iso_code], function(i, item){
  591. if (default_state == item.state_iso_code)
  592. $(\'#avalaratax_state\').append(\'<option selected="selected" value="\'+item.state_iso_code+\'">\'+item.name+\'</option>\');
  593. else
  594. $(\'#avalaratax_state\').append(\'<option value="\'+item.state_iso_code+\'">\'+item.name+\'</option>\');
  595. $(\'#avalaratax_state\').show();
  596. $(\'#avalaratax_label_state\').show();
  597. });
  598. }
  599. else
  600. {
  601. $(\'#avalaratax_state\').hide();
  602. $(\'#avalaratax_label_state\').hide();
  603. }
  604. }
  605. });
  606. }
  607. </script>
  608. <div class="avalara-wrap">
  609. <p class="avalara-intro"><a href="http://www.avalara.com/e-commerce/prestashop" class="avalara-logo" target="_blank"><img src="'.$this->_path.'img/avalara_logo.png" alt="Avalara" border="0" /></a><a href="http://www.avalara.com/e-commerce/prestashop" class="avalara-link" target="_blank">'.$this->l('Create an account').'</a>'.$this->l('Avalara and PrestaShop have partnered to provide the easiest way for you to accurately calculate and fill sales tax.').'</p>
  610. <div class="clear"></div>
  611. <div class="avalara-content">
  612. <div class="avalara-video">
  613. <h3>'.$this->l('No one likes dealing with sales tax.').'</h3>
  614. <p>'.$this->l('Sales tax isn\'t core to your business and should be automated. You may be doing it wrong, exposing your business to unnecessary audit risks, and don\'t even know it.').'</p>
  615. <a href="http://www.youtube.com/embed/tm1tENVdcQ8" class="avalara-video-btn"><img src="'.$this->_path.'img/avalara-video-screen.jpg" alt="Avalara Video" /><img src="'.$this->_path.'img/btn-video.png" alt="" class="video-icon" /></a>
  616. </div>
  617. <h3>'.$this->l('Doing sales tax right is simple with Avalara.').'</h3>
  618. <p>'.$this->l('We do all of the research and automate the process for you, ensuring that the system is up-to-date with the most recent sales tax and VAT rates and rules in every state and country, so you dont have to. As a cloud-based service, AvaTax eliminates ongoing maintenance and support. It provides you with a complete solution to manage your sales tax needs.').'</p>
  619. <img src="'.$this->_path.'img/avatax_badge.png" alt="AvaTax Certified" class="avatax-badge" />
  620. <ul>
  621. <li>'.$this->l('Address Validation included').'</li>
  622. <li>'.$this->l('Rooftop Accurate Calculations').'</li>
  623. <li>'.$this->l('Product and Service Taxability Rules').'</li>
  624. <li>'.$this->l('Exemption Certificate Management').'</li>
  625. <li>'.$this->l('Out-of-the-Box Sales Tax Reporting').'</li>
  626. </ul>
  627. <a href="http://www.avalara.com/e-commerce/prestashop" class="avalara-link" target="_blank">'.$this->l('Create an account').'</a>
  628. </div>
  629. <fieldset class="field-height1 right-fieldset">
  630. <legend><img src="'.$this->_path.'img/icon-console.gif" alt="" />'.$this->l('AvaTax Admin Console').'</legend>
  631. <p><a href="https://admin-avatax.avalara.net/" target="_blank">'.$this->l('Log-in to AvaTax Admin Console').'</a></p>
  632. <a href="https://admin-avatax.avalara.net/" target="_blank"><img src="'.$this->_path.'img/avatax-logo.png" alt="AvaTax" class="avatax-logo" /></a>
  633. </fieldset>
  634. <form action="'.Tools::safeOutput($_SERVER['REQUEST_URI']).'" method="post" class="left-form">
  635. <fieldset class="field-height1">
  636. <legend><img src="'.$this->_path.'img/icon-config.gif" alt="" />'.$this->l('Configuration').'</legend>
  637. <h4>'.$this->l('AvaTax Credentials').'</h4>';
  638. if (isset($connectionTestResult))
  639. $buffer .= '<div id="test_connection" style="background: '.Tools::safeOutput($connectionTestResult[1]).';">'.$connectionTestResult[0].'</div>';
  640. $buffer .= '<label>'.$this->l('Account Number').'</label>
  641. <div class="margin-form">
  642. <input type="text" name="avalaratax_account_number" value="'.(isset($confValues['AVALARATAX_ACCOUNT_NUMBER']) ? Tools::safeOutput($confValues['AVALARATAX_ACCOUNT_NUMBER']) : '').'" />
  643. </div>
  644. <label>'.$this->l('License Key').'</label>
  645. <div class="margin-form">
  646. <input type="text" name="avalaratax_license_key" value="'.(isset($confValues['AVALARATAX_LICENSE_KEY']) ? Tools::safeOutput($confValues['AVALARATAX_LICENSE_KEY']) : '').'" />
  647. </div>
  648. <label>'.$this->l('URL').'</label>
  649. <div class="margin-form">
  650. <input type="text" name="avalaratax_url" value="'.(isset($confValues['AVALARATAX_URL']) ? Tools::safeOutput($confValues['AVALARATAX_URL']) : '').'" />
  651. </div>
  652. <label>'.$this->l('Company Code').'</label>
  653. <div class="margin-form">
  654. <input type="text" name="avalaratax_company_code" value="'.(isset($confValues['AVALARATAX_COMPANY_CODE']) ? Tools::safeOutput($confValues['AVALARATAX_COMPANY_CODE']) : '').'" /> '.$this->l('Located in the top-right corner of your AvaTax Admin Console').'
  655. </div>
  656. <div class="margin-form">
  657. <input type="submit" class="button" name="SubmitAvalaraTaxSettings" value="'.$this->l('Save Settings').'" /><img src="'.$this->_path.'img/icon-connection.gif" alt="" class="icon-connection" /><input type="submit" id="avalaratax_test_connection" class="button" name="SubmitAvalaraTestConnection" value="'.$this->l('Click here to Test Connection').'" />
  658. </div>
  659. </fieldset>
  660. </form>
  661. <form action="'.Tools::safeOutput($_SERVER['REQUEST_URI']).'" method="post" class="form-half reset-label">
  662. <fieldset class="field-height MR7">
  663. <legend><img src="'.$this->_path.'img/icon-options.gif" alt="" />'.$this->l('Options').'</legend>
  664. <label>'.$this->l('Enable address validation').'</label>
  665. <div class="margin-form">
  666. <input type="checkbox" name="avalaratax_address_validation" value="1"'.(isset($confValues['AVALARATAX_ADDRESS_VALIDATION']) && $confValues['AVALARATAX_ADDRESS_VALIDATION'] ? ' checked="checked"' : '').' />
  667. </div>
  668. <label>'.$this->l('Enable tax calculation').'</label>
  669. <div class="margin-form">
  670. <input type="checkbox" name="avalaratax_tax_calculation" value="1" '.(isset($confValues['AVALARATAX_TAX_CALCULATION']) && $confValues['AVALARATAX_TAX_CALCULATION'] ? ' checked="checked"' : '').' />
  671. </div>
  672. <label>'.$this->l('Enable address normalization in uppercase').'</label>
  673. <div class="margin-form">
  674. <input type="checkbox" name="avalaratax_address_normalization" value="1" '.(isset($confValues['AVALARATAX_ADDRESS_NORMALIZATION']) && $confValues['AVALARATAX_ADDRESS_NORMALIZATION'] ? ' checked="checked"' : '').' />
  675. </div>
  676. <label>'.$this->l('Enable tax calculation outside of your state').'</label>
  677. <div class="margin-form">
  678. <input type="checkbox" name="avalaratax_tax_outside" value="1" '.(isset($confValues['AVALARATAX_TAX_OUTSIDE']) && $confValues['AVALARATAX_TAX_OUTSIDE'] ? ' checked="checked"' : '').' />
  679. </div>
  680. <label>'.$this->l('Request timeout').'</label>
  681. <div class="margin-form">
  682. <input type="text" name="avalaratax_timeout" value="'.(isset($confValues['AVALARATAX_TIMEOUT']) ? Tools::safeOutput($confValues['AVALARATAX_TIMEOUT']) : '').'" style="width: 40px;" /> '.$this->l('seconds').'
  683. </div>
  684. <div style="display: none;"
  685. <label>'.$this->l('Refresh tax rate cache every: ').'</label>
  686. <div class="margin-form">
  687. <input type="text" name="avalara_cache_max_limit" value="'.(isset($confValues['AVALARA_CACHE_MAX_LIMIT']) ? Tools::safeOutput($confValues['AVALARA_CACHE_MAX_LIMIT']) : '').'" style="width: 40px;" /> '.$this->l('minutes').'
  688. </div>
  689. </div>
  690. <div class="margin-form">
  691. <input type="submit" class="button avalaratax_button" name="SubmitAvalaraTaxOptions" value="'.$this->l('Save Settings').'" />
  692. <input type="submit" class="button avalaratax_button" name="SubmitAvalaraTaxClearCache" value="'.$this->l('Clear Cache').'" style="display: none;"/>
  693. </div>
  694. <div class="sep"></div>
  695. <h4>'.$this->l('Default Post/Commit/Cancel/Refund Options').'</h4>
  696. <span class="avalara-info">'.$this->l('When an order\'s status is updated, the following options will be used to update Avalara\'s records.').'</span>';
  697. // Check if the order status exist
  698. $orderStatusList = array();
  699. foreach (Db::getInstance()->ExecuteS('SELECT `id_order_state`, `name` FROM `'._DB_PREFIX_.'order_state_lang` WHERE `id_lang` = '.(int)$this->context->cookie->id_lang) as $v)
  700. $orderStatusList[$v['id_order_state']] = Tools::safeOutput($v['name']);
  701. $buffer .= '<table class="avalara-table" cellspacing="0" cellpadding="0" width="100%">
  702. <th>'.$this->l('Action').'</th>
  703. <th>'.$this->l('Order status in your store').'</th>
  704. <tr>
  705. <td class="avalaratax_column">'.$this->l('Post order to Avalara').':</td>
  706. <td>'.(isset($orderStatusList[Configuration::get('AVALARATAX_POST_ID')]) ? html_entity_decode(Tools::safeOutput($orderStatusList[Configuration::get('AVALARATAX_POST_ID')])) :
  707. '<div style="color: red">'.$this->l('[ERROR] A default value was not found. Please, restore PrestaShop\'s default statuses.').'</div>').'
  708. </td>
  709. </tr>
  710. <tr>
  711. <td class="avalaratax_column">'.$this->l('Commit order to Avalara').':</td>
  712. <td>'.(isset($orderStatusList[Configuration::get('AVALARATAX_COMMIT_ID')]) ? html_entity_decode(Tools::safeOutput($orderStatusList[Configuration::get('AVALARATAX_COMMIT_ID')])) :
  713. '<div style="color: red">'.$this->l('[ERROR] A default value was not found. Please, restore PrestaShop\'s default statuses.').'</div>').'
  714. </td>
  715. </tr>
  716. <tr>
  717. <td class="avalaratax_column">'.$this->l('Delete order from Avalara').':</td>
  718. <td>'.(isset($orderStatusList[Configuration::get('AVALARATAX_CANCEL_ID')]) ? html_entity_decode(Tools::safeOutput($orderStatusList[Configuration::get('AVALARATAX_CANCEL_ID')])) :
  719. '<div style="color: red">'.$this->l('[ERROR] A default value was not found. Please, restore PrestaShop\'s default statuses.').'</div>').'
  720. </td>
  721. </tr>
  722. <tr>
  723. <td class="avalaratax_column last">'.$this->l('Void order in Avalara').':</td>
  724. <td class="last">'.(isset($orderStatusList[Configuration::get('AVALARATAX_REFUND_ID')]) ? html_entity_decode(Tools::safeOutput($orderStatusList[Configuration::get('AVALARATAX_REFUND_ID')])) :
  725. '<div style="color: red">'.$this->l('[ERROR] A default value was not found. Please, restore PrestaShop\'s default statuses.').'</div>').'
  726. </td>
  727. </tr>
  728. </table>
  729. </fieldset>
  730. </form>
  731. <form action="'.Tools::safeOutput($_SERVER['REQUEST_URI']).'" method="post" class="form-half">
  732. <fieldset class="field-height ML7">
  733. <legend><img src="'.$this->_path.'img/icon-address.gif" alt="" />'.$this->l('Default Origin Address').'</legend>
  734. <label>'.$this->l('Address Line 1').'</label>
  735. <div class="margin-form">
  736. <input type="text" name="avalaratax_address_line1" value="'.(isset($confValues['AVALARATAX_ADDRESS_LINE1']) ? Tools::safeOutput($confValues['AVALARATAX_ADDRESS_LINE1']) : '').'" />
  737. </div>
  738. <label>'.$this->l('Address Line 2').'</label>
  739. <div class="margin-form">
  740. <input type="text" name="avalaratax_address_line2" value="'.(isset($confValues['AVALARATAX_ADDRESS_LINE2']) ? Tools::safeOutput($confValues['AVALARATAX_ADDRESS_LINE2']) : '').'" />
  741. </div>
  742. <label>'.$this->l('City').'</label>
  743. <div class="margin-form">
  744. <input type="text" name="avalaratax_city" value="'.(isset($confValues['AVALARATAX_CITY']) ? Tools::safeOutput($confValues['AVALARATAX_CITY']) : '').'" />
  745. </div>
  746. <label>'.$this->l('Zip Code').'</label>
  747. <div class="margin-form">
  748. <input type="text" name="avalaratax_zip_code" value="'.(isset($confValues['AVALARATAX_ZIP_CODE']) ? Tools::safeOutput($confValues['AVALARATAX_ZIP_CODE']) : '').'" />
  749. </div>
  750. <label>'.$this->l('Country').'</label>
  751. <div class="margin-form">
  752. <select name="avalaratax_country" id="avalaratax_country">';
  753. foreach ($countryList as $country)
  754. $buffer .= '<option value="'.substr(strtoupper($country['iso_code']), 0, 2).'" '.($country['iso_code'] == $confValues['AVALARATAX_COUNTRY'] ? ' selected="selected"' : '').'>'.Tools::safeOutput($country['name']).'</option>';
  755. $buffer .= '</select>
  756. </div>
  757. <label id="avalaratax_label_state" >'.$this->l('State').'</label>
  758. <div class="margin-form">
  759. <select name="avalaratax_state" id="avalaratax_state">';
  760. foreach ($stateList as $state)
  761. $buffer .= '<option value="'.substr(strtoupper($state['iso_code']), 0, 2).'" '.($state['iso_code'] == $confValues['AVALARATAX_STATE'] ? ' selected="selected"' : '').'>'.Tools::safeOutput($state['name']).'</option>';
  762. return $buffer .'</select>
  763. </div>
  764. <div class="margin-form">
  765. <input type="submit" class="button" name="SubmitAvalaraAddressOptions" value="'.$this->l('Save Settings').'" />
  766. </div>
  767. </fieldset>
  768. </form>
  769. <div class="clear"></div>
  770. </div>';
  771. }
  772. /*
  773. ** Display a custom message for settings update
  774. ** $text string Text to be displayed in the message
  775. ** $type string (confirm|warn|error) Decides what color will the message have (green|yellow)
  776. */
  777. private function _displayConfirmation($text = '', $type = 'confirm')
  778. {
  779. if ($type == 'confirm')
  780. $img = 'ok.gif';
  781. elseif ($type == 'warn')
  782. $img = 'warn2.png';
  783. elseif ($type == 'error')
  784. $img = 'disabled.gif';
  785. else
  786. die('Invalid type.');
  787. return '<div class="conf '.Tools::safeOutput($type).'">
  788. <img src="../img/admin/'.$img.'" alt="" title="" />
  789. '.(empty($text) ? $this->l('Settings updated') : $text).
  790. '<img src="http://www.prestashop.com/modules/avalaratax.png?sid='.urlencode(Configuration::get('AVALARATAX_ACCOUNT_NUMBER')).'" style="float: right;" />
  791. </div>';
  792. }
  793. /**
  794. * @brief init the Avatax SDK
  795. */
  796. private function _connectToAvalara()
  797. {
  798. $timeout = Configuration::get('AVALARATAX_TIMEOUT');
  799. if ((int)$timeout > 0)
  800. ini_set('max_execution_time', (int)$timeout);
  801. include_once(dirname(__FILE__).'/sdk/AvaTax.php');
  802. /* Just instanciate the ATConfig class to init the settings (mandatory...)*/
  803. new ATConfig(Configuration::get('AVALARATAX_MODE'), array('url' => Configuration::get('AVALARATAX_URL'), 'account' => Configuration::get('AVALARATAX_ACCOUNT_NUMBER'),
  804. 'license' => Configuration::get('AVALARATAX_LICENSE_KEY'), 'trace' => false));
  805. }
  806. /**
  807. * @brief Connect to Avalara to make sure everything is OK
  808. */
  809. private function _testConnection()
  810. {
  811. $this->_connectToAvalara();
  812. try
  813. {
  814. $client = new TaxServiceSoap(Configuration::get('AVALARATAX_MODE'));
  815. $connectionTest = $client->ping();
  816. if ($connectionTest->getResultCode() == SeverityLevel::$Success)
  817. {
  818. try
  819. {
  820. $authorizedTest = $client->isAuthorized('GetTax');
  821. if ($authorizedTest->getResultCode() == SeverityLevel::$Success)
  822. $expirationDate = $authorizedTest->getexpires();
  823. }
  824. catch (SoapFault $exception)
  825. {
  826. }
  827. return array('<img src="../img/admin/ok.gif" alt="" /><strong style="color: green;">'.$this->l('Connection Test performed successfully.').'</strong><br /><br />'.$this->l('Ping version is:').' '.Tools::safeOutput($connectionTest->getVersion()).(isset($expirationDate) ? '<br /><br />'.$this->l('License Expiration Date:').' '.Tools::safeOutput($expirationDate) : ''), '#D6F5D6');
  828. }
  829. }
  830. catch (SoapFault $exception)
  831. {
  832. return array('<img src="../img/admin/forbbiden.gif" alt="" /><b style="color: #CC0000;">'.$this->l('Connection Test Failed.').'</b><br /><br />'.$this->l('Either the Account or License Key is incorrect. Please confirm the Account and License Key before testing the connection again.').'<br /><br /><strong style="color: #CC0000;">'.$this->l('Error(s):').' '.Tools::safeOutput($exception->faultstring).'</strong>', '#FFD8D8');
  833. }
  834. }
  835. /**
  836. * @brief Validates a given address
  837. */
  838. public function validateAddress(Address $address)
  839. {
  840. $this->_connectToAvalara();
  841. $client = new AddressServiceSoap(Configuration::get('AVALARATAX_MODE'));
  842. if (!empty($address->id_state))
  843. $state = new State((int)$address->id_state);
  844. if (!empty($address->id_country))
  845. $country = new Country((int)$address->id_country);
  846. $avalaraAddress = new AvalaraAddress($address->address1, $address->address2, null, $address->city,
  847. (isset($state) ? $state->iso_code : null), $address->postcode, (isset($country) ? $country->iso_code : null), 0);
  848. $buffer = array();
  849. try
  850. {
  851. $request = new ValidateRequest($avalaraAddress, TextCase::$Upper, false);
  852. $result = $client->Validate($request);
  853. $addresses = $result->ValidAddresses;
  854. $buffer['ResultCode'] = Tools::safeOutput($result->getResultCode());
  855. if ($result->getResultCode() != SeverityLevel::$Success)
  856. foreach ($result->getMessages() as $msg)
  857. {
  858. $buffer['Messages']['Name'][] = Tools::safeOutput($msg->getName());
  859. $buffer['Messages']['Summary'][] = Tools::safeOutput($msg->getSummary());
  860. }
  861. else
  862. foreach ($result->getvalidAddresses() as $valid)
  863. {
  864. $buffer['Normalized']['Line1'] = Tools::safeOutput($valid->getline1());
  865. $buffer['Normalized']['Line2'] = Tools::safeOutput($valid->getline2());
  866. $buffer['Normalized']['City']= Tools::safeOutput($valid->getcity());
  867. $buffer['Normalized']['Region'] = Tools::safeOutput($valid->getregion());
  868. $buffer['Normalized']['PostalCode'] = Tools::safeOutput($valid->getpostalCode());
  869. $buffer['Normalized']['Country'] = Tools::safeOutput($valid->getcountry());
  870. $buffer['Normalized']['County'] = Tools::safeOutput($valid->getcounty());
  871. $buffer['Normalized']['FIPS'] = Tools::safeOutput($valid->getfipsCode());
  872. $buffer['Normalized']['PostNet'] = Tools::safeOutput($valid->getpostNet());
  873. $buffer['Normalized']['CarrierRoute'] = Tools::safeOutput($valid->getcarrierRoute());
  874. $buffer['Normalized']['AddressType'] = Tools::safeOutput($valid->getaddressType());
  875. }
  876. }
  877. catch (SoapFault $exception)
  878. {
  879. $buffer['Exception']['FaultString'] = Tools::safeOutput($exception->faultstring);
  880. $buffer['Exception']['LastRequest'] = Tools::safeOutput($client->__getLastRequest());
  881. $buffer['Exception']['LastResponse'] = Tools::safeOutput($client->__getLastResponse());
  882. }
  883. return $buffer;
  884. }
  885. /**
  886. * @brief Executes tax actions on documents
  887. *
  888. * @param Array $products Array of Product for which taxes need to be calculated
  889. * @param Array $params
  890. * type : (default SalesOrder) SalesOrder|SalesInvoice|ReturnInvoice
  891. * cart : (required for SalesOrder and SalesInvoice) Cart object
  892. * DocCode : (required in ReturnInvoice, and when 'cart' is not set) Specify the Document Code
  893. *
  894. */
  895. public function getTax($products = array(), $params = array())
  896. {
  897. $confValues = Configuration::getMultiple(array('AVALARATAX_COMPANY_CODE', 'AVALARATAX_ADDRESS_LINE1',
  898. 'AVALARATAX_ADDRESS_LINE2', 'AVALARATAX_CITY', 'AVALARATAX_STATE', 'AVALARATAX_ZIP_CODE'));
  899. if (!isset($params['type']))
  900. $params['type'] = 'SalesOrder';
  901. $this->_connectToAvalara();
  902. $client = new TaxServiceSoap(Configuration::get('AVALARATAX_MODE'));
  903. $request = new GetTaxRequest();
  904. if (isset($params['cart']) && (int)$params['cart']->{Configuration::get('PS_TAX_ADDRESS_TYPE')})
  905. $address = new Address((int)$params['cart']->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
  906. elseif (isset($this->context->cookie) && isset($this->contract->cookie->id_customer) && $this->context->cookie->id_customer)
  907. $address = new Address((int)Db::getInstance()->getValue('SELECT `id_address` FROM `'._DB_PREFIX_.'address` WHERE `id_customer` = '.(int)$this->context->cookie->id_customer.' AND active = 1 AND deleted = 0'));
  908. if (isset($address))
  909. {
  910. if (!empty($address->id_state))
  911. $state = new State((int)$address->id_state);
  912. $addressDest = array();
  913. $addressDest['Line1'] = $address->address1;
  914. $addressDest['Line2'] = $address->address2;
  915. $addressDest['City'] = $address->city;
  916. $addressDest['Region'] = isset($state) ? $state->iso_code : '';
  917. $addressDest['PostalCode'] = $address->postcode;
  918. $addressDest['Country'] = Country::getIsoById($address->id_country);
  919. // Try to normalize the address depending on option in the BO
  920. if (Configuration::get('AVALARATAX_ADDRESS_NORMALIZATION'))
  921. $normalizedAddress = $this->validateAddress($address);
  922. if (isset($normalizedAddress['Normalized']))
  923. $addressDest = $normalizedAddress['Normalized'];
  924. // Add Destination address (Customer address)
  925. $destination = new AvalaraAddress();
  926. $destination->setLine1($addressDest['Line1']);
  927. $destination->setLine2($addressDest['Line2']);
  928. $destination->setCity($addressDest['City']);
  929. $destination->setRegion($addressDest['Region']);
  930. $destination->setPostalCode($addressDest['PostalCode']);
  931. $destination->setCountry($addressDest['Country']);
  932. $request->setDestinationAddress($destination);
  933. }
  934. // Origin Address (Store Address or address setup in BO)
  935. $origin = new AvalaraAddress();
  936. $origin->setLine1(isset($confValues['AVALARATAX_ADDRESS_LINE1']) ? $confValues['AVALARATAX_ADDRESS_LINE1'] : '');
  937. $origin->setLine2(isset($confValues['AVALARATAX_ADDRESS_LINE2']) ? $confValues['AVALARATAX_ADDRESS_LINE2'] : '');
  938. $origin->setCity(isset($confValues['AVALARATAX_CITY']) ? $confValues['AVALARATAX_CITY'] : '');
  939. $origin->setRegion(isset($confValues['AVALARATAX_STATE']) ? $confValues['AVALARATAX_STATE'] : '');
  940. $origin->setPostalCode(isset($confValues['AVALARATAX_ZIP_CODE']) ? $confValues['AVALARATAX_ZIP_CODE'] : '');
  941. $request->setOriginAddress($origin);
  942. $request->setCompanyCode(isset($confValues['AVALARATAX_COMPANY_CODE']) ? $confValues['AVALARATAX_COMPANY_CODE'] : '');
  943. $orderId = isset($

Large files files are truncated, but you can click here to view the full file