/classes/PaymentModule.php

https://github.com/OnjanirinaPro/PrestaShop · PHP · 975 lines · 697 code · 125 blank · 153 comment · 147 complexity · c8b092d6108da0458d7bea404730e2d3 MD5 · raw file

  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 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/osl-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-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. abstract class PaymentModuleCore extends Module
  27. {
  28. /** @var integer Current order's id */
  29. public $currentOrder;
  30. public $currencies = true;
  31. public $currencies_mode = 'checkbox';
  32. const DEBUG_MODE = false;
  33. public function install()
  34. {
  35. if (!parent::install())
  36. return false;
  37. // Insert currencies availability
  38. if ($this->currencies_mode == 'checkbox')
  39. {
  40. if (!$this->addCheckboxCurrencyRestrictionsForModule())
  41. return false;
  42. }
  43. elseif ($this->currencies_mode == 'radio')
  44. {
  45. if (!$this->addRadioCurrencyRestrictionsForModule())
  46. return false;
  47. }
  48. else
  49. Tools::displayError('No currency mode for payment module');
  50. // Insert countries availability
  51. $return = $this->addCheckboxCountryRestrictionsForModule();
  52. if (!Configuration::get('CONF_'.strtoupper($this->name).'_FIXED'))
  53. Configuration::updateValue('CONF_'.strtoupper($this->name).'_FIXED', '0.2');
  54. if (!Configuration::get('CONF_'.strtoupper($this->name).'_VAR'))
  55. Configuration::updateValue('CONF_'.strtoupper($this->name).'_VAR', '2');
  56. if (!Configuration::get('CONF_'.strtoupper($this->name).'_FIXED_FOREIGN'))
  57. Configuration::updateValue('CONF_'.strtoupper($this->name).'_FIXED_FOREIGN', '0.2');
  58. if (!Configuration::get('CONF_'.strtoupper($this->name).'_VAR_FOREIGN'))
  59. Configuration::updateValue('CONF_'.strtoupper($this->name).'_VAR_FOREIGN', '2');
  60. return $return;
  61. }
  62. public function uninstall()
  63. {
  64. if (!Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_country` WHERE id_module = '.(int)$this->id)
  65. || !Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_currency` WHERE id_module = '.(int)$this->id)
  66. || !Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'module_group` WHERE id_module = '.(int)$this->id))
  67. return false;
  68. return parent::uninstall();
  69. }
  70. /**
  71. * Add checkbox currency restrictions for a new module
  72. * @param array $shops
  73. *
  74. * @return bool
  75. */
  76. public function addCheckboxCurrencyRestrictionsForModule(array $shops = array())
  77. {
  78. if (!$shops)
  79. $shops = Shop::getShops(true, null, true);
  80. foreach ($shops as $s)
  81. {
  82. if (!Db::getInstance()->execute('
  83. INSERT INTO `'._DB_PREFIX_.'module_currency` (`id_module`, `id_shop`, `id_currency`)
  84. SELECT '.(int)$this->id.', "'.(int)$s.'", `id_currency` FROM `'._DB_PREFIX_.'currency` WHERE deleted = 0'))
  85. return false;
  86. }
  87. return true;
  88. }
  89. /**
  90. * Add radio currency restrictions for a new module
  91. * @param array $shops
  92. *
  93. * @return bool
  94. */
  95. public function addRadioCurrencyRestrictionsForModule(array $shops = array())
  96. {
  97. if (!$shops)
  98. $shops = Shop::getShops(true, null, true);
  99. foreach ($shops as $s)
  100. if (!Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'module_currency` (`id_module`, `id_shop`, `id_currency`)
  101. VALUES ('.(int)$this->id.', "'.(int)$s.'", -2)'))
  102. return false;
  103. return true;
  104. }
  105. /**
  106. * Add checkbox country restrictions for a new module
  107. * @param array $shops
  108. *
  109. * @return bool
  110. */
  111. public function addCheckboxCountryRestrictionsForModule(array $shops = array())
  112. {
  113. $countries = Country::getCountries((int)Context::getContext()->language->id, true); //get only active country
  114. $country_ids = array();
  115. foreach ($countries as $country)
  116. $country_ids[] = $country['id_country'];
  117. return Country::addModuleRestrictions($shops, $countries, array(array('id_module' => (int)$this->id)));
  118. }
  119. /**
  120. * Validate an order in database
  121. * Function called from a payment module
  122. *
  123. * @param integer $id_cart
  124. * @param integer $id_order_state
  125. * @param float $amount_paid Amount really paid by customer (in the default currency)
  126. * @param string $payment_method Payment method (eg. 'Credit card')
  127. * @param null $message Message to attach to order
  128. * @param array $extra_vars
  129. * @param null $currency_special
  130. * @param bool $dont_touch_amount
  131. * @param bool $secure_key
  132. * @param Shop $shop
  133. *
  134. * @return bool
  135. * @throws PrestaShopException
  136. */
  137. public function validateOrder($id_cart, $id_order_state, $amount_paid, $payment_method = 'Unknown',
  138. $message = null, $extra_vars = array(), $currency_special = null, $dont_touch_amount = false,
  139. $secure_key = false, Shop $shop = null)
  140. {
  141. if (self::DEBUG_MODE)
  142. PrestaShopLogger::addLog('PaymentModule::validateOrder - Function called', 1, null, 'Cart', (int)$id_cart, true);
  143. $this->context->cart = new Cart($id_cart);
  144. $this->context->customer = new Customer($this->context->cart->id_customer);
  145. $this->context->language = new Language($this->context->cart->id_lang);
  146. $this->context->shop = ($shop ? $shop : new Shop($this->context->cart->id_shop));
  147. ShopUrl::resetMainDomainCache();
  148. $id_currency = $currency_special ? (int)$currency_special : (int)$this->context->cart->id_currency;
  149. $this->context->currency = new Currency($id_currency, null, $this->context->shop->id);
  150. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_delivery')
  151. $context_country = $this->context->country;
  152. $order_status = new OrderState((int)$id_order_state, (int)$this->context->language->id);
  153. if (!Validate::isLoadedObject($order_status))
  154. {
  155. PrestaShopLogger::addLog('PaymentModule::validateOrder - Order Status cannot be loaded', 3, null, 'Cart', (int)$id_cart, true);
  156. throw new PrestaShopException('Can\'t load Order status');
  157. }
  158. if (!$this->active)
  159. {
  160. PrestaShopLogger::addLog('PaymentModule::validateOrder - Module is not active', 3, null, 'Cart', (int)$id_cart, true);
  161. die(Tools::displayError());
  162. }
  163. // Does order already exists ?
  164. if (Validate::isLoadedObject($this->context->cart) && $this->context->cart->OrderExists() == false)
  165. {
  166. if ($secure_key !== false && $secure_key != $this->context->cart->secure_key)
  167. {
  168. PrestaShopLogger::addLog('PaymentModule::validateOrder - Secure key does not match', 3, null, 'Cart', (int)$id_cart, true);
  169. die(Tools::displayError());
  170. }
  171. // For each package, generate an order
  172. $delivery_option_list = $this->context->cart->getDeliveryOptionList();
  173. $package_list = $this->context->cart->getPackageList();
  174. $cart_delivery_option = $this->context->cart->getDeliveryOption();
  175. // If some delivery options are not defined, or not valid, use the first valid option
  176. foreach ($delivery_option_list as $id_address => $package)
  177. if (!isset($cart_delivery_option[$id_address]) || !array_key_exists($cart_delivery_option[$id_address], $package))
  178. foreach ($package as $key => $val)
  179. {
  180. $cart_delivery_option[$id_address] = $key;
  181. break;
  182. }
  183. $order_list = array();
  184. $order_detail_list = array();
  185. do
  186. $reference = Order::generateReference();
  187. while (Order::getByReference($reference)->count());
  188. $this->currentOrderReference = $reference;
  189. $order_creation_failed = false;
  190. $cart_total_paid = (float)Tools::ps_round((float)$this->context->cart->getOrderTotal(true, Cart::BOTH), 2);
  191. foreach ($cart_delivery_option as $id_address => $key_carriers)
  192. foreach ($delivery_option_list[$id_address][$key_carriers]['carrier_list'] as $id_carrier => $data)
  193. foreach ($data['package_list'] as $id_package)
  194. {
  195. // Rewrite the id_warehouse
  196. $package_list[$id_address][$id_package]['id_warehouse'] = (int)$this->context->cart->getPackageIdWarehouse($package_list[$id_address][$id_package], (int)$id_carrier);
  197. $package_list[$id_address][$id_package]['id_carrier'] = $id_carrier;
  198. }
  199. // Make sure CarRule caches are empty
  200. CartRule::cleanCache();
  201. $cart_rules = $this->context->cart->getCartRules();
  202. foreach ($cart_rules as $cart_rule)
  203. {
  204. if (($rule = new CartRule((int)$cart_rule['obj']->id)) && Validate::isLoadedObject($rule))
  205. {
  206. if ($error = $rule->checkValidity($this->context, true, true))
  207. {
  208. $this->context->cart->removeCartRule((int)$rule->id);
  209. if (isset($this->context->cookie) && isset($this->context->cookie->id_customer) && $this->context->cookie->id_customer && !empty($rule->code))
  210. {
  211. if (Configuration::get('PS_ORDER_PROCESS_TYPE') == 1)
  212. Tools::redirect('index.php?controller=order-opc&submitAddDiscount=1&discount_name='.urlencode($rule->code));
  213. Tools::redirect('index.php?controller=order&submitAddDiscount=1&discount_name='.urlencode($rule->code));
  214. }
  215. else
  216. {
  217. $rule_name = isset($rule->name[(int)$this->context->cart->id_lang]) ? $rule->name[(int)$this->context->cart->id_lang] : $rule->code;
  218. $error = Tools::displayError(sprintf('CartRule ID %1s (%2s) used in this cart is not valid and has been withdrawn from cart', (int)$rule->id, $rule_name));
  219. PrestaShopLogger::addLog($error, 3, '0000002', 'Cart', (int)$this->context->cart->id);
  220. }
  221. }
  222. }
  223. }
  224. foreach ($package_list as $id_address => $packageByAddress)
  225. foreach ($packageByAddress as $id_package => $package)
  226. {
  227. $order = new Order();
  228. $order->product_list = $package['product_list'];
  229. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_delivery')
  230. {
  231. $address = new Address($id_address);
  232. $this->context->country = new Country($address->id_country, $this->context->cart->id_lang);
  233. if (!$this->context->country->active)
  234. throw new PrestaShopException('The delivery address country is not active.');
  235. }
  236. $carrier = null;
  237. if (!$this->context->cart->isVirtualCart() && isset($package['id_carrier']))
  238. {
  239. $carrier = new Carrier($package['id_carrier'], $this->context->cart->id_lang);
  240. $order->id_carrier = (int)$carrier->id;
  241. $id_carrier = (int)$carrier->id;
  242. }
  243. else
  244. {
  245. $order->id_carrier = 0;
  246. $id_carrier = 0;
  247. }
  248. $order->id_customer = (int)$this->context->cart->id_customer;
  249. $order->id_address_invoice = (int)$this->context->cart->id_address_invoice;
  250. $order->id_address_delivery = (int)$id_address;
  251. $order->id_currency = $this->context->currency->id;
  252. $order->id_lang = (int)$this->context->cart->id_lang;
  253. $order->id_cart = (int)$this->context->cart->id;
  254. $order->reference = $reference;
  255. $order->id_shop = (int)$this->context->shop->id;
  256. $order->id_shop_group = (int)$this->context->shop->id_shop_group;
  257. $order->secure_key = ($secure_key ? pSQL($secure_key) : pSQL($this->context->customer->secure_key));
  258. $order->payment = $payment_method;
  259. if (isset($this->name))
  260. $order->module = $this->name;
  261. $order->recyclable = $this->context->cart->recyclable;
  262. $order->gift = (int)$this->context->cart->gift;
  263. $order->gift_message = $this->context->cart->gift_message;
  264. $order->mobile_theme = $this->context->cart->mobile_theme;
  265. $order->conversion_rate = $this->context->currency->conversion_rate;
  266. $amount_paid = !$dont_touch_amount ? Tools::ps_round((float)$amount_paid, 2) : $amount_paid;
  267. $order->total_paid_real = 0;
  268. $order->total_products = (float)$this->context->cart->getOrderTotal(false, Cart::ONLY_PRODUCTS, $order->product_list, $id_carrier);
  269. $order->total_products_wt = (float)$this->context->cart->getOrderTotal(true, Cart::ONLY_PRODUCTS, $order->product_list, $id_carrier);
  270. $order->total_discounts_tax_excl = (float)abs($this->context->cart->getOrderTotal(false, Cart::ONLY_DISCOUNTS, $order->product_list, $id_carrier));
  271. $order->total_discounts_tax_incl = (float)abs($this->context->cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS, $order->product_list, $id_carrier));
  272. $order->total_discounts = $order->total_discounts_tax_incl;
  273. $order->total_shipping_tax_excl = (float)$this->context->cart->getPackageShippingCost((int)$id_carrier, false, null, $order->product_list);
  274. $order->total_shipping_tax_incl = (float)$this->context->cart->getPackageShippingCost((int)$id_carrier, true, null, $order->product_list);
  275. $order->total_shipping = $order->total_shipping_tax_incl;
  276. if (!is_null($carrier) && Validate::isLoadedObject($carrier))
  277. $order->carrier_tax_rate = $carrier->getTaxesRate(new Address($this->context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}));
  278. $order->total_wrapping_tax_excl = (float)abs($this->context->cart->getOrderTotal(false, Cart::ONLY_WRAPPING, $order->product_list, $id_carrier));
  279. $order->total_wrapping_tax_incl = (float)abs($this->context->cart->getOrderTotal(true, Cart::ONLY_WRAPPING, $order->product_list, $id_carrier));
  280. $order->total_wrapping = $order->total_wrapping_tax_incl;
  281. $order->total_paid_tax_excl = (float)Tools::ps_round((float)$this->context->cart->getOrderTotal(false, Cart::BOTH, $order->product_list, $id_carrier), 2);
  282. $order->total_paid_tax_incl = (float)Tools::ps_round((float)$this->context->cart->getOrderTotal(true, Cart::BOTH, $order->product_list, $id_carrier), 2);
  283. $order->total_paid = $order->total_paid_tax_incl;
  284. $order->invoice_date = '0000-00-00 00:00:00';
  285. $order->delivery_date = '0000-00-00 00:00:00';
  286. if (self::DEBUG_MODE)
  287. PrestaShopLogger::addLog('PaymentModule::validateOrder - Order is about to be added', 1, null, 'Cart', (int)$id_cart, true);
  288. // Creating order
  289. $result = $order->add();
  290. if (!$result)
  291. {
  292. PrestaShopLogger::addLog('PaymentModule::validateOrder - Order cannot be created', 3, null, 'Cart', (int)$id_cart, true);
  293. throw new PrestaShopException('Can\'t save Order');
  294. }
  295. // Amount paid by customer is not the right one -> Status = payment error
  296. // We don't use the following condition to avoid the float precision issues : http://www.php.net/manual/en/language.types.float.php
  297. // if ($order->total_paid != $order->total_paid_real)
  298. // We use number_format in order to compare two string
  299. if ($order_status->logable && number_format($cart_total_paid, 2) != number_format($amount_paid, 2))
  300. $id_order_state = Configuration::get('PS_OS_ERROR');
  301. $order_list[] = $order;
  302. if (self::DEBUG_MODE)
  303. PrestaShopLogger::addLog('PaymentModule::validateOrder - OrderDetail is about to be added', 1, null, 'Cart', (int)$id_cart, true);
  304. // Insert new Order detail list using cart for the current order
  305. $order_detail = new OrderDetail(null, null, $this->context);
  306. $order_detail->createList($order, $this->context->cart, $id_order_state, $order->product_list, 0, true, $package_list[$id_address][$id_package]['id_warehouse']);
  307. $order_detail_list[] = $order_detail;
  308. if (self::DEBUG_MODE)
  309. PrestaShopLogger::addLog('PaymentModule::validateOrder - OrderCarrier is about to be added', 1, null, 'Cart', (int)$id_cart, true);
  310. // Adding an entry in order_carrier table
  311. if (!is_null($carrier))
  312. {
  313. $order_carrier = new OrderCarrier();
  314. $order_carrier->id_order = (int)$order->id;
  315. $order_carrier->id_carrier = (int)$id_carrier;
  316. $order_carrier->weight = (float)$order->getTotalWeight();
  317. $order_carrier->shipping_cost_tax_excl = (float)$order->total_shipping_tax_excl;
  318. $order_carrier->shipping_cost_tax_incl = (float)$order->total_shipping_tax_incl;
  319. $order_carrier->add();
  320. }
  321. }
  322. // The country can only change if the address used for the calculation is the delivery address, and if multi-shipping is activated
  323. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_delivery')
  324. $this->context->country = $context_country;
  325. if (!$this->context->country->active)
  326. {
  327. PrestaShopLogger::addLog('PaymentModule::validateOrder - Country is not active', 3, null, 'Cart', (int)$id_cart, true);
  328. throw new PrestaShopException('The order address country is not active.');
  329. }
  330. if (self::DEBUG_MODE)
  331. PrestaShopLogger::addLog('PaymentModule::validateOrder - Payment is about to be added', 1, null, 'Cart', (int)$id_cart, true);
  332. // Register Payment only if the order status validate the order
  333. if ($order_status->logable)
  334. {
  335. // $order is the last order loop in the foreach
  336. // The method addOrderPayment of the class Order make a create a paymentOrder
  337. // linked to the order reference and not to the order id
  338. if (isset($extra_vars['transaction_id']))
  339. $transaction_id = $extra_vars['transaction_id'];
  340. else
  341. $transaction_id = null;
  342. if (!$order->addOrderPayment($amount_paid, null, $transaction_id))
  343. {
  344. PrestaShopLogger::addLog('PaymentModule::validateOrder - Cannot save Order Payment', 3, null, 'Cart', (int)$id_cart, true);
  345. throw new PrestaShopException('Can\'t save Order Payment');
  346. }
  347. }
  348. // Next !
  349. $only_one_gift = false;
  350. $cart_rule_used = array();
  351. $products = $this->context->cart->getProducts();
  352. // Make sure CarRule caches are empty
  353. CartRule::cleanCache();
  354. foreach ($order_detail_list as $key => $order_detail)
  355. {
  356. $order = $order_list[$key];
  357. if (!$order_creation_failed && isset($order->id))
  358. {
  359. if (!$secure_key)
  360. $message .= '<br />'.Tools::displayError('Warning: the secure key is empty, check your payment account before validation');
  361. // Optional message to attach to this order
  362. if (isset($message) & !empty($message))
  363. {
  364. $msg = new Message();
  365. $message = strip_tags($message, '<br>');
  366. if (Validate::isCleanHtml($message))
  367. {
  368. if (self::DEBUG_MODE)
  369. PrestaShopLogger::addLog('PaymentModule::validateOrder - Message is about to be added', 1, null, 'Cart', (int)$id_cart, true);
  370. $msg->message = $message;
  371. $msg->id_order = intval($order->id);
  372. $msg->private = 1;
  373. $msg->add();
  374. }
  375. }
  376. // Insert new Order detail list using cart for the current order
  377. //$orderDetail = new OrderDetail(null, null, $this->context);
  378. //$orderDetail->createList($order, $this->context->cart, $id_order_state);
  379. // Construct order detail table for the email
  380. $products_list = '';
  381. $virtual_product = true;
  382. $product_var_tpl_list = array();
  383. foreach ($order->product_list as $product)
  384. {
  385. $price = Product::getPriceStatic((int)$product['id_product'], false, ($product['id_product_attribute'] ? (int)$product['id_product_attribute'] : null), 6, null, false, true, $product['cart_quantity'], false, (int)$order->id_customer, (int)$order->id_cart, (int)$order->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
  386. $price_wt = Product::getPriceStatic((int)$product['id_product'], true, ($product['id_product_attribute'] ? (int)$product['id_product_attribute'] : null), 2, null, false, true, $product['cart_quantity'], false, (int)$order->id_customer, (int)$order->id_cart, (int)$order->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
  387. $product_price = Product::getTaxCalculationMethod() == PS_TAX_EXC ? Tools::ps_round($price, 2) : $price_wt;
  388. $product_var_tpl = array(
  389. 'reference' => $product['reference'],
  390. 'name' => $product['name'].(isset($product['attributes']) ? ' - '.$product['attributes'] : ''),
  391. 'unit_price' => Tools::displayPrice($product_price, $this->context->currency, false),
  392. 'price' => Tools::displayPrice($product_price * $product['quantity'], $this->context->currency, false),
  393. 'quantity' => $product['quantity'],
  394. 'customization' => array()
  395. );
  396. $customized_datas = Product::getAllCustomizedDatas((int)$order->id_cart);
  397. if (isset($customized_datas[$product['id_product']][$product['id_product_attribute']]))
  398. {
  399. $product_var_tpl['customization'] = array();
  400. foreach ($customized_datas[$product['id_product']][$product['id_product_attribute']][$order->id_address_delivery] as $customization)
  401. {
  402. $customization_text = '';
  403. if (isset($customization['datas'][Product::CUSTOMIZE_TEXTFIELD]))
  404. foreach ($customization['datas'][Product::CUSTOMIZE_TEXTFIELD] as $text)
  405. $customization_text .= $text['name'].': '.$text['value'].'<br />';
  406. if (isset($customization['datas'][Product::CUSTOMIZE_FILE]))
  407. $customization_text .= sprintf(Tools::displayError('%d image(s)'), count($customization['datas'][Product::CUSTOMIZE_FILE])).'<br />';
  408. $customization_quantity = (int)$product['customization_quantity'];
  409. $product_var_tpl['customization'][] = array(
  410. 'customization_text' => $customization_text,
  411. 'customization_quantity' => $customization_quantity,
  412. 'quantity' => Tools::displayPrice($customization_quantity * $product_price, $this->context->currency, false)
  413. );
  414. }
  415. }
  416. $product_var_tpl_list[] = $product_var_tpl;
  417. // Check if is not a virutal product for the displaying of shipping
  418. if (!$product['is_virtual'])
  419. $virtual_product &= false;
  420. } // end foreach ($products)
  421. $product_list_txt = '';
  422. $product_list_html = '';
  423. if (count($product_var_tpl_list) > 0)
  424. {
  425. $product_list_txt = $this->getEmailTemplateContent('order_conf_product_list.txt', Mail::TYPE_TEXT, $product_var_tpl_list);
  426. $product_list_html = $this->getEmailTemplateContent('order_conf_product_list.tpl', Mail::TYPE_HTML, $product_var_tpl_list);
  427. }
  428. $cart_rules_list = array();
  429. $total_reduction_value_ti = 0;
  430. $total_reduction_value_tex = 0;
  431. foreach ($cart_rules as $cart_rule)
  432. {
  433. $package = array('id_carrier' => $order->id_carrier, 'id_address' => $order->id_address_delivery, 'products' => $order->product_list);
  434. $values = array(
  435. 'tax_incl' => $cart_rule['obj']->getContextualValue(true, $this->context, CartRule::FILTER_ACTION_ALL_NOCAP, $package),
  436. 'tax_excl' => $cart_rule['obj']->getContextualValue(false, $this->context, CartRule::FILTER_ACTION_ALL_NOCAP, $package)
  437. );
  438. // If the reduction is not applicable to this order, then continue with the next one
  439. if (!$values['tax_excl'])
  440. continue;
  441. /* IF
  442. ** - This is not multi-shipping
  443. ** - The value of the voucher is greater than the total of the order
  444. ** - Partial use is allowed
  445. ** - This is an "amount" reduction, not a reduction in % or a gift
  446. ** THEN
  447. ** The voucher is cloned with a new value corresponding to the remainder
  448. */
  449. if (count($order_list) == 1 && $values['tax_incl'] > ($order->total_products_wt - $total_reduction_value_ti) && $cart_rule['obj']->partial_use == 1 && $cart_rule['obj']->reduction_amount > 0)
  450. {
  451. // Create a new voucher from the original
  452. $voucher = new CartRule($cart_rule['obj']->id); // We need to instantiate the CartRule without lang parameter to allow saving it
  453. unset($voucher->id);
  454. // Set a new voucher code
  455. $voucher->code = empty($voucher->code) ? substr(md5($order->id.'-'.$order->id_customer.'-'.$cart_rule['obj']->id), 0, 16) : $voucher->code.'-2';
  456. if (preg_match('/\-([0-9]{1,2})\-([0-9]{1,2})$/', $voucher->code, $matches) && $matches[1] == $matches[2])
  457. $voucher->code = preg_replace('/'.$matches[0].'$/', '-'.(intval($matches[1]) + 1), $voucher->code);
  458. // Set the new voucher value
  459. if ($voucher->reduction_tax)
  460. {
  461. $voucher->reduction_amount = $values['tax_incl'] - ($order->total_products_wt - $total_reduction_value_ti);
  462. // Add total shipping amout only if reduction amount > total shipping
  463. if ($voucher->free_shipping == 1 && $voucher->reduction_amount >= $order->total_shipping_tax_incl)
  464. $voucher->reduction_amount -= $order->total_shipping_tax_incl;
  465. }
  466. else
  467. {
  468. $voucher->reduction_amount = $values['tax_excl'] - ($order->total_products - $total_reduction_value_tex);
  469. // Add total shipping amout only if reduction amount > total shipping
  470. if ($voucher->free_shipping == 1 && $voucher->reduction_amount >= $order->total_shipping_tax_excl)
  471. $voucher->reduction_amount -= $order->total_shipping_tax_excl;
  472. }
  473. $voucher->id_customer = $order->id_customer;
  474. $voucher->quantity = 1;
  475. $voucher->quantity_per_user = 1;
  476. $voucher->free_shipping = 0;
  477. if ($voucher->add())
  478. {
  479. // If the voucher has conditions, they are now copied to the new voucher
  480. CartRule::copyConditions($cart_rule['obj']->id, $voucher->id);
  481. $params = array(
  482. '{voucher_amount}' => Tools::displayPrice($voucher->reduction_amount, $this->context->currency, false),
  483. '{voucher_num}' => $voucher->code,
  484. '{firstname}' => $this->context->customer->firstname,
  485. '{lastname}' => $this->context->customer->lastname,
  486. '{id_order}' => $order->reference,
  487. '{order_name}' => $order->getUniqReference()
  488. );
  489. Mail::Send(
  490. (int)$order->id_lang,
  491. 'voucher',
  492. sprintf(Mail::l('New voucher for your order %s', (int)$order->id_lang), $order->reference),
  493. $params,
  494. $this->context->customer->email,
  495. $this->context->customer->firstname.' '.$this->context->customer->lastname,
  496. null, null, null, null, _PS_MAIL_DIR_, false, (int)$order->id_shop
  497. );
  498. }
  499. $values['tax_incl'] -= $values['tax_incl'] - $order->total_products_wt;
  500. $values['tax_excl'] -= $values['tax_excl'] - $order->total_products;
  501. }
  502. $total_reduction_value_ti += $values['tax_incl'];
  503. $total_reduction_value_tex += $values['tax_excl'];
  504. $order->addCartRule($cart_rule['obj']->id, $cart_rule['obj']->name, $values, 0, $cart_rule['obj']->free_shipping);
  505. if ($id_order_state != Configuration::get('PS_OS_ERROR') && $id_order_state != Configuration::get('PS_OS_CANCELED') && !in_array($cart_rule['obj']->id, $cart_rule_used))
  506. {
  507. $cart_rule_used[] = $cart_rule['obj']->id;
  508. // Create a new instance of Cart Rule without id_lang, in order to update its quantity
  509. $cart_rule_to_update = new CartRule($cart_rule['obj']->id);
  510. $cart_rule_to_update->quantity = max(0, $cart_rule_to_update->quantity - 1);
  511. $cart_rule_to_update->update();
  512. }
  513. $cart_rules_list[] = array(
  514. 'voucher_name' => $cart_rule['obj']->name,
  515. 'voucher_reduction' => ($values['tax_incl'] != 0.00 ? '-' : '').Tools::displayPrice($values['tax_incl'], $this->context->currency, false)
  516. );
  517. }
  518. $cart_rules_list_txt = '';
  519. $cart_rules_list_html = '';
  520. if (count($cart_rules_list) > 0)
  521. {
  522. $cart_rules_list_txt = $this->getEmailTemplateContent('order_conf_cart_rules.txt', Mail::TYPE_TEXT, $cart_rules_list);
  523. $cart_rules_list_html = $this->getEmailTemplateContent('order_conf_cart_rules.tpl', Mail::TYPE_HTML, $cart_rules_list);
  524. }
  525. // Specify order id for message
  526. $old_message = Message::getMessageByCartId((int)$this->context->cart->id);
  527. if ($old_message)
  528. {
  529. $update_message = new Message((int)$old_message['id_message']);
  530. $update_message->id_order = (int)$order->id;
  531. $update_message->update();
  532. // Add this message in the customer thread
  533. $customer_thread = new CustomerThread();
  534. $customer_thread->id_contact = 0;
  535. $customer_thread->id_customer = (int)$order->id_customer;
  536. $customer_thread->id_shop = (int)$this->context->shop->id;
  537. $customer_thread->id_order = (int)$order->id;
  538. $customer_thread->id_lang = (int)$this->context->language->id;
  539. $customer_thread->email = $this->context->customer->email;
  540. $customer_thread->status = 'open';
  541. $customer_thread->token = Tools::passwdGen(12);
  542. $customer_thread->add();
  543. $customer_message = new CustomerMessage();
  544. $customer_message->id_customer_thread = $customer_thread->id;
  545. $customer_message->id_employee = 0;
  546. $customer_message->message = $update_message->message;
  547. $customer_message->private = 0;
  548. if (!$customer_message->add())
  549. $this->errors[] = Tools::displayError('An error occurred while saving message');
  550. }
  551. if (self::DEBUG_MODE)
  552. PrestaShopLogger::addLog('PaymentModule::validateOrder - Hook validateOrder is about to be called', 1, null, 'Cart', (int)$id_cart, true);
  553. // Hook validate order
  554. Hook::exec('actionValidateOrder', array(
  555. 'cart' => $this->context->cart,
  556. 'order' => $order,
  557. 'customer' => $this->context->customer,
  558. 'currency' => $this->context->currency,
  559. 'orderStatus' => $order_status
  560. ));
  561. foreach ($this->context->cart->getProducts() as $product)
  562. if ($order_status->logable)
  563. ProductSale::addProductSale((int)$product['id_product'], (int)$product['cart_quantity']);
  564. if (self::DEBUG_MODE)
  565. PrestaShopLogger::addLog('PaymentModule::validateOrder - Order Status is about to be added', 1, null, 'Cart', (int)$id_cart, true);
  566. // Set the order status
  567. $new_history = new OrderHistory();
  568. $new_history->id_order = (int)$order->id;
  569. $new_history->changeIdOrderState((int)$id_order_state, $order, true);
  570. $new_history->addWithemail(true, $extra_vars);
  571. // Switch to back order if needed
  572. if (Configuration::get('PS_STOCK_MANAGEMENT') && $order_detail->getStockState())
  573. {
  574. $history = new OrderHistory();
  575. $history->id_order = (int)$order->id;
  576. $history->changeIdOrderState(Configuration::get('PS_OS_OUTOFSTOCK'), $order, true);
  577. $history->addWithemail();
  578. }
  579. unset($order_detail);
  580. // Order is reloaded because the status just changed
  581. $order = new Order($order->id);
  582. // Send an e-mail to customer (one order = one email)
  583. if ($id_order_state != Configuration::get('PS_OS_ERROR') && $id_order_state != Configuration::get('PS_OS_CANCELED') && $this->context->customer->id)
  584. {
  585. $invoice = new Address($order->id_address_invoice);
  586. $delivery = new Address($order->id_address_delivery);
  587. $delivery_state = $delivery->id_state ? new State($delivery->id_state) : false;
  588. $invoice_state = $invoice->id_state ? new State($invoice->id_state) : false;
  589. $data = array(
  590. '{firstname}' => $this->context->customer->firstname,
  591. '{lastname}' => $this->context->customer->lastname,
  592. '{email}' => $this->context->customer->email,
  593. '{delivery_block_txt}' => $this->_getFormatedAddress($delivery, "\n"),
  594. '{invoice_block_txt}' => $this->_getFormatedAddress($invoice, "\n"),
  595. '{delivery_block_html}' => $this->_getFormatedAddress($delivery, '<br />', array(
  596. 'firstname' => '<span style="font-weight:bold;">%s</span>',
  597. 'lastname' => '<span style="font-weight:bold;">%s</span>'
  598. )),
  599. '{invoice_block_html}' => $this->_getFormatedAddress($invoice, '<br />', array(
  600. 'firstname' => '<span style="font-weight:bold;">%s</span>',
  601. 'lastname' => '<span style="font-weight:bold;">%s</span>'
  602. )),
  603. '{delivery_company}' => $delivery->company,
  604. '{delivery_firstname}' => $delivery->firstname,
  605. '{delivery_lastname}' => $delivery->lastname,
  606. '{delivery_address1}' => $delivery->address1,
  607. '{delivery_address2}' => $delivery->address2,
  608. '{delivery_city}' => $delivery->city,
  609. '{delivery_postal_code}' => $delivery->postcode,
  610. '{delivery_country}' => $delivery->country,
  611. '{delivery_state}' => $delivery->id_state ? $delivery_state->name : '',
  612. '{delivery_phone}' => ($delivery->phone) ? $delivery->phone : $delivery->phone_mobile,
  613. '{delivery_other}' => $delivery->other,
  614. '{invoice_company}' => $invoice->company,
  615. '{invoice_vat_number}' => $invoice->vat_number,
  616. '{invoice_firstname}' => $invoice->firstname,
  617. '{invoice_lastname}' => $invoice->lastname,
  618. '{invoice_address2}' => $invoice->address2,
  619. '{invoice_address1}' => $invoice->address1,
  620. '{invoice_city}' => $invoice->city,
  621. '{invoice_postal_code}' => $invoice->postcode,
  622. '{invoice_country}' => $invoice->country,
  623. '{invoice_state}' => $invoice->id_state ? $invoice_state->name : '',
  624. '{invoice_phone}' => ($invoice->phone) ? $invoice->phone : $invoice->phone_mobile,
  625. '{invoice_other}' => $invoice->other,
  626. '{order_name}' => $order->getUniqReference(),
  627. '{date}' => Tools::displayDate(date('Y-m-d H:i:s'), null, 1),
  628. '{carrier}' => ($virtual_product || !isset($carrier->name)) ? Tools::displayError('No carrier') : $carrier->name,
  629. '{payment}' => Tools::substr($order->payment, 0, 32),
  630. '{products}' => $product_list_html,
  631. '{products_txt}' => $product_list_txt,
  632. '{discounts}' => $cart_rules_list_html,
  633. '{discounts_txt}' => $cart_rules_list_txt,
  634. '{total_paid}' => Tools::displayPrice($order->total_paid, $this->context->currency, false),
  635. '{total_products}' => Tools::displayPrice($order->total_paid - $order->total_shipping - $order->total_wrapping + $order->total_discounts, $this->context->currency, false),
  636. '{total_discounts}' => Tools::displayPrice($order->total_discounts, $this->context->currency, false),
  637. '{total_shipping}' => Tools::displayPrice($order->total_shipping, $this->context->currency, false),
  638. '{total_wrapping}' => Tools::displayPrice($order->total_wrapping, $this->context->currency, false),
  639. '{total_tax_paid}' => Tools::displayPrice(($order->total_products_wt - $order->total_products) + ($order->total_shipping_tax_incl - $order->total_shipping_tax_excl), $this->context->currency, false));
  640. if (is_array($extra_vars))
  641. $data = array_merge($data, $extra_vars);
  642. // Join PDF invoice
  643. if ((int)Configuration::get('PS_INVOICE') && $order_status->invoice && $order->invoice_number)
  644. {
  645. $pdf = new PDF($order->getInvoicesCollection(), PDF::TEMPLATE_INVOICE, $this->context->smarty);
  646. $file_attachement['content'] = $pdf->render(false);
  647. $file_attachement['name'] = Configuration::get('PS_INVOICE_PREFIX', (int)$order->id_lang, null, $order->id_shop).sprintf('%06d', $order->invoice_number).'.pdf';
  648. $file_attachement['mime'] = 'application/pdf';
  649. }
  650. else
  651. $file_attachement = null;
  652. if (self::DEBUG_MODE)
  653. PrestaShopLogger::addLog('PaymentModule::validateOrder - Mail is about to be sent', 1, null, 'Cart', (int)$id_cart, true);
  654. if (Validate::isEmail($this->context->customer->email))
  655. Mail::Send(
  656. (int)$order->id_lang,
  657. 'order_conf',
  658. Mail::l('Order confirmation', (int)$order->id_lang),
  659. $data,
  660. $this->context->customer->email,
  661. $this->context->customer->firstname.' '.$this->context->customer->lastname,
  662. null,
  663. null,
  664. $file_attachement,
  665. null, _PS_MAIL_DIR_, false, (int)$order->id_shop
  666. );
  667. }
  668. // updates stock in shops
  669. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
  670. {
  671. $product_list = $order->getProducts();
  672. foreach ($product_list as $product)
  673. {
  674. // if the available quantities depends on the physical stock
  675. if (StockAvailable::dependsOnStock($product['product_id']))
  676. {
  677. // synchronizes
  678. StockAvailable::synchronize($product['product_id'], $order->id_shop);
  679. }
  680. }
  681. }
  682. }
  683. else
  684. {
  685. $error = Tools::displayError('Order creation failed');
  686. PrestaShopLogger::addLog($error, 4, '0000002', 'Cart', intval($order->id_cart));
  687. die($error);
  688. }
  689. } // End foreach $order_detail_list
  690. // Use the last order as currentOrder
  691. $this->currentOrder = (int)$order->id;
  692. if (self::DEBUG_MODE)
  693. PrestaShopLogger::addLog('PaymentModule::validateOrder - End of validateOrder', 1, null, 'Cart', (int)$id_cart, true);
  694. return true;
  695. }
  696. else
  697. {
  698. $error = Tools::displayError('Cart cannot be loaded or an order has already been placed using this cart');
  699. PrestaShopLogger::addLog($error, 4, '0000001', 'Cart', intval($this->context->cart->id));
  700. die($error);
  701. }
  702. }
  703. public function formatProductAndVoucherForEmail($content)
  704. {
  705. Tools::displayAsDeprecated();
  706. return $content;
  707. }
  708. /**
  709. * @param Object Address $the_address that needs to be txt formated
  710. * @return String the txt formated address block
  711. */
  712. protected function _getTxtFormatedAddress($the_address)
  713. {
  714. $adr_fields = AddressFormat::getOrderedAddressFields($the_address->id_country, false, true);
  715. $r_values = array();
  716. foreach ($adr_fields as $fields_line)
  717. {
  718. $tmp_values = array();
  719. foreach (explode(' ', $fields_line) as $field_item)
  720. {
  721. $field_item = trim($field_item);
  722. $tmp_values[] = $the_address->{$field_item};
  723. }
  724. $r_values[] = implode(' ', $tmp_values);
  725. }
  726. $out = implode("\n", $r_values);
  727. return $out;
  728. }
  729. /**
  730. * @param Object Address $the_address that needs to be txt formated
  731. * @return String the txt formated address block
  732. */
  733. protected function _getFormatedAddress(Address $the_address, $line_sep, $fields_style = array())
  734. {
  735. return AddressFormat::generateAddress($the_address, array('avoid' => array()), $line_sep, ' ', $fields_style);
  736. }
  737. /**
  738. * @param int $id_currency : this parameter is optionnal but on 1.5 version of Prestashop, it will be REQUIRED
  739. * @return Currency
  740. */
  741. public function getCurrency($current_id_currency = null)
  742. {
  743. if (!(int)$current_id_currency)
  744. $current_id_currency = Context::getContext()->currency->id;
  745. if (!$this->currencies)
  746. return false;
  747. if ($this->currencies_mode == 'checkbox')
  748. {
  749. $currencies = Currency::getPaymentCurrencies($this->id);
  750. return $currencies;
  751. }
  752. elseif ($this->currencies_mode == 'radio')
  753. {
  754. $currencies = Currency::getPaymentCurrenciesSpecial($this->id);
  755. $currency = $currencies['id_currency'];
  756. if ($currency == -1)
  757. $id_currency = (int)$current_id_currency;
  758. elseif ($currency == -2)
  759. $id_currency = (int)Configuration::get('PS_CURRENCY_DEFAULT');
  760. else
  761. $id_currency = $currency;
  762. }
  763. if (!isset($id_currency) || empty($id_currency))
  764. return false;
  765. $currency = new Currency($id_currency);
  766. return $currency;
  767. }
  768. /**
  769. * Allows specified payment modules to be used by a specific currency
  770. *
  771. * @since 1.4.5
  772. * @param int $id_currency
  773. * @param array $id_module_list
  774. * @return boolean
  775. */
  776. public static function addCurrencyPermissions($id_currency, array $id_module_list = array())
  777. {
  778. $values = '';
  779. if (count($id_module_list) == 0)
  780. {
  781. // fetch all installed module ids
  782. $modules = PaymentModuleCore::getInstalledPaymentModules();
  783. foreach ($modules as $module)
  784. $id_module_list[] = $module['id_module'];
  785. }
  786. foreach ($id_module_list as $id_module)
  787. $values .= '('.(int)$id_module.','.(int)$id_currency.'),';
  788. if (!empty($values))
  789. {
  790. return Db::getInstance()->execute('
  791. INSERT INTO `'._DB_PREFIX_.'module_currency` (`id_module`, `id_currency`)
  792. VALUES '.rtrim($values, ','));
  793. }
  794. return true;
  795. }
  796. /**
  797. * List all installed and active payment modules
  798. * @see Module::getPaymentModules() if you need a list of module related to the user context
  799. *
  800. * @since 1.4.5
  801. * @return array module informations
  802. */
  803. public static function getInstalledPaymentModules()
  804. {
  805. $hook_payment = 'Payment';
  806. if (Db::getInstance()->getValue('SELECT `id_hook` FROM `'._DB_PREFIX_.'hook` WHERE `name` = \'displayPayment\''))
  807. $hook_payment = 'displayPayment';
  808. return Db::getInstance()->executeS('
  809. SELECT DISTINCT m.`id_module`, h.`id_hook`, m.`name`, hm.`position`
  810. FROM `'._DB_PREFIX_.'module` m
  811. LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module`
  812. LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook`
  813. INNER JOIN `'._DB_PREFIX_.'module_shop` ms ON (m.`id_module` = ms.`id_module` AND ms.id_shop='.(int)Context::getContext()->shop->id.')
  814. WHERE h.`name` = \''.pSQL($hook_payment).'\'');
  815. }
  816. public static function preCall($module_name)
  817. {
  818. if (!parent::preCall($module_name))
  819. return false;
  820. if (($module_instance = Module::getInstanceByName($module_name)))
  821. if (!$module_instance->currencies || ($module_instance->currencies && count(Currency::checkPaymentCurrencies($module_instance->id))))
  822. return true;
  823. return false;
  824. }
  825. /**
  826. * Fetch the content of $template_name inside the folder current_theme/mails/current_iso_lang/ if found, otherwise in mails/current_iso_lang
  827. *
  828. * @param string $template_name template name with extension
  829. * @param integer $mail_type Mail::TYPE_HTML or Mail::TYPE_TXT
  830. * @param array $var list send to smarty
  831. *
  832. * @return string
  833. */
  834. protected function getEmailTemplateContent($template_name, $mail_type, $var)
  835. {
  836. $email_configuration = Configuration::get('PS_MAIL_TYPE');
  837. if ($email_configuration != $mail_type && $email_configuration != Mail::TYPE_BOTH)
  838. return '';
  839. $theme_template_path = _PS_THEME_DIR_.'mails'.DIRECTORY_SEPARATOR.$this->context->language->iso_code.DIRECTORY_SEPARATOR.$template_name;
  840. $default_mail_template_path = _PS_MAIL_DIR_.$this->context->language->iso_code.DIRECTORY_SEPARATOR.$template_name;
  841. if (Tools::file_exists_cache($theme_template_path))
  842. $default_mail_template_path = $theme_template_path;
  843. if (Tools::file_exists_cache($default_mail_template_path))
  844. {
  845. $this->context->smarty->assign('list', $var);
  846. return $this->context->smarty->fetch($default_mail_template_path);
  847. }
  848. return '';
  849. }
  850. }