* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ namespace PrestaShop\PrestaShop\Adapter\Order\CommandHandler; use Cart; use CartRule; use Configuration; use Currency; use Customer; use Language; use Order; use OrderInvoice; use PrestaShop\PrestaShop\Adapter\ContextStateManager; use PrestaShop\PrestaShop\Adapter\Order\AbstractOrderHandler; use PrestaShop\PrestaShop\Adapter\Order\OrderAmountUpdater; use PrestaShop\PrestaShop\Core\Domain\CartRule\Exception\InvalidCartRuleDiscountValueException; use PrestaShop\PrestaShop\Core\Domain\Order\Command\AddCartRuleToOrderCommand; use PrestaShop\PrestaShop\Core\Domain\Order\CommandHandler\AddCartRuleToOrderHandlerInterface; use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderException; use PrestaShop\PrestaShop\Core\Domain\Order\OrderDiscountType; use PrestaShopException; use Shop; use Validate; /** * @internal */ final class AddCartRuleToOrderHandler extends AbstractOrderHandler implements AddCartRuleToOrderHandlerInterface { /** * @var OrderAmountUpdater */ private $orderAmountUpdater; /** * @var ContextStateManager */ private $contextStateManager; /** * @param OrderAmountUpdater $orderAmountUpdater * @param ContextStateManager $contextStateManager */ public function __construct(OrderAmountUpdater $orderAmountUpdater, ContextStateManager $contextStateManager) { $this->orderAmountUpdater = $orderAmountUpdater; $this->contextStateManager = $contextStateManager; } /** * {@inheritdoc} */ public function handle(AddCartRuleToOrderCommand $command): void { $this->assertPercentCartRule($command); $order = $this->getOrder($command->getOrderId()); $this->contextStateManager ->setCurrency(new Currency($order->id_currency)) ->setCustomer(new Customer($order->id_customer)) ->setLanguage((new Language($order->id_lang))) ->setShop(new Shop($order->id_shop)) ; try { $this->addCartRuleAndUpdateOrder($command, $order); } finally { $this->contextStateManager->restorePreviousContext(); } } /** * @param AddCartRuleToOrderCommand $command * @param Order $order * * @return void * * @throws InvalidCartRuleDiscountValueException * @throws OrderException * @throws PrestaShopException * @throws \PrestaShopDatabaseException */ private function addCartRuleAndUpdateOrder(AddCartRuleToOrderCommand $command, Order $order): void { // If the discount is for only one invoice $orderInvoice = null; if ($order->hasInvoice() && null !== $command->getOrderInvoiceId()) { $orderInvoice = new OrderInvoice($command->getOrderInvoiceId()->getValue()); if (!Validate::isLoadedObject($orderInvoice)) { throw new OrderException('Can\'t load Order Invoice object'); } } $this->assertAmountCartRule($command, $order, $orderInvoice); $this->assertFreeShippingCartRule($command, $order, $orderInvoice); $cart = Cart::getCartByOrderId($order->id); $cartRuleObj = new CartRule(); $cartRuleObj->date_from = date('Y-m-d H:i:s', strtotime('-1 hour', strtotime($order->date_add))); $cartRuleObj->date_to = date('Y-m-d H:i:s', strtotime('+1 hour')); $cartRuleObj->name[Configuration::get('PS_LANG_DEFAULT')] = $command->getCartRuleName(); // This a one time cart rule, for a specific user that can only be used once $cartRuleObj->id_customer = $cart->id_customer; $cartRuleObj->quantity = 1; $cartRuleObj->quantity_per_user = 1; $cartRuleObj->reduction_currency = (int) $order->id_currency; $cartRuleObj->active = false; $cartRuleObj->highlight = false; if ($command->getCartRuleType() === OrderDiscountType::DISCOUNT_PERCENT) { $cartRuleObj->reduction_percent = (float) (string) $command->getDiscountValue(); } elseif ($command->getCartRuleType() === OrderDiscountType::DISCOUNT_AMOUNT) { $discountValueTaxIncluded = (float) (string) $command->getDiscountValue(); $cartRuleObj->reduction_amount = $discountValueTaxIncluded; $cartRuleObj->reduction_tax = true; } elseif ($command->getCartRuleType() === OrderDiscountType::FREE_SHIPPING) { $cartRuleObj->free_shipping = true; } try { if (!$cartRuleObj->add()) { throw new OrderException('An error occurred during the CartRule creation'); } } catch (PrestaShopException $e) { throw new OrderException('An error occurred during the CartRule creation', 0, $e); } try { // It's important to add the cart rule to the cart Or it will be ignored when cart performs AutoRemove AddAdd if (!$cart->addCartRule($cartRuleObj->id)) { throw new OrderException('An error occurred while adding CartRule to cart'); } } catch (PrestaShopException $e) { throw new OrderException('An error occurred while adding CartRule to cart', 0, $e); } $this->orderAmountUpdater->update($order, $cart, null !== $orderInvoice ? (int) $orderInvoice->id : null); } /** * @param AddCartRuleToOrderCommand $command * * @throws InvalidCartRuleDiscountValueException */ private function assertPercentCartRule(AddCartRuleToOrderCommand $command): void { if (OrderDiscountType::DISCOUNT_PERCENT !== $command->getCartRuleType()) { return; } $discountValue = (float) (string) $command->getDiscountValue(); if ($discountValue <= 0) { throw new InvalidCartRuleDiscountValueException( 'Percent value must be greater than 0', InvalidCartRuleDiscountValueException::INVALID_MIN_PERCENT ); } elseif ($discountValue > 100) { throw new InvalidCartRuleDiscountValueException( 'Percent value must be less than 100', InvalidCartRuleDiscountValueException::INVALID_MAX_PERCENT ); } } /** * @param AddCartRuleToOrderCommand $command * @param Order $order * @param OrderInvoice|null $orderInvoice * * @throws InvalidCartRuleDiscountValueException */ private function assertAmountCartRule(AddCartRuleToOrderCommand $command, Order $order, ?OrderInvoice $orderInvoice): void { if (OrderDiscountType::DISCOUNT_AMOUNT !== $command->getCartRuleType()) { return; } if (null === $command->getDiscountValue() || $command->getDiscountValue()->isLowerOrEqualThanZero()) { throw new InvalidCartRuleDiscountValueException( 'Discount amount specified is not positive', InvalidCartRuleDiscountValueException::INVALID_MIN_AMOUNT ); } $discountValue = (float) (string) $command->getDiscountValue(); if (null !== $orderInvoice) { $orderInvoices = [$orderInvoice]; } elseif ($order->hasInvoice()) { $orderInvoices = $order->getInvoicesCollection()->getResults(); } if (!empty($orderInvoices)) { foreach ($orderInvoices as $invoice) { if ($discountValue > $invoice->total_paid_tax_incl) { throw new InvalidCartRuleDiscountValueException( 'Discount amount specified is too high', InvalidCartRuleDiscountValueException::INVALID_MAX_AMOUNT ); } } } else { if ($discountValue > $order->total_paid_tax_incl) { throw new InvalidCartRuleDiscountValueException( 'Discount amount specified is too high', InvalidCartRuleDiscountValueException::INVALID_MAX_AMOUNT ); } } } /** * @param AddCartRuleToOrderCommand $command * @param Order $order * @param OrderInvoice|null $orderInvoice * * @throws InvalidCartRuleDiscountValueException */ private function assertFreeShippingCartRule(AddCartRuleToOrderCommand $command, Order $order, ?OrderInvoice $orderInvoice): void { if (OrderDiscountType::FREE_SHIPPING !== $command->getCartRuleType()) { return; } if (null !== $orderInvoice) { $orderInvoices = [$orderInvoice]; } elseif ($order->hasInvoice()) { $orderInvoices = $order->getInvoicesCollection()->getResults(); } if (!empty($orderInvoices)) { foreach ($orderInvoices as $invoice) { if ($invoice->total_paid_tax_incl < $invoice->total_shipping_tax_incl) { throw new InvalidCartRuleDiscountValueException( 'Discount amount specified is too high', InvalidCartRuleDiscountValueException::INVALID_FREE_SHIPPING ); } } } else { if ($order->total_paid_tax_incl < $order->total_shipping_tax_incl) { throw new InvalidCartRuleDiscountValueException( 'Discount amount specified is too high', InvalidCartRuleDiscountValueException::INVALID_FREE_SHIPPING ); } } } }