custom/plugins/ChannelpilotTrackingSW6/src/Subscriber/DeliveryStateSubscriber.php line 107

Open in your IDE?
  1. <?php
  2. namespace Chann\Channelpilot\Subscriber;
  3. use Chann\Channelpilot\Service\ReturnTrackingService;
  4. use Monolog\Logger;
  5. use Shopware\Core\Checkout\Cart\Exception\OrderDeliveryNotFoundException;
  6. use Shopware\Core\Checkout\Cart\Exception\OrderNotFoundException;
  7. use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity;
  8. use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
  9. use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler;
  10. use Shopware\Core\Checkout\Order\OrderEntity;
  11. use Shopware\Core\Framework\Context;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  13. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  15. use Shopware\Core\System\StateMachine\Aggregation\StateMachineTransition\StateMachineTransitionActions;
  16. use Shopware\Core\System\StateMachine\Event\StateMachineStateChangeEvent;
  17. use Shopware\Core\System\SystemConfig\SystemConfigService;
  18. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  19. class DeliveryStateSubscriber implements EventSubscriberInterface
  20. {
  21.     /**
  22.      * @var OrderTransactionStateHandler
  23.      */
  24.     protected $transactionStateHandler;
  25.     /**
  26.      * @var EntityRepository
  27.      */
  28.     protected $orderTransactionRepository;
  29.     /**
  30.      * @var EntityRepository
  31.      */
  32.     protected $orderRepository;
  33.     /**
  34.      * @var EntityRepository
  35.      */
  36.     protected EntityRepository $deliveryRepository;
  37.     /**
  38.      * @var Logger
  39.      */
  40.     protected Logger $logger;
  41.     /**
  42.      * @var EntityRepositoryInterface
  43.      */
  44.     protected EntityRepositoryInterface $salesChannelRepository;
  45.     /**
  46.      * @var SystemConfigService
  47.      */
  48.     protected SystemConfigService $systemConfigService;
  49.     /**
  50.      * @var int Avoids multiple attempts to cancel the same order
  51.      */
  52.     protected $previousAttempts 0;
  53.     private ReturnTrackingService $returnTrackingService;
  54.     /**
  55.      * @param Logger $logger
  56.      * @param SystemConfigService $systemConfigService
  57.      * @param EntityRepository $transactionRepository
  58.      * @param EntityRepository $orderRepository
  59.      * @param EntityRepository $deliveryRepository
  60.      * @param OrderTransactionStateHandler $transactionStateHandler
  61.      * @param EntityRepositoryInterface $salesChannelRepository
  62.      */
  63.     public function __construct(
  64.         Logger $logger,
  65.         SystemConfigService $systemConfigService,
  66.         EntityRepository $transactionRepository,
  67.         EntityRepository $orderRepository,
  68.         EntityRepository $deliveryRepository,
  69.         OrderTransactionStateHandler $transactionStateHandler,
  70.         EntityRepositoryInterface $salesChannelRepository,
  71.         ReturnTrackingService $returnTrackingService
  72.     ) {
  73.         $this->logger $logger;
  74.         $this->orderTransactionRepository $transactionRepository;
  75.         $this->orderRepository $orderRepository;
  76.         $this->deliveryRepository $deliveryRepository;
  77.         $this->transactionStateHandler $transactionStateHandler;
  78.         $this->salesChannelRepository $salesChannelRepository;
  79.         $this->systemConfigService $systemConfigService;
  80.         $this->returnTrackingService $returnTrackingService;
  81.         require_once __DIR__ '/../Service/API/ChannelPilotSellerAPI_v4_0.php';
  82.     }
  83.     /**
  84.      * @return array The event names to listen to
  85.      */
  86.     public static function getSubscribedEvents()
  87.     {
  88.         return [
  89.             'state_machine.order.state_changed' => 'onOrderStateChange',
  90.             'state_machine.order_transaction.state_changed' => 'onOrderTransactionStateChange',
  91.             'state_machine.order_delivery.state_changed' => 'onOrderDeliveryStateChange',
  92.         ];
  93.     }
  94.     /**
  95.      * @param StateMachineStateChangeEvent $event
  96.      * @throws \Exception
  97.      */
  98.     public function onOrderDeliveryStateChange(StateMachineStateChangeEvent $event): void
  99.     {
  100.         try {
  101.             // Execute only once per Event
  102.             if ($event->getTransitionSide() !== StateMachineStateChangeEvent::STATE_MACHINE_TRANSITION_SIDE_LEAVE) {
  103.                 return;
  104.             }
  105.             //Don't execute on "fully shipped" event if it comes from a "partially shipped" event.
  106.             if ($event->getPreviousState()->getTechnicalName() === 'shipped_partially'
  107.                 && $event->getNextState()->getTechnicalName() === 'shipped') {
  108.                 return;
  109.             }
  110.             //Don't execute if it's not set to "fully shipped" or "partially shipped"
  111.             $transitionName $event->getTransition()->getTransitionName(); // ship_partially, ship
  112.             if (StateMachineTransitionActions::ACTION_SHIP !== $transitionName
  113.                 && StateMachineTransitionActions::ACTION_SHIP_PARTIALLY !== $transitionName) {
  114.                 return;
  115.             }
  116.             $orderDeliveryId $event->getTransition()->getEntityId();
  117.             $criteria = new Criteria([$orderDeliveryId]);
  118.             $criteria->addAssociation('order');
  119.             $criteria->addAssociation('order.orderCustomer');
  120.             $criteria->addAssociation('order.transactions');
  121.             $criteria->addAssociation('order.lineItems');
  122.             $criteria->addAssociation('shippingMethod');
  123.             /** @var OrderDeliveryEntity|null $orderDelivery */
  124.             $orderDelivery $this->deliveryRepository
  125.                 ->search($criteria$event->getContext())
  126.                 ->first();
  127.             if ($orderDelivery === null) {
  128.                 throw new OrderDeliveryNotFoundException($orderDeliveryId);
  129.             }
  130.             if ($orderDelivery->getOrder() === null) {
  131.                 throw new OrderNotFoundException($orderDeliveryId);
  132.             }
  133.             $order $orderDelivery->getOrder();
  134.             if (!$order->getCustomFields() || !isset($order->getCustomFields()['custom_channelpilot_token'])) {
  135.                 // No Channelpilot Order
  136.                 return;
  137.             }
  138.             $trackingCodes $orderDelivery->getTrackingCodes() ?: [];
  139.             $latestTrackingCode \array_pop($trackingCodes);
  140.             $carrierName $orderDelivery->getShippingMethod() && $orderDelivery->getShippingMethod()->getName()
  141.                 ? $orderDelivery->getShippingMethod()->getName()
  142.                 : '';
  143.             $delivery = new \CPDelivery(
  144.                 $order->getOrderNumber(),
  145.                 $order->getCustomFields()['custom_channelpilot_source'],
  146.                 true,
  147.                 $latestTrackingCode ?: '--------',
  148.                 (new \DateTime())->format('c'),
  149.                 $carrierName,
  150.                 $this->returnTrackingService->getReturnTrackingIds($order->getId(), $event->getContext())
  151.             );
  152.             $customFields $order->getCustomFields();
  153.             $customFields['custom_channelpilot_delivery2send'] = serialize($delivery);
  154.             $this->orderRepository->update(
  155.                 [
  156.                     [
  157.                         'id' => $order->getId(),
  158.                         'customFields' => $customFields
  159.                     ]
  160.                 ],
  161.                 Context::createDefaultContext()
  162.             );
  163.             $this->logger->info('Marked order ID ' $order->getId() . ' for delivery');
  164.         } catch (\Exception $e) {
  165.             $this->logger->error($e);
  166.             throw $e;
  167.         }
  168.     }
  169.     /**
  170.      * New Approach on SW 6.4.11
  171.      * refund / Cancel payment
  172.      * @param StateMachineStateChangeEvent $args
  173.      */
  174.     public function onOrderTransactionStateChange(StateMachineStateChangeEvent $args): void
  175.     {
  176.         try {
  177.             if ($this->previousAttempts) {
  178.                 return;
  179.             }
  180.             $this->previousAttempts 1;
  181.             if (strtolower($args->getTransition()->getEntityName()) !== 'order_transaction') {
  182.                 return;
  183.             }
  184.             if (strtolower($args->getNextState()->getTechnicalName()) !== 'refunded') {
  185.                 return;
  186.             }
  187.             // We have a cancelled order.
  188.             // Load order
  189.             $orderTransactionId $args->getTransition()->getEntityId();
  190.             $criteria = new Criteria([$orderTransactionId]);
  191.             $criteria->addAssociation('order');
  192.             /** @var OrderTransactionEntity $orderTransaction */
  193.             $orderTransaction $this->orderTransactionRepository
  194.                 ->search($criteria$args->getContext())
  195.                 ->first();
  196.             $order $orderTransaction->getOrder();
  197.             if (!$order) {
  198.                 return;
  199.             }
  200.             $customFields $order->getCustomFields();
  201.             if (!isset($customFields['custom_channelpilot_orderjson'])) {
  202.                 // Not a CP order
  203.                 return;
  204.             }
  205.             // Observed https://paragonie.com/blog/2016/04/securely-implementing-de-serialization-in-php by limiting allowable classes and only parsing code-provided input.
  206.             /**
  207.              * @var \CPOrder $cpOrder
  208.              */
  209.             $cpOrder unserialize($customFields['custom_channelpilot_orderjson'], ["allowed_classes" => ["CPOrder""CPOrderHeader""CPOrderSummary""CPMoney"]]);
  210.             //$sums = $cpOrder->summary->totalSumOrderInclDiscount;
  211.             //$oCPRefund = new \CPRefund($orderId, (new \DateTime())->format('c'), 'Cancellation', $sums->net, $sums->gross, $sums->tax, $sums->taxRate);
  212.             $oCPCancellation = new \CPCancellation(
  213.                 $cpOrder->orderHeader->orderIdExternal,
  214.                 $order->getOrderNumber(),
  215.                 null,
  216.                 $cpOrder->orderHeader->source,
  217.                 (new \DateTime())->format('c'),
  218.                 true,
  219.                 '------'
  220.             );
  221.             //$customFields['custom_channelpilot_refund2send'] = serialize($oCPRefund);
  222.             $customFields['custom_channelpilot_cancel2send'] = serialize($oCPCancellation);
  223.             $this->orderRepository->update(
  224.                 [
  225.                     [
  226.                         'id' => $order->getId(),
  227.                         'customFields' => $customFields
  228.                     ]
  229.                 ],
  230.                 Context::createDefaultContext()
  231.             );
  232.             $this->logger->info('Marked order ID ' $order->getId() . ' for cancellation');
  233.         } catch (\Exception $e) {
  234.             $this->logger->error($e);
  235.             if (isset($order)) {
  236.                 $this->apiClient->markAsFailed(
  237.                     $order->getId(),
  238.                     Context::createDefaultContext()
  239.                 );
  240.             }
  241.             throw $e;
  242.         }
  243.     }
  244.     /**
  245.      * refund / Cancel payment
  246.      * @param StateMachineStateChangeEvent $args
  247.      */
  248.     public function onOrderStateChange(StateMachineStateChangeEvent $args): void
  249.     {
  250.         try {
  251.             if ($this->previousAttempts) {
  252.                 return;
  253.             }
  254.             $this->previousAttempts 1;
  255.             if (strtolower($args->getTransition()->getEntityName()) !== 'order') {
  256.                 return;
  257.             }
  258.             if (strtolower($args->getNextState()->getTechnicalName()) !== 'cancelled') {
  259.                 return;
  260.             }
  261.             // We have a cancelled order.
  262.             // Load order
  263.             $orderId $args->getTransition()->getEntityId();
  264.             $criteria = new Criteria([$orderId]);
  265.             // Currently not needed, hence skipped for performance:
  266.             //$criteria->addAssociation('orderCustomer');
  267.             //$criteria->addAssociation('transactions');
  268.             //$criteria->addAssociation('lineItems');
  269.             /** @var OrderEntity $order */
  270.             $order $this->orderRepository
  271.                 ->search($criteria$args->getContext())
  272.                 ->first();
  273.             $customFields $order->getCustomFields();
  274.             if (!isset($customFields['custom_channelpilot_orderjson'])) {
  275.                 // Not a CP order
  276.                 return;
  277.             }
  278.             // Observed https://paragonie.com/blog/2016/04/securely-implementing-de-serialization-in-php by limiting allowable classes and only parsing code-provided input.
  279.             /**
  280.              * @var \CPOrder $cpOrder
  281.              */
  282.             $cpOrder unserialize($customFields['custom_channelpilot_orderjson'], ["allowed_classes" => ["CPOrder""CPOrderHeader""CPOrderSummary""CPMoney"]]);
  283.             //$sums = $cpOrder->summary->totalSumOrderInclDiscount;
  284.             //$oCPRefund = new \CPRefund($orderId, (new \DateTime())->format('c'), 'Cancellation', $sums->net, $sums->gross, $sums->tax, $sums->taxRate);
  285.             $oCPCancellation = new \CPCancellation(
  286.                 $cpOrder->orderHeader->orderIdExternal,
  287.                 $order->getOrderNumber(),
  288.                 null,
  289.                 $cpOrder->orderHeader->source,
  290.                 (new \DateTime())->format('c'),
  291.                 true,
  292.                 '------'
  293.             );
  294.             //$customFields['custom_channelpilot_refund2send'] = serialize($oCPRefund);
  295.             $customFields['custom_channelpilot_cancel2send'] = serialize($oCPCancellation);
  296.             $this->orderRepository->update(
  297.                 [
  298.                     [
  299.                         'id' => $orderId,
  300.                         'customFields' => $customFields
  301.                     ]
  302.                 ],
  303.                 Context::createDefaultContext()
  304.             );
  305.             $this->logger->info('Marked order ID ' $orderId ' for cancellation');
  306.         } catch (\Exception $e) {
  307.             $this->logger->error($e);
  308.             if (isset($order)) {
  309.                 $this->apiClient->markAsFailed(
  310.                     $order->getId(),
  311.                     Context::createDefaultContext()
  312.                 );
  313.             }
  314.             throw $e;
  315.         }
  316.     }
  317.     protected function getApi(string $sProvidedToken): \ChannelPilotSellerAPI_v4_0
  318.     {
  319.         $salesChannelIDs $this->salesChannelRepository->search(new Criteria(), Context::createDefaultContext())->getIds();
  320.         // Find the requested Sales Channel
  321.         foreach ($salesChannelIDs as $salesChannelID) {
  322.             $token $this->systemConfigService->get(
  323.                 'Channelpilot.config.token',
  324.                 $salesChannelID
  325.             );
  326.             if ($token === $sProvidedToken) {
  327.                 // This is the required Saleschannel
  328.                 // Save MerchantId
  329.                 $sMerchantId $this->systemConfigService->get(
  330.                     'Channelpilot.config.merchantid',
  331.                     $salesChannelID
  332.                 );
  333.                 $api = new \ChannelPilotSellerAPI_v4_0($sMerchantId$sProvidedToken$salesChannelID);
  334.                 return $api;
  335.             }
  336.         }
  337.         $this->logger->error('Could not create Channel Pilot Pro API for token ' $sProvidedToken);
  338.         throw new \RuntimeException('Could not create Channel Pilot Pro API for token ' $sProvidedToken);
  339.     }
  340. }