local/modules/Front/Controller/OrderController.php line 52

  1. <?php
  2. /*
  3.  * This file is part of the Thelia package.
  4.  * http://www.thelia.net
  5.  *
  6.  * (c) OpenStudio <info@thelia.net>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Front\Controller;
  12. use Front\Front;
  13. use Propel\Runtime\ActiveQuery\Criteria;
  14. use Propel\Runtime\Exception\PropelException;
  15. use Symfony\Component\HttpFoundation\Response as BaseResponse;
  16. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  17. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  18. use Thelia\Controller\Front\BaseFrontController;
  19. use Thelia\Core\Event\Delivery\DeliveryPostageEvent;
  20. use Thelia\Core\Event\Order\OrderEvent;
  21. use Thelia\Core\Event\Product\VirtualProductOrderDownloadResponseEvent;
  22. use Thelia\Core\Event\TheliaEvents;
  23. use Thelia\Core\HttpFoundation\Request;
  24. use Thelia\Exception\TheliaProcessException;
  25. use Thelia\Form\Definition\FrontForm;
  26. use Thelia\Form\Exception\FormValidationException;
  27. use Thelia\Log\Tlog;
  28. use Thelia\Model\Address;
  29. use Thelia\Model\AddressQuery;
  30. use Thelia\Model\AreaDeliveryModuleQuery;
  31. use Thelia\Model\ConfigQuery;
  32. use Thelia\Model\ModuleQuery;
  33. use Thelia\Model\Order;
  34. use Thelia\Model\OrderProductQuery;
  35. use Thelia\Model\OrderQuery;
  36. use Thelia\Module\AbstractDeliveryModule;
  37. use Thelia\Module\Exception\DeliveryException;
  38. /**
  39.  * Class OrderController.
  40.  *
  41.  * @author Etienne Roudeix <eroudeix@openstudio.fr>
  42.  */
  43. class OrderController extends BaseFrontController
  44. {
  45.     /**
  46.      * Check if the cart contains only virtual products.
  47.      */
  48.     public function deliverView(EventDispatcherInterface $eventDispatcher)
  49.     {
  50.         $this->checkAuth();
  51.         $this->checkCartNotEmpty($eventDispatcher);
  52.         // check if the cart contains only virtual products
  53.         $cart $this->getSession()->getSessionCart($eventDispatcher);
  54.         $deliveryAddress $this->getCustomerAddress();
  55.         if ($cart->isVirtual()) {
  56.             if (null !== $deliveryAddress) {
  57.                 $deliveryModule ModuleQuery::create()->retrieveVirtualProductDelivery($this->container);
  58.                 if (false === $deliveryModule) {
  59.                     Tlog::getInstance()->error(
  60.                         $this->getTranslator()->trans(
  61.                             'To enable the virtual product feature, the VirtualProductDelivery module should be activated',
  62.                             [],
  63.                             Front::MESSAGE_DOMAIN
  64.                         )
  65.                     );
  66.                 } elseif (\count($deliveryModule) == 1) {
  67.                     return $this->registerVirtualProductDelivery($eventDispatcher$deliveryModule[0], $deliveryAddress);
  68.                 }
  69.             }
  70.         }
  71.         return $this->render(
  72.             'order-delivery',
  73.             [
  74.                 'delivery_address_id' => (null !== $deliveryAddress) ? $deliveryAddress->getId() : null,
  75.             ]
  76.         );
  77.     }
  78.     /**
  79.      * @param AbstractDeliveryModule $moduleInstance
  80.      * @param Address                $deliveryAddress
  81.      *
  82.      * @return \Symfony\Component\HttpFoundation\Response
  83.      */
  84.     private function registerVirtualProductDelivery(EventDispatcherInterface $eventDispatcher$moduleInstance$deliveryAddress)
  85.     {
  86.         /* get postage amount */
  87.         $deliveryModule $moduleInstance->getModuleModel();
  88.         $cart $this->getSession()->getSessionCart($eventDispatcher);
  89.         $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance$cart$deliveryAddress);
  90.         $eventDispatcher->dispatch(
  91.             $deliveryPostageEvent,
  92.             TheliaEvents::MODULE_DELIVERY_GET_POSTAGE
  93.         );
  94.         $postage $deliveryPostageEvent->getPostage();
  95.         $orderEvent $this->getOrderEvent();
  96.         $orderEvent->setDeliveryAddress($deliveryAddress->getId());
  97.         $orderEvent->setDeliveryModule($deliveryModule->getId());
  98.         $orderEvent->setPostage($postage->getAmount());
  99.         $orderEvent->setPostageTax($postage->getAmountTax());
  100.         $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle());
  101.         $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_DELIVERY_ADDRESS);
  102.         $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_DELIVERY_MODULE);
  103.         $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_POSTAGE);
  104.         return $this->generateRedirectFromRoute('order.invoice');
  105.     }
  106.     /**
  107.      * set delivery address
  108.      * set delivery module.
  109.      */
  110.     public function deliver(EventDispatcherInterface $eventDispatcher)
  111.     {
  112.         $this->checkAuth();
  113.         $this->checkCartNotEmpty($eventDispatcher);
  114.         $message false;
  115.         $orderDelivery $this->createForm(FrontForm::ORDER_DELIVER);
  116.         try {
  117.             $form $this->validateForm($orderDelivery'post');
  118.             $deliveryAddressId $form->get('delivery-address')->getData();
  119.             $deliveryModuleId $form->get('delivery-module')->getData();
  120.             $deliveryAddress AddressQuery::create()->findPk($deliveryAddressId);
  121.             $deliveryModule ModuleQuery::create()->findPk($deliveryModuleId);
  122.             /* check that the delivery address belongs to the current customer */
  123.             if ($deliveryAddress->getCustomerId() !== $this->getSecurityContext()->getCustomerUser()->getId()) {
  124.                 throw new \Exception(
  125.                     $this->getTranslator()->trans(
  126.                         'Delivery address does not belong to the current customer',
  127.                         [],
  128.                         Front::MESSAGE_DOMAIN
  129.                     )
  130.                 );
  131.             }
  132.             /* check that the delivery module fetches the delivery address area */
  133.             if (null === AreaDeliveryModuleQuery::create()->findByCountryAndModule(
  134.                 $deliveryAddress->getCountry(),
  135.                 $deliveryModule,
  136.                 $deliveryAddress->getState()
  137.             )) {
  138.                 throw new \Exception(
  139.                     $this->getTranslator()->trans(
  140.                         'Delivery module cannot be use with selected delivery address',
  141.                         [],
  142.                         Front::MESSAGE_DOMAIN
  143.                     )
  144.                 );
  145.             }
  146.             /* get postage amount */
  147.             $moduleInstance $deliveryModule->getDeliveryModuleInstance($this->container);
  148.             $cart $this->getSession()->getSessionCart($eventDispatcher);
  149.             $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance$cart$deliveryAddress);
  150.             $eventDispatcher->dispatch(
  151.                 $deliveryPostageEvent,
  152.                 TheliaEvents::MODULE_DELIVERY_GET_POSTAGE
  153.             );
  154.             if (!$deliveryPostageEvent->isValidModule() || null === $deliveryPostageEvent->getPostage()) {
  155.                 throw new DeliveryException(
  156.                     $this->getTranslator()->trans('The delivery module is not valid.', [], Front::MESSAGE_DOMAIN)
  157.                 );
  158.             }
  159.             $postage $deliveryPostageEvent->getPostage();
  160.             $orderEvent $this->getOrderEvent();
  161.             $orderEvent->setDeliveryAddress($deliveryAddressId);
  162.             $orderEvent->setDeliveryModule($deliveryModuleId);
  163.             $orderEvent->setPostage($postage->getAmount());
  164.             $orderEvent->setPostageTax($postage->getAmountTax());
  165.             $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle());
  166.             $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_DELIVERY_ADDRESS);
  167.             $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_DELIVERY_MODULE);
  168.             $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_POSTAGE);
  169.             return $this->generateRedirectFromRoute('order.invoice');
  170.         } catch (FormValidationException $e) {
  171.             $message $this->getTranslator()->trans(
  172.                 'Please check your input: %s',
  173.                 ['%s' => $e->getMessage()],
  174.                 Front::MESSAGE_DOMAIN
  175.             );
  176.         } catch (PropelException $e) {
  177.             $this->getParserContext()->setGeneralError($e->getMessage());
  178.         } catch (\Exception $e) {
  179.             $message $this->getTranslator()->trans(
  180.                 'Sorry, an error occured: %s',
  181.                 ['%s' => $e->getMessage()],
  182.                 Front::MESSAGE_DOMAIN
  183.             );
  184.         }
  185.         if ($message !== false) {
  186.             Tlog::getInstance()->error(
  187.                 sprintf('Error during order delivery process : %s. Exception was %s'$message$e->getMessage())
  188.             );
  189.             $orderDelivery->setErrorMessage($message);
  190.             $this->getParserContext()
  191.                 ->addForm($orderDelivery)
  192.                 ->setGeneralError($message)
  193.             ;
  194.         }
  195.     }
  196.     /**
  197.      * set invoice address
  198.      * set payment module.
  199.      */
  200.     public function invoice(EventDispatcherInterface $eventDispatcher)
  201.     {
  202.         $this->checkAuth();
  203.         $this->checkCartNotEmpty($eventDispatcher);
  204.         $this->checkValidDelivery();
  205.         $message false;
  206.         $orderPayment $this->createForm(FrontForm::ORDER_PAYMENT);
  207.         try {
  208.             $form $this->validateForm($orderPayment'post');
  209.             $invoiceAddressId $form->get('invoice-address')->getData();
  210.             $paymentModuleId $form->get('payment-module')->getData();
  211.             /* check that the invoice address belongs to the current customer */
  212.             $invoiceAddress AddressQuery::create()->findPk($invoiceAddressId);
  213.             if ($invoiceAddress->getCustomerId() !== $this->getSecurityContext()->getCustomerUser()->getId()) {
  214.                 throw new \Exception(
  215.                     $this->getTranslator()->trans(
  216.                         'Invoice address does not belong to the current customer',
  217.                         [],
  218.                         Front::MESSAGE_DOMAIN
  219.                     )
  220.                 );
  221.             }
  222.             $orderEvent $this->getOrderEvent();
  223.             $orderEvent->setInvoiceAddress($invoiceAddressId);
  224.             $orderEvent->setPaymentModule($paymentModuleId);
  225.             $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_INVOICE_ADDRESS);
  226.             $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_SET_PAYMENT_MODULE);
  227.             return $this->generateRedirectFromRoute('order.payment.process');
  228.         } catch (FormValidationException $e) {
  229.             $message $this->getTranslator()->trans(
  230.                 'Please check your input: %s',
  231.                 ['%s' => $e->getMessage()],
  232.                 Front::MESSAGE_DOMAIN
  233.             );
  234.         } catch (PropelException $e) {
  235.             $this->getParserContext()->setGeneralError($e->getMessage());
  236.         } catch (\Exception $e) {
  237.             $message $this->getTranslator()->trans(
  238.                 'Sorry, an error occured: %s',
  239.                 ['%s' => $e->getMessage()],
  240.                 Front::MESSAGE_DOMAIN
  241.             );
  242.         }
  243.         if ($message !== false) {
  244.             Tlog::getInstance()->error(
  245.                 sprintf('Error during order payment process : %s. Exception was %s'$message$e->getMessage())
  246.             );
  247.             $orderPayment->setErrorMessage($message);
  248.             $this->getParserContext()
  249.                 ->addForm($orderPayment)
  250.                 ->setGeneralError($message)
  251.             ;
  252.         }
  253.         return $this->generateErrorRedirect($orderPayment);
  254.     }
  255.     public function pay(EventDispatcherInterface $eventDispatcher)
  256.     {
  257.         /* check customer */
  258.         $this->checkAuth();
  259.         /* check cart count */
  260.         $this->checkCartNotEmpty($eventDispatcher);
  261.         /* check stock not empty */
  262.         if (true === ConfigQuery::checkAvailableStock()) {
  263.             if (null !== $response $this->checkStockNotEmpty($eventDispatcher)) {
  264.                 return $response;
  265.             }
  266.         }
  267.         /* check delivery address and module */
  268.         $this->checkValidDelivery();
  269.         /* check invoice address and payment module */
  270.         $this->checkValidInvoice();
  271.         $orderEvent $this->getOrderEvent();
  272.         $eventDispatcher->dispatch($orderEventTheliaEvents::ORDER_PAY);
  273.         $placedOrder $orderEvent->getPlacedOrder();
  274.         if (null !== $placedOrder && null !== $placedOrder->getId()) {
  275.             /* order has been placed */
  276.             if ($orderEvent->hasResponse()) {
  277.                 return $orderEvent->getResponse();
  278.             }
  279.             return $this->generateRedirectFromRoute(
  280.                 'order.placed',
  281.                 [],
  282.                 ['order_id' => $orderEvent->getPlacedOrder()->getId()]
  283.             );
  284.         }
  285.         /* order has not been placed */
  286.         return $this->generateRedirectFromRoute('cart.view');
  287.     }
  288.     public function orderPlaced(EventDispatcherInterface $eventDispatcher$order_id): void
  289.     {
  290.         /* check if the placed order matched the customer */
  291.         $placedOrder OrderQuery::create()->findPk(
  292.             $this->getRequest()->attributes->get('order_id')
  293.         );
  294.         if (null === $placedOrder) {
  295.             throw new TheliaProcessException(
  296.                 $this->getTranslator()->trans(
  297.                     'No placed order',
  298.                     [],
  299.                     Front::MESSAGE_DOMAIN
  300.                 ),
  301.                 TheliaProcessException::NO_PLACED_ORDER,
  302.                 $placedOrder
  303.             );
  304.         }
  305.         $customer $this->getSecurityContext()->getCustomerUser();
  306.         if (null === $customer || $placedOrder->getCustomerId() !== $customer->getId()) {
  307.             throw new TheliaProcessException(
  308.                 $this->getTranslator()->trans(
  309.                     'Received placed order id does not belong to the current customer',
  310.                     [],
  311.                     Front::MESSAGE_DOMAIN
  312.                 ),
  313.                 TheliaProcessException::PLACED_ORDER_ID_BAD_CURRENT_CUSTOMER,
  314.                 $placedOrder
  315.             );
  316.         }
  317.         $eventDispatcher->dispatch($this->getOrderEvent(), TheliaEvents::ORDER_CART_CLEAR);
  318.         $this->getParserContext()->set('placed_order_id'$placedOrder->getId());
  319.     }
  320.     public function orderFailed($order_id$message): void
  321.     {
  322.         if (empty($order_id)) {
  323.             // Fallback to request parameter if the method parameter is empty.
  324.             $order_id $this->getRequest()->get('order_id');
  325.         }
  326.         $failedOrder OrderQuery::create()->findPk($order_id);
  327.         if (null !== $failedOrder) {
  328.             $customer $this->getSecurityContext()->getCustomerUser();
  329.             if (null === $customer || $failedOrder->getCustomerId() !== $customer->getId()) {
  330.                 throw new TheliaProcessException(
  331.                     $this->getTranslator()->trans(
  332.                         'Received failed order id does not belong to the current customer',
  333.                         [],
  334.                         Front::MESSAGE_DOMAIN
  335.                     ),
  336.                     TheliaProcessException::PLACED_ORDER_ID_BAD_CURRENT_CUSTOMER,
  337.                     $failedOrder
  338.                 );
  339.             }
  340.         } else {
  341.             Tlog::getInstance()->warning("Failed order ID '$order_id' not found.");
  342.         }
  343.         $this->getParserContext()
  344.             ->set('failed_order_id'$order_id)
  345.             ->set('failed_order_message'$message)
  346.         ;
  347.     }
  348.     protected function getOrderEvent()
  349.     {
  350.         $order $this->getOrder($this->getRequest());
  351.         return new OrderEvent($order);
  352.     }
  353.     public function getOrder(Request $request)
  354.     {
  355.         $session $request->getSession();
  356.         if (null !== $order $session->getOrder()) {
  357.             return $order;
  358.         }
  359.         $order = new Order();
  360.         $session->setOrder($order);
  361.         return $order;
  362.     }
  363.     public function viewAction($order_id)
  364.     {
  365.         $this->checkOrderCustomer($order_id);
  366.         return $this->render('account-order', ['order_id' => $order_id]);
  367.     }
  368.     public function generateInvoicePdf(EventDispatcherInterface $eventDispatcher$order_id)
  369.     {
  370.         $this->checkOrderCustomer($order_id);
  371.         return $this->generateOrderPdf($eventDispatcher$order_idConfigQuery::read('pdf_invoice_file''invoice'));
  372.     }
  373.     public function generateDeliveryPdf(EventDispatcherInterface $eventDispatcher$order_id)
  374.     {
  375.         $this->checkOrderCustomer($order_id);
  376.         return $this->generateOrderPdf($eventDispatcher$order_idConfigQuery::read('pdf_delivery_file''delivery'));
  377.     }
  378.     public function downloadVirtualProduct(EventDispatcherInterface $eventDispatcher$order_product_id)
  379.     {
  380.         if (null !== $orderProduct OrderProductQuery::create()->findPk($order_product_id)) {
  381.             $order $orderProduct->getOrder();
  382.             if ($order->isPaid(false)) {
  383.                 // check customer
  384.                 $this->checkOrderCustomer($order->getId());
  385.                 $virtualProductEvent = new VirtualProductOrderDownloadResponseEvent($orderProduct);
  386.                 $eventDispatcher->dispatch(
  387.                     $virtualProductEvent,
  388.                     TheliaEvents::VIRTUAL_PRODUCT_ORDER_DOWNLOAD_RESPONSE
  389.                 );
  390.                 $response $virtualProductEvent->getResponse();
  391.                 if (!$response instanceof BaseResponse) {
  392.                     throw new \RuntimeException('A Response must be added in the event TheliaEvents::VIRTUAL_PRODUCT_ORDER_DOWNLOAD_RESPONSE');
  393.                 }
  394.                 return $response;
  395.             }
  396.         }
  397.         throw new AccessDeniedHttpException();
  398.     }
  399.     private function checkOrderCustomer($order_id): void
  400.     {
  401.         $this->checkAuth();
  402.         $order OrderQuery::create()->findPk($order_id);
  403.         $valid true;
  404.         if ($order) {
  405.             $customerOrder $order->getCustomer();
  406.             $customer $this->getSecurityContext()->getCustomerUser();
  407.             if ($customerOrder->getId() != $customer->getId()) {
  408.                 $valid false;
  409.             }
  410.         } else {
  411.             $valid false;
  412.         }
  413.         if (false === $valid) {
  414.             throw new AccessDeniedHttpException();
  415.         }
  416.     }
  417.     public function getDeliveryModuleListAjaxAction()
  418.     {
  419.         $this->checkXmlHttpRequest();
  420.         // Change the delivery address if customer has changed it
  421.         $address null;
  422.         $session $this->getSession();
  423.         $addressId $this->getRequest()->get('address_id'null);
  424.         if (null !== $addressId && $addressId !== $session->getOrder()->getChoosenDeliveryAddress()) {
  425.             $address AddressQuery::create()->findPk($addressId);
  426.             if (null !== $address && $address->getCustomerId() === $session->getCustomerUser()->getId()) {
  427.                 $session->getOrder()->setChoosenDeliveryAddress($addressId);
  428.             }
  429.         }
  430.         $address AddressQuery::create()->findPk($session->getOrder()->getChoosenDeliveryAddress());
  431.         $countryId $address->getCountryId();
  432.         $stateId $address->getStateId();
  433.         $args = [
  434.             'country' => $countryId,
  435.             'state' => $stateId,
  436.             'address' => $session->getOrder()->getChoosenDeliveryAddress(),
  437.         ];
  438.         return $this->render('ajax/order-delivery-module-list'$args);
  439.     }
  440.     /**
  441.      * Redirect to cart view if at least one non product is out of stock.
  442.      *
  443.      * @return BaseResponse|null
  444.      */
  445.     private function checkStockNotEmpty(EventDispatcherInterface $eventDispatcher)
  446.     {
  447.         $cart $this->getSession()->getSessionCart($eventDispatcher);
  448.         $cartItems $cart->getCartItems();
  449.         foreach ($cartItems as $cartItem) {
  450.             $pse $cartItem->getProductSaleElements();
  451.             $product $cartItem->getProduct();
  452.             if ($pse->getQuantity() <= && $product->getVirtual() !== 1) {
  453.                 return $this->generateRedirectFromRoute('cart.view');
  454.             }
  455.         }
  456.         return null;
  457.     }
  458.     /**
  459.      * Retrieve the chosen delivery address for a cart or the default customer address if not exists.
  460.      *
  461.      * @return Address|null
  462.      */
  463.     protected function getCustomerAddress()
  464.     {
  465.         $deliveryAddress null;
  466.         $addressId $this->getSession()->getOrder()->getChoosenDeliveryAddress();
  467.         if (null === $addressId) {
  468.             $customer $this->getSecurityContext()->getCustomerUser();
  469.             $deliveryAddress AddressQuery::create()
  470.                 ->filterByCustomerId($customer->getId())
  471.                 ->orderByIsDefault(Criteria::DESC)
  472.                 ->findOne();
  473.             if (null !== $deliveryAddress) {
  474.                 $this->getSession()->getOrder()->setChoosenDeliveryAddress(
  475.                     $deliveryAddress->getId()
  476.                 );
  477.             }
  478.         } else {
  479.             $deliveryAddress AddressQuery::create()->findPk($addressId);
  480.         }
  481.         return $deliveryAddress;
  482.     }
  483. }