name = 'uscharge'; $this->tab = 'pricing_promotion'; $this->version = '0.1.1'; $this->author = 'BigM'; $this->need_instance = 0; $this->bootstrap = true; $this->ps_versions_compliancy = array('min' => '1.6.1.0', 'max' => '1.6.99.99'); parent::__construct(); $this->displayName = $this->l('USA Surcharge'); $this->description = $this->l('% surcharge for US delivery addresses.'); $this->confirmUninstall = $this->l('Uninstall the USA surcharge module?'); } public function install() { return parent::install() && $this->registerHook('actionCartGetOrderTotal') && $this->registerHook('actionValidateOrder') && $this->registerHook('displayHeader') && $this->registerHook('displayShoppingCart'); } public function uninstall() { return parent::uninstall(); } /** * Determine whether the cart's DELIVERY address is in the USA. */ protected function isUsDeliveryCart($cart) { if (!Validate::isLoadedObject($cart) || !(int)$cart->id_address_delivery) { return false; } $address = new Address((int)$cart->id_address_delivery); return Validate::isLoadedObject($address) && (int)$address->id_country === (int)self::US_COUNTRY_ID; } /** * Calculate the discounted merchandise value without shipping. * Cart::ONLY_PRODUCTS and ONLY_DISCOUNTS do not include this module's surcharge, * avoiding recursive calculations and keeping the surcharge out of discounts. */ protected function getDiscountedMerchandiseBase($cart) { $products = (float)$cart->getOrderTotal(true, Cart::ONLY_PRODUCTS); $discounts = (float)$cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS); // PrestaShop's ONLY_DISCOUNTS value is the discount amount to subtract. $base = $products - $discounts; if ($base < 0) { $base = 0; } return (float)Tools::ps_round($base, 2); } protected function getSurchargeAmount($cart) { if (!$this->isUsDeliveryCart($cart)) { return 0.0; } $base = $this->getDiscountedMerchandiseBase($cart); return (float)Tools::ps_round($base * self::RATE, 2); } /** * Make the surcharge available to the ajax shopping-cart. */ public function hookDisplayHeader($params) { $cart = $this->context->cart; $fee = 0.0; if (Validate::isLoadedObject($cart)) { $fee = $this->getSurchargeAmount($cart); } $this->context->smarty->assign(array( 'uscharge_amount' => $fee, 'uscharge_line_name' => self::LINE_NAME )); } /** * Make the surcharge available to the shopping-cart template. */ public function hookDisplayShoppingCart($params) { $cart = $this->context->cart; $fee = 0.0; if (Validate::isLoadedObject($cart)) { $fee = $this->getSurchargeAmount($cart); } $this->context->smarty->assign(array( 'uscharge_amount' => $fee, 'uscharge_line_name' => self::LINE_NAME )); return ''; } public function hookActionCartGetOrderTotal($params) { if (empty($params['cart']) || !isset($params['order_total']) || !isset($params['type'])) { return; } $cart = $params['cart']; $type = (int)$params['type']; if (!$this->isUsDeliveryCart($cart)) { return; } // Do not change product-only, discount-only, shipping-only, or wrapping-only totals. if ($type !== (int)Cart::BOTH && $type !== (int)Cart::BOTH_WITHOUT_SHIPPING) { return; } $fee = $this->getSurchargeAmount($cart); if ($fee <= 0) { return; } // The hook receives order_total by reference in this PhenixSuite fork. $params['order_total'] += $fee; } // Add a standalone order-detail line and update stored order/invoice totals. public function hookActionValidateOrder($params) { if (empty($params['cart']) || empty($params['order'])) { return; } $cart = $params['cart']; $order = $params['order']; if (!Validate::isLoadedObject($order) || !$this->isUsDeliveryCart($cart)) { return; } $fee = $this->getSurchargeAmount($cart); if ($fee <= 0) { return; } $db = Db::getInstance(); $prefix = _DB_PREFIX_; // Idempotency guard: do not add the line or totals twice if the hook repeats. $existing = $db->getValue( 'SELECT id_order_detail FROM `' . bqSQL($prefix) . 'order_detail` WHERE id_order = ' . (int)$order->id . ' AND product_id = 0 AND product_name = "' . pSQL(self::LINE_NAME) . '"' ); if ($existing) { return; } // Clone the shape of a real order-detail row so this works across the shop's // exact database schema, then replace the fee-specific fields. $template = $db->getRow( 'SELECT * FROM `' . bqSQL($prefix) . 'order_detail` WHERE id_order = ' . (int)$order->id . ' ORDER BY id_order_detail ASC' ); if (!$template || !is_array($template)) { PrestaShopLogger::addLog( 'USCHARGE: Could not create surcharge line: no order-detail template for order ' . (int)$order->id, 2 ); return; } unset($template['id_order_detail']); $values = $template; $set = array( 'id_order' => (int)$order->id, 'product_id' => 0, 'product_attribute_id' => 0, 'product_name' => self::LINE_NAME, 'product_reference' => '', 'product_supplier_reference' => '', 'product_quantity' => 1, 'product_quantity_in_stock' => 0, 'product_quantity_refunded' => 0, 'product_quantity_return' => 0, 'product_quantity_reinjected' => 0, 'product_price' => $fee, 'original_product_price' => $fee, 'unit_price_tax_incl' => $fee, 'unit_price_tax_excl' => $fee, 'total_price_tax_incl' => $fee, 'total_price_tax_excl' => $fee, 'total_shipping_price_tax_incl' => 0, 'total_shipping_price_tax_excl' => 0, 'reduction_percent' => 0, 'reduction_amount' => 0, 'group_reduction' => 0, 'ecotax' => 0, 'ecotax_tax_rate' => 0, 'tax_rate' => 0, 'tax_name' => '', 'id_tax_rules_group' => 0, 'tax_computation_method' => 0, 'product_weight' => 0, 'product_is_virtual' => 1, 'id_customization' => 0, 'download_hash' => '', 'download_nb' => 0 ); foreach ($set as $key => $value) { if (array_key_exists($key, $values)) { $values[$key] = $value; } } if (!$db->insert('order_detail', $values)) { PrestaShopLogger::addLog( 'USCHARGE: Could not insert surcharge line for order ' . (int)$order->id, 3 ); return; } // The fee is represented as its own order-detail line, so product totals must // include it as well as the order grand totals. Tax-excl/incl are kept equal // to preserve the shop's existing tax-free setup. $order->total_products = (float)$order->total_products + $fee; $order->total_products_wt = (float)$order->total_products_wt + $fee; $order->total_paid_tax_excl = (float)$order->total_paid_tax_excl + $fee; $order->total_paid_tax_incl = (float)$order->total_paid_tax_incl + $fee; $order->total_paid = (float)$order->total_paid + $fee; if (!$order->update()) { PrestaShopLogger::addLog( 'USCHARGE: Could not update order totals for order ' . (int)$order->id, 3 ); } // Update any invoice already generated for this order. $invoiceIds = $db->executeS( 'SELECT id_order_invoice FROM `' . bqSQL($prefix) . 'order_invoice` WHERE id_order = ' . (int)$order->id ); if (is_array($invoiceIds) && count($invoiceIds)) { foreach ($invoiceIds as $invoiceRow) { $idInvoice = (int)$invoiceRow['id_order_invoice']; $db->execute( 'UPDATE `' . bqSQL($prefix) . 'order_invoice` SET total_products = total_products + ' . (float)$fee . ', total_products_wt = total_products_wt + ' . (float)$fee . ', total_paid_tax_excl = total_paid_tax_excl + ' . (float)$fee . ', total_paid_tax_incl = total_paid_tax_incl + ' . (float)$fee . ' WHERE id_order_invoice = ' . $idInvoice ); $db->execute( 'UPDATE `' . bqSQL($prefix) . 'order_detail` SET id_order_invoice = ' . $idInvoice . ' WHERE id_order = ' . (int)$order->id . ' AND product_id = 0 AND product_name = "' . pSQL(self::LINE_NAME) . '"' ); } } PrestaShopLogger::addLog( 'USCHARGE: Added ' . number_format($fee, 2, '.', '') . ' surcharge to order ' . (int)$order->id, 1 ); } }