src/Subscriber/Payment/WebhookSubscriber.php line 49

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Subscriber\Payment;
  4. use App\Action\Query\Order\GetBasketStatus\GetBasketStatusQuery;
  5. use App\Event\Cinema\CinemaContextOnDemandEvent;
  6. use App\Event\Order\OrderPaymentTransactionHandledEvent;
  7. use App\Event\Payment\WebhookNotificationEvent;
  8. use App\Exception\Order\BasketClosedException;
  9. use App\Exception\Order\BasketDoesNotExistException;
  10. use App\Exception\Order\BasketOrderLockedException;
  11. use App\Manager\OrderManager;
  12. use App\Model\Order\BasketStatus;
  13. use App\Model\Order\Order;
  14. use App\Payment\LockablePaymentClientInterface;
  15. use App\Payment\Model\Response as PaymentProviderResponse;
  16. use App\Payment\PaymentFactory;
  17. use App\Service\Order\OrderLockedException;
  18. use App\Service\Order\OrderLockServiceInterface;
  19. use App\Service\Payment\PaymentProviderService;
  20. use App\Tool\Uuid\UuidBuilder;
  21. use Psr\Log\LoggerInterface;
  22. use Ramsey\Uuid\Uuid;
  23. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  24. use Symfony\Component\Messenger\MessageBusInterface;
  25. use Symfony\Component\Messenger\Stamp\HandledStamp;
  26. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  27. class WebhookSubscriber implements EventSubscriberInterface
  28. {
  29.     private PaymentFactory $paymentFactory;
  30.     private PaymentProviderService $paymentProviderService;
  31.     private EventDispatcherInterface $eventDispatcher;
  32.     private MessageBusInterface $messageBus;
  33.     private OrderManager $orderManager;
  34.     private OrderLockServiceInterface $lockService;
  35.     private LoggerInterface $orderLockLogger;
  36.     public function __construct(
  37.         PaymentFactory              $paymentFactory,
  38.         PaymentProviderService      $paymentProviderService,
  39.         EventDispatcherInterface    $eventDispatcher,
  40.         MessageBusInterface         $messageBus,
  41.         OrderManager                $orderManager,
  42.         OrderLockServiceInterface   $lockService,
  43.         LoggerInterface             $orderLockLogger
  44.     )
  45.     {
  46.         $this->paymentFactory $paymentFactory;
  47.         $this->paymentProviderService $paymentProviderService;
  48.         $this->eventDispatcher $eventDispatcher;
  49.         $this->messageBus $messageBus;
  50.         $this->orderManager $orderManager;
  51.         $this->lockService $lockService;
  52.         $this->orderLockLogger $orderLockLogger;
  53.     }
  54.     public static function getSubscribedEvents(): array
  55.     {
  56.         return [
  57.             WebhookNotificationEvent::class => 'onNotification'
  58.         ];
  59.     }
  60.     public function onNotification(WebhookNotificationEvent $event): void
  61.     {
  62.         $client $this->paymentFactory->get(
  63.             $this->paymentProviderService->getPaymentProviderServiceIdentifier($event->getPaymentProvider())
  64.         );
  65.         if($client instanceof LockablePaymentClientInterface) {
  66.             $sessionId $client->getSessionId($event->getRequest());
  67.             if($sessionId !== null) {
  68.                 try {
  69.                     $this->processOrderLock($sessionId);
  70.                 } catch (BasketClosedException) {
  71.                     return;
  72.                 } catch (OrderLockedException $e) {
  73.                     throw $e;
  74.                 } catch (\Throwable) {}
  75.             }
  76.         }
  77.         $paymentProviderResponse $client->dispatch($event->getRequest());
  78.         if ($paymentProviderResponse !== null) {
  79.             $this->eventDispatcher->dispatch(
  80.                 new CinemaContextOnDemandEvent($paymentProviderResponse->getCinemaId())
  81.             );
  82.             $order $this->orderManager->get(Uuid::fromString($paymentProviderResponse->getId()));
  83.             $client->flushLogger();
  84.         } else {
  85.             return;
  86.         }
  87.         if ($order === null) {
  88.             throw new BasketDoesNotExistException();
  89.         }
  90.         if ($paymentProviderResponse->getStatus() !== PaymentProviderResponse::PENDING_TRANSACTION) {
  91.             if ($this->orderManager->isOrderLocked(Uuid::fromString($paymentProviderResponse->getId()))) {
  92.                 throw new BasketOrderLockedException();
  93.             }
  94.             $this->orderManager->lockOrder(Uuid::fromString($paymentProviderResponse->getId()));
  95.         }
  96.         try {
  97.             $this->orderManager->handlePaymentProviderResponse(
  98.                 $paymentProviderResponse,
  99.                 $order,
  100.                 $client,
  101.                 $event->getPaymentChannel(),
  102.                 ['isProcessingExpired' => false'isProcessingAllowed' => true],
  103.             );
  104.         } catch (\Exception $exception) {
  105.             throw $exception;
  106.         } finally {
  107.             $this->eventDispatcher->dispatch(
  108.                 new OrderPaymentTransactionHandledEvent($order$paymentProviderResponse,
  109.                     $client$event->getPaymentChannel()
  110.                 )
  111.             );
  112.             if($client instanceof LockablePaymentClientInterface) {
  113.                 $this->lockService->unlock(
  114.                     Uuid::fromString($paymentProviderResponse->getId()),
  115.                     Uuid::fromString($paymentProviderResponse->getCinemaId())
  116.                 );
  117.             }
  118.         }
  119.     }
  120.     /**
  121.      * Locks the order if it isn't closed yet.
  122.      */
  123.     private function processOrderLock(array $sessionId): void
  124.     {
  125.         $orderId UuidBuilder::build($sessionId['merchantTransactionId']);
  126.         $cinemaId UuidBuilder::build($sessionId['cinemaId']);
  127.         if($cinemaId !== null && $orderId !== null) {
  128.             $this->eventDispatcher->dispatch(new CinemaContextOnDemandEvent($cinemaId->toString()));
  129.             /** @var BasketStatus $basketStatus */
  130.             $basketStatus $this->messageBus->dispatch(new GetBasketStatusQuery($orderId->toString()))
  131.                 ->last(HandledStamp::class)->getResult();
  132.             if($basketStatus->getStatus() === Order::CLOSED) {
  133.                 $context = [
  134.                     'cinemaId' => $cinemaId->toString(),
  135.                     'orderId' => $orderId->toString(),
  136.                     'basketStatus' => $basketStatus->getStatus(),
  137.                     'channel' => 'webhook'
  138.                 ];
  139.                 $this->orderLockLogger->error('Webhook received when basket is closed',$context);
  140.                 throw new BasketClosedException();
  141.             }
  142.             $this->lockService->lock($orderId$cinemaId'webhook');
  143.         }
  144.     }
  145. }