vendor/store.shopware.com/adyenpaymentshopware6/src/Subscriber/PaymentSubscriber.php line 219

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /**
  3.  *                       ######
  4.  *                       ######
  5.  * ############    ####( ######  #####. ######  ############   ############
  6.  * #############  #####( ######  #####. ######  #############  #############
  7.  *        ######  #####( ######  #####. ######  #####  ######  #####  ######
  8.  * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
  9.  * ###### ######  #####( ######  #####. ######  #####          #####  ######
  10.  * #############  #############  #############  #############  #####  ######
  11.  *  ############   ############  #############   ############  #####  ######
  12.  *                                      ######
  13.  *                               #############
  14.  *                               ############
  15.  *
  16.  * Adyen Payment Module
  17.  *
  18.  * Copyright (c) 2020 Adyen B.V.
  19.  * This file is open source and available under the MIT license.
  20.  * See the LICENSE file for more info.
  21.  *
  22.  * Author: Adyen <shopware@adyen.com>
  23.  */
  24. namespace Adyen\Shopware\Subscriber;
  25. use Adyen\AdyenException;
  26. use Adyen\Shopware\Handlers\OneClickPaymentMethodHandler;
  27. use Adyen\Shopware\Provider\AdyenPluginProvider;
  28. use Adyen\Shopware\Service\ConfigurationService;
  29. use Adyen\Shopware\Service\PaymentMethodsFilterService;
  30. use Adyen\Shopware\Service\PaymentMethodsService;
  31. use Adyen\Shopware\Service\PaymentStateDataService;
  32. use Adyen\Shopware\Service\Repository\SalesChannelRepository;
  33. use Adyen\Util\Currency;
  34. use Psr\Container\ContainerInterface;
  35. use Psr\Log\LoggerInterface;
  36. use Shopware\Core\Checkout\Cart\CartCalculator;
  37. use Shopware\Core\Checkout\Cart\CartPersisterInterface;
  38. use Shopware\Core\Checkout\Cart\Exception\CartTokenNotFoundException;
  39. use Shopware\Core\Checkout\Payment\PaymentMethodCollection;
  40. use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
  41. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  42. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  43. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  44. use Shopware\Core\Framework\Struct\ArrayEntity;
  45. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  46. use Shopware\Core\System\SalesChannel\Context\AbstractSalesChannelContextFactory;
  47. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
  48. use Shopware\Core\System\SalesChannel\Event\SalesChannelContextSwitchEvent;
  49. use Shopware\Core\System\SalesChannel\SalesChannel\ContextSwitchRoute;
  50. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  51. use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
  52. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
  53. use Shopware\Storefront\Page\PageLoadedEvent;
  54. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  55. use Symfony\Component\HttpFoundation\RedirectResponse;
  56. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  57. use Symfony\Component\HttpKernel\Event\RequestEvent;
  58. use Symfony\Component\Routing\RouterInterface;
  59. class PaymentSubscriber implements EventSubscriberInterface
  60. {
  61.     const ADYEN_DATA_EXTENSION_ID 'adyenFrontendData';
  62.     /**
  63.      * @var PaymentStateDataService
  64.      */
  65.     private $paymentStateDataService;
  66.     /**
  67.      * @var PaymentMethodsFilterService
  68.      */
  69.     private $paymentMethodsFilterService;
  70.     /**
  71.      * @var RouterInterface
  72.      */
  73.     private $router;
  74.     /**
  75.      * @var SalesChannelRepository
  76.      */
  77.     private $salesChannelRepository;
  78.     /**
  79.      * @var ConfigurationService
  80.      */
  81.     private $configurationService;
  82.     /**
  83.      * @var PaymentMethodsService
  84.      */
  85.     private $paymentMethodsService;
  86.     /**
  87.      * @var EntityRepositoryInterface $paymentMethodRepository
  88.      */
  89.     private $paymentMethodRepository;
  90.     /**
  91.      * @var SessionInterface $session
  92.      */
  93.     private $session;
  94.     /**
  95.      * @var ContainerInterface $container
  96.      */
  97.     private $container;
  98.     /**
  99.      * @var CartPersisterInterface
  100.      */
  101.     private $cartPersister;
  102.     /**
  103.      * @var CartCalculator
  104.      */
  105.     private $cartCalculator;
  106.     /**
  107.      * @var Currency
  108.      */
  109.     private $currency;
  110.     /**
  111.      * @var LoggerInterface $logger
  112.      */
  113.     private $logger;
  114.     /**
  115.      * @var AdyenPluginProvider
  116.      */
  117.     private $adyenPluginProvider;
  118.     /**
  119.      * @var ContextSwitchRoute
  120.      */
  121.     private $contextSwitchRoute;
  122.     /**
  123.      * @var AbstractSalesChannelContextFactory
  124.      */
  125.     private $salesChannelContextFactory;
  126.     /**
  127.      * PaymentSubscriber constructor.
  128.      *
  129.      * @param AdyenPluginProvider $adyenPluginProvider
  130.      * @param PaymentMethodsFilterService $paymentMethodsFilterService
  131.      * @param PaymentStateDataService $paymentStateDataService
  132.      * @param RouterInterface $router
  133.      * @param SalesChannelRepository $salesChannelRepository
  134.      * @param ConfigurationService $configurationService
  135.      * @param PaymentMethodsService $paymentMethodsService
  136.      * @param EntityRepositoryInterface $paymentMethodRepository
  137.      * @param SessionInterface $session
  138.      * @param ContainerInterface $container
  139.      * @param CartPersisterInterface $cartPersister
  140.      * @param CartCalculator $cartCalculator
  141.      * @param ContextSwitchRoute $contextSwitchRoute
  142.      * @param AbstractSalesChannelContextFactory $salesChannelContextFactory
  143.      * @param Currency $currency
  144.      * @param LoggerInterface $logger
  145.      */
  146.     public function __construct(
  147.         AdyenPluginProvider $adyenPluginProvider,
  148.         PaymentMethodsFilterService $paymentMethodsFilterService,
  149.         PaymentStateDataService $paymentStateDataService,
  150.         RouterInterface $router,
  151.         SalesChannelRepository $salesChannelRepository,
  152.         ConfigurationService $configurationService,
  153.         PaymentMethodsService $paymentMethodsService,
  154.         EntityRepositoryInterface $paymentMethodRepository,
  155.         SessionInterface $session,
  156.         ContainerInterface $container,
  157.         CartPersisterInterface $cartPersister,
  158.         CartCalculator $cartCalculator,
  159.         ContextSwitchRoute $contextSwitchRoute,
  160.         AbstractSalesChannelContextFactory $salesChannelContextFactory,
  161.         Currency $currency,
  162.         LoggerInterface $logger
  163.     ) {
  164.         $this->paymentStateDataService $paymentStateDataService;
  165.         $this->paymentMethodsFilterService $paymentMethodsFilterService;
  166.         $this->router $router;
  167.         $this->salesChannelRepository $salesChannelRepository;
  168.         $this->configurationService $configurationService;
  169.         $this->paymentMethodsService $paymentMethodsService;
  170.         $this->paymentMethodRepository $paymentMethodRepository;
  171.         $this->session $session;
  172.         $this->container $container;
  173.         $this->cartPersister $cartPersister;
  174.         $this->cartCalculator $cartCalculator;
  175.         $this->contextSwitchRoute $contextSwitchRoute;
  176.         $this->salesChannelContextFactory $salesChannelContextFactory;
  177.         $this->currency $currency;
  178.         $this->logger $logger;
  179.         $this->adyenPluginProvider $adyenPluginProvider;
  180.     }
  181.     /**
  182.      * @return array|string[]
  183.      */
  184.     public static function getSubscribedEvents(): array
  185.     {
  186.         return [
  187.             SalesChannelContextSwitchEvent::class => 'onContextTokenUpdate',
  188.             CheckoutConfirmPageLoadedEvent::class => 'onCheckoutConfirmLoaded',
  189.             AccountEditOrderPageLoadedEvent::class => 'onCheckoutConfirmLoaded',
  190.             RequestEvent::class => 'onKernelRequest',
  191.         ];
  192.     }
  193.     /**
  194.      * @param SalesChannelContextSwitchEvent $event
  195.      */
  196.     public function onContextTokenUpdate(SalesChannelContextSwitchEvent $event)
  197.     {
  198.         // Clear state.data if payment method is updated
  199.         if ($event->getRequestDataBag()->has('paymentMethodId')) {
  200.             $this->removeCurrentStateData($event);
  201.         }
  202.         // Save state data, only if Adyen payment method is selected
  203.         if ($event->getRequestDataBag()->get('adyenStateData')) {
  204.             // Use payment method selected in the same request if available, otherwise get payment method from context
  205.             $paymentMethodId $event->getRequestDataBag()->get('paymentMethodId')
  206.                 ?? $event->getSalesChannelContext()->getPaymentMethod()->getId();
  207.             /** @var PaymentMethodEntity $paymentMethod */
  208.             $paymentMethod $this->paymentMethodRepository->search(
  209.                 (new Criteria())
  210.                     ->addFilter(new EqualsFilter('id'$paymentMethodId)),
  211.                 $event->getContext()
  212.             )->first();
  213.             if ($paymentMethod->getPluginId() === $this->adyenPluginProvider->getAdyenPluginId()) {
  214.                 $this->saveStateData($event$paymentMethod);
  215.             } else {
  216.                 $this->logger->error('No Adyen payment method selected, skipping state data save.');
  217.                 $this->session->getFlashBag()
  218.                     ->add('danger'$this->trans('adyen.paymentMethodSelectionError'));
  219.             }
  220.         }
  221.     }
  222.     /**
  223.      * Adds vars to frontend template to be used in JS
  224.      *
  225.      * @param PageLoadedEvent $event
  226.      */
  227.     public function onCheckoutConfirmLoaded(PageLoadedEvent $event)
  228.     {
  229.         $salesChannelContext $event->getSalesChannelContext();
  230.         $paymentMethod $salesChannelContext->getPaymentMethod();
  231.         $page $event->getPage();
  232.         $orderId '';
  233.         if (method_exists($page'getOrder')) {
  234.             $orderId $page->getOrder()->getId();
  235.         }
  236.         $currency $salesChannelContext->getCurrency()->getIsoCode();
  237.         $amount null;
  238.         try {
  239.             $cart $this->cartCalculator->calculate(
  240.                 $this->cartPersister->load($salesChannelContext->getToken(), $salesChannelContext),
  241.                 $salesChannelContext
  242.             );
  243.             $amount $this->currency->sanitize($cart->getPrice()->getTotalPrice(), $currency);
  244.         } catch (CartTokenNotFoundException $exception) {
  245.             $cart null;
  246.             if (!empty($orderId)) {
  247.                 $amount $this->currency->sanitize($page->getOrder()->getPrice()->getTotalPrice(), $currency);
  248.             }
  249.         }
  250.         $displaySaveCreditCardOption $this->paymentMethodsFilterService->isPaymentMethodInCollection(
  251.             $page->getPaymentMethods(),
  252.             OneClickPaymentMethodHandler::getPaymentMethodCode(),
  253.             $this->adyenPluginProvider->getAdyenPluginId(),
  254.         );
  255.         $paymentMethodsResponse $this->paymentMethodsService->getPaymentMethods($salesChannelContext$orderId);
  256.         $filteredPaymentMethods $this->paymentMethodsFilterService->filterShopwarePaymentMethods(
  257.             $page->getPaymentMethods(),
  258.             $salesChannelContext,
  259.             $this->adyenPluginProvider->getAdyenPluginId(),
  260.             $paymentMethodsResponse
  261.         );
  262.         $page->setPaymentMethods($filteredPaymentMethods);
  263.         $stateDataIsStored = (bool)$this->paymentStateDataService->getPaymentStateDataFromContextToken(
  264.             $salesChannelContext->getToken()
  265.         );
  266.         $salesChannelId $salesChannelContext->getSalesChannel()->getId();
  267.         $page->addExtension(
  268.             self::ADYEN_DATA_EXTENSION_ID,
  269.             new ArrayEntity(
  270.                 [
  271.                     'paymentStatusUrl' => $this->router->generate(
  272.                         'store-api.action.adyen.payment-status'
  273.                     ),
  274.                     'checkoutOrderUrl' => $this->router->generate(
  275.                         'store-api.checkout.cart.order'
  276.                     ),
  277.                     'paymentHandleUrl' => $this->router->generate(
  278.                         'store-api.payment.handle'
  279.                     ),
  280.                     'paymentDetailsUrl' => $this->router->generate(
  281.                         'store-api.action.adyen.payment-details'
  282.                     ),
  283.                     'paymentFinishUrl' => $this->router->generate(
  284.                         'frontend.checkout.finish.page',
  285.                         ['orderId' => '']
  286.                     ),
  287.                     'paymentErrorUrl' => $this->router->generate(
  288.                         'frontend.checkout.finish.page',
  289.                         [
  290.                             'orderId' => '',
  291.                             'changedPayment' => false,
  292.                             'paymentFailed' => true,
  293.                         ]
  294.                     ),
  295.                     'updatePaymentUrl' => $this->router->generate(
  296.                         'store-api.action.adyen.set-payment'
  297.                     ),
  298.                     'cancelOrderTransactionUrl' => $this->router->generate(
  299.                         'store-api.action.adyen.cancel-order-transaction',
  300.                     ),
  301.                     'languageId' => $salesChannelContext->getContext()->getLanguageId(),
  302.                     'clientKey' => $this->configurationService->getClientKey($salesChannelId),
  303.                     'locale' => $this->salesChannelRepository->getSalesChannelAssocLocale($salesChannelContext)
  304.                         ->getLanguage()->getLocale()->getCode(),
  305.                     'currency' => $currency,
  306.                     'amount' => $amount,
  307.                     'environment' => $this->configurationService->getEnvironment($salesChannelId),
  308.                     'paymentMethodsResponse' => json_encode($paymentMethodsResponse),
  309.                     'orderId' => $orderId,
  310.                     'pluginId' => $this->adyenPluginProvider->getAdyenPluginId(),
  311.                     'stateDataIsStored' => $stateDataIsStored,
  312.                     'storedPaymentMethods' => $paymentMethodsResponse['storedPaymentMethods'] ?? [],
  313.                     'selectedPaymentMethodHandler' => $paymentMethod->getFormattedHandlerIdentifier(),
  314.                     'selectedPaymentMethodPluginId' => $paymentMethod->getPluginId(),
  315.                     'displaySaveCreditCardOption' => $displaySaveCreditCardOption,
  316.                     'billingAddressStreetHouse' => $this->paymentMethodsService->getSplitStreetAddressHouseNumber(
  317.                         $salesChannelContext->getCustomer()->getActiveBillingAddress()->getStreet()
  318.                     ),
  319.                     'shippingAddressStreetHouse' => $this->paymentMethodsService->getSplitStreetAddressHouseNumber(
  320.                         $salesChannelContext->getCustomer()->getActiveShippingAddress()->getStreet()
  321.                     ),
  322.                 ]
  323.             )
  324.         );
  325.     }
  326.     public function onKernelRequest(RequestEvent $event)
  327.     {
  328.         $request $event->getRequest();
  329.         if (($request->attributes->get('_route') === 'frontend.account.edit-order.change-payment-method')
  330.             && $request->request->has('adyenStateData')) {
  331.             $this->contextSwitchRoute->switchContext(
  332.                 new RequestDataBag(
  333.                     [
  334.                         SalesChannelContextService::PAYMENT_METHOD_ID => $request->get('paymentMethodId'),
  335.                         'adyenStateData' => $request->request->get('adyenStateData'),
  336.                         'adyenOrigin' => $request->request->get('adyenOrigin'),
  337.                     ]
  338.                 ),
  339.                 $this->salesChannelContextFactory->create(
  340.                     $this->session->get('sw-context-token'),
  341.                     $request->attributes->get('sw-sales-channel-id')
  342.                 )
  343.             );
  344.             $event->setResponse(
  345.                 new RedirectResponse(
  346.                     $this->router->generate(
  347.                         'frontend.account.edit-order.page',
  348.                         ['orderId' => $request->attributes->get('orderId')]
  349.                     )
  350.                 )
  351.             );
  352.         }
  353.     }
  354.     private function trans(string $snippet, array $parameters = []): string
  355.     {
  356.         return $this->container
  357.             ->get('translator')
  358.             ->trans($snippet$parameters);
  359.     }
  360.     /**
  361.      * Persists the Adyen payment state data on payment method confirmation/update
  362.      *
  363.      * @param SalesChannelContextSwitchEvent $event
  364.      */
  365.     private function saveStateData(SalesChannelContextSwitchEvent $eventPaymentMethodEntity $selectedPaymentMethod)
  366.     {
  367.         //State data from the frontend
  368.         $stateData $event->getRequestDataBag()->get('adyenStateData');
  369.         //Convert the state data into an array
  370.         $stateDataArray json_decode($stateDatatrue);
  371.         if (json_last_error() !== JSON_ERROR_NONE) {
  372.             $this->logger->error('Payment state data is an invalid JSON: ' json_last_error_msg());
  373.             $this->session->getFlashBag()
  374.                 ->add('danger'$this->trans('adyen.paymentMethodSelectionError'));
  375.             return;
  376.         }
  377.         $selectedPaymentMethodIsStoredPM =
  378.             $selectedPaymentMethod->getFormattedHandlerIdentifier() == 'handler_adyen_oneclickpaymentmethodhandler';
  379.         $stateDataIsStoredPM = !empty($stateDataArray["paymentMethod"]["storedPaymentMethodId"]);
  380.         //Only store the state data if it matches the selected PM
  381.         if ($stateDataIsStoredPM === $selectedPaymentMethodIsStoredPM) {
  382.             try {
  383.                 $this->paymentStateDataService->insertPaymentStateData(
  384.                     $event->getSalesChannelContext()->getToken(),
  385.                     $event->getRequestDataBag()->get('adyenStateData'),
  386.                     $event->getRequestDataBag()->get('adyenOrigin')
  387.                 );
  388.             } catch (AdyenException $exception) {
  389.                 $this->session->getFlashBag()
  390.                     ->add('danger'$this->trans('adyen.paymentMethodSelectionError'));
  391.                 return;
  392.             }
  393.         } else {
  394.             //PM selected and state.data don't match, clear previous state.data
  395.             $this->removeCurrentStateData($event);
  396.         }
  397.     }
  398.     private function removeCurrentStateData(SalesChannelContextSwitchEvent $event)
  399.     {
  400.         $this->paymentStateDataService->deletePaymentStateDataFromContextToken(
  401.             $event->getSalesChannelContext()->getToken()
  402.         );
  403.     }
  404. }