custom/plugins/RpayPayments/src/Components/RatepayApi/Subscriber/PaymentChangeSubscriber.php line 84

Open in your IDE?
  1. <?php
  2. /*
  3.  * Copyright (c) Ratepay GmbH
  4.  *
  5.  * For the full copyright and license information, please view the LICENSE
  6.  * file that was distributed with this source code.
  7.  */
  8. namespace Ratepay\RpayPayments\Components\RatepayApi\Subscriber;
  9. use Exception;
  10. use Monolog\Logger;
  11. use Ratepay\RpayPayments\Components\Checkout\Model\Extension\OrderExtension;
  12. use Ratepay\RpayPayments\Components\Checkout\Model\RatepayOrderDataEntity;
  13. use Ratepay\RpayPayments\Components\Checkout\Model\RatepayOrderLineItemDataEntity;
  14. use Ratepay\RpayPayments\Components\Checkout\Model\RatepayPositionEntity;
  15. use Ratepay\RpayPayments\Components\Checkout\Service\ExtensionService;
  16. use Ratepay\RpayPayments\Components\Logging\Service\HistoryLogger;
  17. use Ratepay\RpayPayments\Components\RatepayApi\Dto\AddCreditData;
  18. use Ratepay\RpayPayments\Components\RatepayApi\Dto\OrderOperationData;
  19. use Ratepay\RpayPayments\Components\RatepayApi\Event\ResponseEvent;
  20. use Ratepay\RpayPayments\Components\RatepayApi\Service\Request\PaymentCancelService;
  21. use Ratepay\RpayPayments\Components\RatepayApi\Service\Request\PaymentCreditService;
  22. use Ratepay\RpayPayments\Components\RatepayApi\Service\Request\PaymentDeliverService;
  23. use Ratepay\RpayPayments\Components\RatepayApi\Service\Request\PaymentReturnService;
  24. use Ratepay\RpayPayments\Util\CriteriaHelper;
  25. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  26. use Shopware\Core\Checkout\Cart\Order\RecalculationService;
  27. use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
  28. use Shopware\Core\Framework\Context;
  29. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  30. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  31. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  32. class PaymentChangeSubscriber implements EventSubscriberInterface
  33. {
  34.     private EntityRepositoryInterface $productRepository;
  35.     private EntityRepositoryInterface $orderRepository;
  36.     private Logger $logger;
  37.     private HistoryLogger $historyLogger;
  38.     private RecalculationService $recalculationService;
  39.     private EventDispatcherInterface $eventDispatcher;
  40.     private EntityRepositoryInterface $ratepayPositionRepository;
  41.     private ExtensionService $extensionService;
  42.     public function __construct(
  43.         EventDispatcherInterface $eventDispatcher,
  44.         EntityRepositoryInterface $productRepository,
  45.         EntityRepositoryInterface $orderRepository,
  46.         EntityRepositoryInterface $ratepayPositionRepository,
  47.         ExtensionService $extensionService,
  48.         RecalculationService $recalculationService,
  49.         Logger $logger,
  50.         HistoryLogger $historyLogger
  51.     ) {
  52.         $this->eventDispatcher $eventDispatcher;
  53.         $this->productRepository $productRepository;
  54.         $this->orderRepository $orderRepository;
  55.         $this->recalculationService $recalculationService;
  56.         $this->logger $logger;
  57.         $this->historyLogger $historyLogger;
  58.         $this->ratepayPositionRepository $ratepayPositionRepository;
  59.         $this->extensionService $extensionService;
  60.     }
  61.     public static function getSubscribedEvents(): array
  62.     {
  63.         return [
  64.             PaymentCancelService::EVENT_SUCCESSFUL => 'onSuccess',
  65.             PaymentDeliverService::EVENT_SUCCESSFUL => 'onSuccess',
  66.             PaymentReturnService::EVENT_SUCCESSFUL => 'onSuccess',
  67.             PaymentCreditService::EVENT_SUCCESSFUL => 'onSuccessAddCredit',
  68.         ];
  69.     }
  70.     public function onSuccess(ResponseEvent $event): void
  71.     {
  72.         /** @var OrderOperationData $requestData */
  73.         $requestData $event->getRequestData();
  74.         $positionUpdates = [];
  75.         /* @var OrderLineItemEntity $item */
  76.         foreach ($requestData->getItems() as $id => $qty) {
  77.             if ($id === OrderOperationData::ITEM_ID_SHIPPING) {
  78.                 /** @var RatepayOrderDataEntity $ratepayData */
  79.                 $ratepayData $requestData->getOrder()->getExtension(OrderExtension::EXTENSION_NAME);
  80.                 $position $ratepayData->getShippingPosition();
  81.                 $productName $id;
  82.                 $productNumber $id;
  83.             } else {
  84.                 /** @var OrderLineItemEntity $lineItem */
  85.                 $lineItem $requestData->getOrder()->getLineItems()->get($id);
  86.                 /** @var RatepayOrderLineItemDataEntity $ratepayData */
  87.                 $ratepayData $lineItem->getExtension(OrderExtension::EXTENSION_NAME);
  88.                 if ($ratepayData === null) {
  89.                     // will occur if the item has been just added to the order
  90.                     $ratepayData $this->extensionService->createLineItemExtensionEntities([$lineItem], $event->getContext())->first();
  91.                 }
  92.                 $position $ratepayData->getPosition();
  93.                 $productName $lineItem->getLabel();
  94.                 $productNumber $lineItem->getPayload()['productNumber'] ?? $id;
  95.             }
  96.             $updateData $this->getPositionUpdates($requestData$position$qty);
  97.             $updateData[RatepayPositionEntity::FIELD_ID] = $position->getId();
  98.             $positionUpdates[] = $updateData;
  99.             // todo trigger event
  100.             $this->historyLogger->logHistory(
  101.                 $event->getContext(),
  102.                 $requestData->getOrder()->getId(),
  103.                 $requestData->getOperation(),
  104.                 $productName,
  105.                 $productNumber,
  106.                 $qty
  107.             );
  108.         }
  109.         if (count($positionUpdates)) {
  110.             $this->ratepayPositionRepository->upsert($positionUpdates$event->getContext());
  111.         }
  112.         if ($requestData->isUpdateStock()) {
  113.             $this->updateProductStocks($event->getContext(), $requestData);
  114.         }
  115.     }
  116.     public function onSuccessAddCredit(ResponseEvent $event): void
  117.     {
  118.         /** @var AddCreditData $requestData */
  119.         $requestData $event->getRequestData();
  120.         $versionId $this->orderRepository->createVersion($requestData->getOrder()->getId(), $event->getContext());
  121.         $versionContext $event->getContext()->createWithVersionId($versionId);
  122.         $newItems = [];
  123.         /** @var LineItem $item */
  124.         foreach ($requestData->getItems() as $item) {
  125.             $this->recalculationService->addCustomLineItem($requestData->getOrder()->getId(), $item$versionContext);
  126.             $newItems[$item->getId()] = $item->getPriceDefinition()->getQuantity();
  127.         }
  128.         // recalculate the whole order. (without this, shipping costs will added to the order if there is a shipping free position - RATESWSX-71)
  129.         $this->recalculationService->recalculateOrder($requestData->getOrder()->getId(), $versionContext);
  130.         // merge the order with the SYSTEM_SCOPE, cause the recalculateOrder locked the order with this scope.
  131.         $event->getContext()->scope(Context::SYSTEM_SCOPE, function (Context $context) use ($versionId): void {
  132.             $this->orderRepository->merge($versionId$context);
  133.         });
  134.         $reloadedOrder $this->orderRepository->search(
  135.             CriteriaHelper::getCriteriaForOrder($requestData->getOrder()->getId()),
  136.             $event->getContext()
  137.         )->first();
  138.         // trigger deliver event
  139.         $this->eventDispatcher->dispatch(new ResponseEvent(
  140.             $event->getContext(),
  141.             $event->getRequestBuilder(),
  142.             new OrderOperationData($event->getContext(), $reloadedOrderOrderOperationData::OPERATION_ADD$newItemsfalse)
  143.         ), PaymentDeliverService::EVENT_SUCCESSFUL);
  144.     }
  145.     protected function getPositionUpdates(OrderOperationData $requestDataRatepayPositionEntity $position$qty): array
  146.     {
  147.         $updates = [];
  148.         switch ($requestData->getOperation()) {
  149.             case OrderOperationData::OPERATION_ADD:
  150.             case OrderOperationData::OPERATION_DELIVER:
  151.                 $updates[RatepayPositionEntity::FIELD_DELIVERED] = $position->getDelivered() + $qty;
  152.                 break;
  153.             case OrderOperationData::OPERATION_CANCEL:
  154.                 $updates[RatepayPositionEntity::FIELD_CANCELED] = $position->getCanceled() + $qty;
  155.                 break;
  156.             case OrderOperationData::OPERATION_RETURN:
  157.                 $updates[RatepayPositionEntity::FIELD_RETURNED] = $position->getReturned() + $qty;
  158.                 break;
  159.         }
  160.         return $updates;
  161.     }
  162.     protected function updateProductStocks(Context $contextOrderOperationData $requestData): void
  163.     {
  164.         $items $requestData->getItems();
  165.         unset($items[OrderOperationData::ITEM_ID_SHIPPING]); // "shipping" is not a valid uuid - maybe an error will throw (in the future)
  166.         $lineItems $requestData->getOrder()->getLineItems()->getList(array_keys($items));
  167.         $data = [];
  168.         /** @var OrderLineItemEntity $item */
  169.         foreach ($lineItems as $item) {
  170.             if ($item->getProduct()) {
  171.                 // verify if the product still exists
  172.                 $data[] = [
  173.                     'id' => $item->getProduct()->getId(),
  174.                     'stock' => $item->getProduct()->getStock() + $requestData->getItems()[$item->getId()],
  175.                 ];
  176.             }
  177.         }
  178.         if (count($data) === 0) {
  179.             // nothing to do
  180.             return;
  181.         }
  182.         try {
  183.             $this->productRepository->update($data$context);
  184.         } catch (Exception $e) {
  185.             // todo trigger event
  186.             $this->logger->error('Error during the updating of the stock', [
  187.                 'message' => $e->getMessage(),
  188.                 'orderId' => $requestData->getOrder()->getId(),
  189.                 'orderNumber' => $requestData->getOrder()->getOrderNumber(),
  190.                 'items' => $requestData->getItems(),
  191.                 'trace' => $e->getTraceAsString(),
  192.             ]);
  193.         }
  194.     }
  195. }