vendor/pimcore/pimcore/bundles/EcommerceFrameworkBundle/CheckoutManager/CheckoutManager.php line 483

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Enterprise License (PEL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  * @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  * @license    http://www.pimcore.org/license     GPLv3 and PEL
  13.  */
  14. namespace Pimcore\Bundle\EcommerceFrameworkBundle\CheckoutManager;
  15. use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartInterface;
  16. use Pimcore\Bundle\EcommerceFrameworkBundle\EnvironmentInterface;
  17. use Pimcore\Bundle\EcommerceFrameworkBundle\Exception\UnsupportedException;
  18. use Pimcore\Bundle\EcommerceFrameworkBundle\Model\AbstractOrder;
  19. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\Order\Agent;
  20. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\OrderManager;
  21. use Pimcore\Bundle\EcommerceFrameworkBundle\OrderManager\OrderManagerLocatorInterface;
  22. use Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\Payment\PaymentInterface;
  23. use Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\Payment\QPay;
  24. use Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\StatusInterface;
  25. use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\Price;
  26. use Pimcore\Bundle\EcommerceFrameworkBundle\Type\Decimal;
  27. use Pimcore\Event\Ecommerce\CheckoutManagerEvents;
  28. use Pimcore\Event\Model\Ecommerce\CheckoutManagerStepsEvent;
  29. use Pimcore\Model\DataObject\Fieldcollection\Data\PaymentInfo;
  30. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  31. class CheckoutManager implements CheckoutManagerInterface
  32. {
  33.     /**
  34.      * Constants for custom environment item names for persisting state of checkout
  35.      * always concatenated with current cart id
  36.      */
  37.     const CURRENT_STEP 'checkout_current_step';
  38.     const FINISHED 'checkout_finished';
  39.     /**
  40.      * @var CartInterface
  41.      */
  42.     protected $cart;
  43.     /**
  44.      * @var EnvironmentInterface
  45.      */
  46.     protected $environment;
  47.     /**
  48.      * @var OrderManagerLocatorInterface
  49.      */
  50.     protected $orderManagers;
  51.     /**
  52.      * @var CommitOrderProcessorLocatorInterface
  53.      */
  54.     protected $commitOrderProcessors;
  55.     /**
  56.      * Payment Provider
  57.      *
  58.      * @var PaymentInterface
  59.      */
  60.     protected $payment;
  61.     /**
  62.      * Needed for effective access to one specific checkout step
  63.      *
  64.      * @var CheckoutStepInterface[]
  65.      */
  66.     protected $checkoutSteps = [];
  67.     /**
  68.      * Needed for preserving order of checkout steps
  69.      *
  70.      * @var CheckoutStepInterface[]
  71.      */
  72.     protected $checkoutStepOrder = [];
  73.     /**
  74.      * @var CheckoutStepInterface
  75.      */
  76.     protected $currentStep;
  77.     /**
  78.      * @var bool
  79.      */
  80.     protected $finished false;
  81.     /**
  82.      * @var bool
  83.      */
  84.     protected $paid true;
  85.     /**
  86.      * @var EventDispatcherInterface
  87.      */
  88.     protected $eventDispatcher;
  89.     /**
  90.      * CheckoutManager constructor.
  91.      *
  92.      * @param CartInterface $cart
  93.      * @param EnvironmentInterface $environment
  94.      * @param OrderManagerLocatorInterface $orderManagers
  95.      * @param CommitOrderProcessorLocatorInterface $commitOrderProcessors
  96.      * @param array $checkoutSteps
  97.      * @param EventDispatcherInterface $eventDispatcher
  98.      * @param PaymentInterface|null $paymentProvider
  99.      */
  100.     public function __construct(
  101.         CartInterface $cart,
  102.         EnvironmentInterface $environment,
  103.         OrderManagerLocatorInterface $orderManagers,
  104.         CommitOrderProcessorLocatorInterface $commitOrderProcessors,
  105.         array $checkoutSteps,
  106.         EventDispatcherInterface $eventDispatcher,
  107.         PaymentInterface $paymentProvider null
  108.     ) {
  109.         @trigger_error(
  110.             'Class ' self::class . ' is deprecated since version 6.1.0 and will be removed in 7.0.0. ' .
  111.             ' Use ' . \Pimcore\Bundle\EcommerceFrameworkBundle\CheckoutManager\V7\CheckoutManager::class . ' class instead.',
  112.             E_USER_DEPRECATED
  113.         );
  114.         $this->cart $cart;
  115.         $this->environment $environment;
  116.         $this->orderManagers $orderManagers;
  117.         $this->commitOrderProcessors $commitOrderProcessors;
  118.         $this->payment $paymentProvider;
  119.         $this->eventDispatcher $eventDispatcher;
  120.         $this->setCheckoutSteps($checkoutSteps);
  121.     }
  122.     /**
  123.      * @param CheckoutStepInterface[] $checkoutSteps
  124.      */
  125.     protected function setCheckoutSteps(array $checkoutSteps)
  126.     {
  127.         if (empty($checkoutSteps)) {
  128.             return;
  129.         }
  130.         foreach ($checkoutSteps as $checkoutStep) {
  131.             $this->addCheckoutStep($checkoutStep);
  132.         }
  133.         $this->initializeStepState();
  134.     }
  135.     protected function addCheckoutStep(CheckoutStepInterface $checkoutStep)
  136.     {
  137.         $this->checkoutStepOrder[] = $checkoutStep;
  138.         $this->checkoutSteps[$checkoutStep->getName()] = $checkoutStep;
  139.     }
  140.     protected function initializeStepState()
  141.     {
  142.         // getting state information for checkout from custom environment items
  143.         $this->finished = (bool) ($this->environment->getCustomItem(self::FINISHED '_' $this->cart->getId(), false));
  144.         if ($currentStepItem $this->environment->getCustomItem(self::CURRENT_STEP '_' $this->cart->getId())) {
  145.             if (!isset($this->checkoutSteps[$currentStepItem])) {
  146.                 throw new \RuntimeException(sprintf(
  147.                     'Environment defines current step as "%s", but step "%s" does not exist',
  148.                     $currentStepItem,
  149.                     $currentStepItem
  150.                 ));
  151.             }
  152.             $this->currentStep $this->checkoutSteps[$currentStepItem];
  153.         }
  154.         // if no step is set and cart is not finished -> set current step to first step of checkout
  155.         if (null === $this->currentStep && !$this->isFinished()) {
  156.             $this->currentStep $this->checkoutStepOrder[0];
  157.         }
  158.         $event = new CheckoutManagerStepsEvent($this$this->currentStep);
  159.         $this->eventDispatcher->dispatch(CheckoutManagerEvents::INITIALIZE_STEP_STATE$event);
  160.         $this->currentStep $event->getCurrentStep();
  161.     }
  162.     /**
  163.      * @inheritdoc
  164.      */
  165.     public function hasActivePayment()
  166.     {
  167.         $orderManager $this->orderManagers->getOrderManager();
  168.         $order $orderManager->getOrderFromCart($this->cart);
  169.         if ($order) {
  170.             $paymentInfo $orderManager->createOrderAgent($order)->getCurrentPendingPaymentInfo();
  171.             return !empty($paymentInfo);
  172.         }
  173.         return false;
  174.     }
  175.     /**
  176.      * @return AbstractOrder
  177.      *
  178.      * @throws UnsupportedException
  179.      */
  180.     protected function checkIfPaymentIsPossible()
  181.     {
  182.         if (!$this->isFinished()) {
  183.             throw new UnsupportedException('Checkout is not finished yet.');
  184.         }
  185.         if (!$this->payment) {
  186.             throw new UnsupportedException('Payment is not activated');
  187.         }
  188.         // create order
  189.         $orderManager $this->orderManagers->getOrderManager();
  190.         $order $orderManager->getOrCreateOrderFromCart($this->cart);
  191.         if ($order->getOrderState() === AbstractOrder::ORDER_STATE_COMMITTED) {
  192.             throw new UnsupportedException('Order is already committed');
  193.         }
  194.         return $order;
  195.     }
  196.     /**
  197.      * @inheritdoc
  198.      */
  199.     public function initOrderPayment()
  200.     {
  201.         $order $this->checkIfPaymentIsPossible();
  202.         $orderManager $this->orderManagers->getOrderManager();
  203.         $orderAgent $orderManager->createOrderAgent($order);
  204.         $paymentInfo $orderAgent->initPayment();
  205.         return $paymentInfo;
  206.     }
  207.     /**
  208.      * @inheritdoc
  209.      */
  210.     public function startOrderPayment()
  211.     {
  212.         $order $this->checkIfPaymentIsPossible();
  213.         $orderManager $this->orderManagers->getOrderManager();
  214.         $orderAgent $orderManager->createOrderAgent($order);
  215.         $paymentInfo $orderAgent->startPayment();
  216.         // always set order state to payment pending when calling start payment
  217.         if ($order->getOrderState() != $order::ORDER_STATE_PAYMENT_PENDING) {
  218.             $order->setOrderState($order::ORDER_STATE_PAYMENT_PENDING);
  219.             $order->save(['versionNote' => 'CheckoutManager::startOrderPayment - set order state to ' $order::ORDER_STATE_PAYMENT_PENDING '.']);
  220.         }
  221.         return $paymentInfo;
  222.     }
  223.     /**
  224.      * @inheritdoc
  225.      */
  226.     public function cancelStartedOrderPayment()
  227.     {
  228.         $orderManager $this->orderManagers->getOrderManager();
  229.         $order $orderManager->getOrderFromCart($this->cart);
  230.         if ($order) {
  231.             $orderAgent $orderManager->createOrderAgent($order);
  232.             return $orderAgent->cancelStartedOrderPayment();
  233.         }
  234.     }
  235.     /**
  236.      * @inheritdoc
  237.      */
  238.     public function getOrder()
  239.     {
  240.         return $this->orderManagers->getOrderManager()->getOrCreateOrderFromCart($this->cart);
  241.     }
  242.     /**
  243.      * Updates and cleans up environment after order is committed
  244.      *
  245.      * @param AbstractOrder $order
  246.      */
  247.     protected function updateEnvironmentAfterOrderCommit(?AbstractOrder $order)
  248.     {
  249.         $this->validateCheckoutSteps();
  250.         if (empty($order) || empty($order->getOrderState())) {
  251.             // if payment not successful -> set current checkout step to last step and checkout to not finished
  252.             // last step must be committed again in order to restart payment or e.g. commit without payment?
  253.             $this->currentStep $this->checkoutStepOrder[count($this->checkoutStepOrder) - 1];
  254.             $this->environment->setCustomItem(self::CURRENT_STEP '_' $this->cart->getId(), $this->currentStep->getName());
  255.         } else {
  256.             $this->cart->delete();
  257.             $this->environment->removeCustomItem(self::CURRENT_STEP '_' $this->cart->getId());
  258.         }
  259.         $this->environment->save();
  260.     }
  261.     /**
  262.      * @inheritdoc
  263.      */
  264.     public function handlePaymentResponseAndCommitOrderPayment($paymentResponseParams)
  265.     {
  266.         $this->validateCheckoutSteps();
  267.         $commitOrderProcessor $this->commitOrderProcessors->getCommitOrderProcessor();
  268.         // check if order is already committed and payment information with same internal payment id has same state
  269.         // if so, do nothing and return order
  270.         if ($committedOrder $commitOrderProcessor->committedOrderWithSamePaymentExists($paymentResponseParams$this->getPayment())) {
  271.             return $committedOrder;
  272.         }
  273.         if (!$this->payment) {
  274.             throw new UnsupportedException('Payment is not activated');
  275.         }
  276.         // delegate commit order to commit order processor
  277.         $order null;
  278.         try {
  279.             $order $commitOrderProcessor->handlePaymentResponseAndCommitOrderPayment($paymentResponseParams$this->getPayment());
  280.         } catch (\Exception $e) {
  281.             throw $e;
  282.         } finally {
  283.             $this->updateEnvironmentAfterOrderCommit($order);
  284.         }
  285.         return $order;
  286.     }
  287.     /**
  288.      * Verifies if the payment provider is supported for recurring payment
  289.      *
  290.      * @param PaymentInterface $provider
  291.      * @param AbstractOrder $sourceOrder
  292.      *
  293.      * @throws \Exception
  294.      */
  295.     protected function verifyRecurringPayment(PaymentInterface $providerAbstractOrder $sourceOrderstring $customerId)
  296.     {
  297.         /* @var OrderManager $orderManager */
  298.         $orderManager $this->orderManagers->getOrderManager();
  299.         if (!$provider->isRecurringPaymentEnabled()) {
  300.             throw new \Exception("Recurring Payment is not enabled or is not supported by payment provider [{$provider->getName()}].");
  301.         }
  302.         if (!$orderManager->isValidOrderForRecurringPayment($sourceOrder$this->getPayment(), $customerId)) {
  303.             throw new \Exception('The given source order is not valid for recurring payment.');
  304.         }
  305.     }
  306.     /**
  307.      * @param AbstractOrder $sourceOrder
  308.      * @param string $customerId
  309.      *
  310.      * @return null|AbstractOrder
  311.      */
  312.     public function startAndCommitRecurringOrderPayment(AbstractOrder $sourceOrderstring $customerId)
  313.     {
  314.         /* @var PaymentInfo $targetPaymentInfo */
  315.         $targetPaymentInfo $this->startOrderPayment();
  316.         /* @var OrderManager $orderManager */
  317.         $orderManager $this->orderManagers->getOrderManager();
  318.         /* @var Agent $sourceOrderAgent */
  319.         $sourceOrderAgent $orderManager->createOrderAgent($sourceOrder);
  320.         /* @var QPay $paymentProvider */
  321.         $paymentProvider $sourceOrderAgent->getPaymentProvider();
  322.         $this->verifyRecurringPayment($paymentProvider$sourceOrder$customerId);
  323.         $targetOrder $orderManager->getOrderFromCart($this->getCart());
  324.         /* @var Agent $targetOrderAgent */
  325.         $targetOrderAgent $orderManager->createOrderAgent($targetOrder);
  326.         $targetOrderAgent->setPaymentProvider($paymentProvider$sourceOrder);
  327.         $price = new Price(
  328.             Decimal::create($targetOrder->getTotalPrice(), 2),
  329.             $sourceOrderAgent->getCurrency()
  330.         );
  331.         // execute recurPayment operation
  332.         $paymentStatus $paymentProvider->executeDebit(
  333.             $price,
  334.             $targetPaymentInfo->getInternalPaymentId()
  335.         );
  336.         $targetOrderAgent->updatePayment($paymentStatus);
  337.         // delegate commit order to commit order processor
  338.         $targetOrder $this->commitOrderProcessors->getCommitOrderProcessor()->commitOrderPayment($paymentStatus$this->getPayment());
  339.         $this->updateEnvironmentAfterOrderCommit($targetOrder);
  340.         return $targetOrder;
  341.     }
  342.     /**
  343.      * @inheritdoc
  344.      */
  345.     public function commitOrderPayment(StatusInterface $statusAbstractOrder $sourceOrder null)
  346.     {
  347.         $this->validateCheckoutSteps();
  348.         if (!$this->payment) {
  349.             throw new UnsupportedException('Payment is not activated');
  350.         }
  351.         if ($this->isCommitted()) {
  352.             throw new UnsupportedException('Order already committed.');
  353.         }
  354.         if (!$this->isFinished()) {
  355.             throw new UnsupportedException('Checkout not finished yet.');
  356.         }
  357.         // delegate commit order to commit order processor
  358.         $order $this->commitOrderProcessors->getCommitOrderProcessor()->commitOrderPayment($status$this->getPayment(), $sourceOrder);
  359.         $this->updateEnvironmentAfterOrderCommit($order);
  360.         return $order;
  361.     }
  362.     /**
  363.      * @inheritdoc
  364.      */
  365.     public function commitOrder()
  366.     {
  367.         $this->validateCheckoutSteps();
  368.         if ($this->isCommitted()) {
  369.             throw new UnsupportedException('Order already committed.');
  370.         }
  371.         if (!$this->isFinished()) {
  372.             throw new UnsupportedException('Checkout not finished yet.');
  373.         }
  374.         // delegate commit order to commit order processor
  375.         $order $this->orderManagers->getOrderManager()->getOrCreateOrderFromCart($this->cart);
  376.         $order $this->commitOrderProcessors->getCommitOrderProcessor()->commitOrder($order);
  377.         $this->updateEnvironmentAfterOrderCommit($order);
  378.         return $order;
  379.     }
  380.     /**
  381.      * @inheritdoc
  382.      */
  383.     public function commitStep(CheckoutStepInterface $step$data)
  384.     {
  385.         $this->validateCheckoutSteps();
  386.         $event = new CheckoutManagerStepsEvent($this$step, ['data' => $data]);
  387.         $this->eventDispatcher->dispatch(CheckoutManagerEvents::PRE_COMMIT_STEP$event);
  388.         $data $event->getArgument('data');
  389.         // get index of current step and index of step to commit
  390.         $indexCurrentStep array_search($this->currentStep$this->checkoutStepOrder);
  391.         $index array_search($step$this->checkoutStepOrder);
  392.         // if indexCurrentStep is < index -> there are uncommitted previous steps
  393.         if ($indexCurrentStep $index) {
  394.             throw new UnsupportedException('There are uncommitted previous steps.');
  395.         }
  396.         // delegate commit to step implementation (for data storage etc.)
  397.         $result $step->commit($data);
  398.         if ($result) {
  399.             $index++;
  400.             if (count($this->checkoutStepOrder) > $index) {
  401.                 //setting checkout manager to next step
  402.                 $this->currentStep $this->checkoutStepOrder[$index];
  403.                 $this->environment->setCustomItem(
  404.                     self::CURRENT_STEP '_' $this->cart->getId(),
  405.                     $this->currentStep->getName()
  406.                 );
  407.                 // checkout NOT finished
  408.                 $this->finished false;
  409.             } else {
  410.                 // checkout is finished
  411.                 $this->finished true;
  412.             }
  413.             $this->environment->setCustomItem(
  414.                 self::FINISHED '_' $this->cart->getId(),
  415.                 $this->finished
  416.             );
  417.             $this->cart->save();
  418.             $this->environment->save();
  419.         }
  420.         $event = new CheckoutManagerStepsEvent($this$step, ['data' => $data]);
  421.         $this->eventDispatcher->dispatch(CheckoutManagerEvents::POST_COMMIT_STEP$event);
  422.         return $result;
  423.     }
  424.     /**
  425.      * @inheritdoc
  426.      */
  427.     public function getCart()
  428.     {
  429.         return $this->cart;
  430.     }
  431.     /**
  432.      * @inheritdoc
  433.      */
  434.     public function getCheckoutStep($stepName)
  435.     {
  436.         return $this->checkoutSteps[$stepName] ?? null;
  437.     }
  438.     /**
  439.      * @inheritdoc
  440.      */
  441.     public function getCheckoutSteps()
  442.     {
  443.         return $this->checkoutStepOrder;
  444.     }
  445.     /**
  446.      * @inheritdoc
  447.      */
  448.     public function getCurrentStep()
  449.     {
  450.         $this->validateCheckoutSteps();
  451.         return $this->currentStep;
  452.     }
  453.     protected function validateCheckoutSteps()
  454.     {
  455.         if (empty($this->checkoutSteps)) {
  456.             throw new \RuntimeException('Checkout manager does not define any checkout steps');
  457.         }
  458.     }
  459.     /**
  460.      * @inheritdoc
  461.      */
  462.     public function isFinished()
  463.     {
  464.         return $this->finished;
  465.     }
  466.     /**
  467.      * @inheritdoc
  468.      */
  469.     public function isCommitted()
  470.     {
  471.         $order $this->orderManagers->getOrderManager()->getOrderFromCart($this->cart);
  472.         return $order && $order->getOrderState() === $order::ORDER_STATE_COMMITTED;
  473.     }
  474.     /**
  475.      * @inheritdoc
  476.      */
  477.     public function getPayment()
  478.     {
  479.         return $this->payment;
  480.     }
  481.     /**
  482.      * @inheritdoc
  483.      */
  484.     public function cleanUpPendingOrders()
  485.     {
  486.         $this->commitOrderProcessors->getCommitOrderProcessor()->cleanUpPendingOrders();
  487.     }
  488. }