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

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