custom/plugins/KlarnaPayment/src/Components/EventListener/SessionEventListener.php line 176

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace KlarnaPayment\Components\EventListener;
  4. use KlarnaPayment\Components\CartHasher\CartHasherInterface;
  5. use KlarnaPayment\Components\Client\ClientInterface;
  6. use KlarnaPayment\Components\Client\Hydrator\Request\CreateSession\CreateSessionRequestHydratorInterface;
  7. use KlarnaPayment\Components\Client\Hydrator\Request\UpdateSession\UpdateSessionRequestHydratorInterface;
  8. use KlarnaPayment\Components\Client\Hydrator\Struct\Address\AddressStructHydrator;
  9. use KlarnaPayment\Components\Client\Hydrator\Struct\Address\AddressStructHydratorInterface;
  10. use KlarnaPayment\Components\Client\Hydrator\Struct\Customer\CustomerStructHydratorInterface;
  11. use KlarnaPayment\Components\Client\Response\GenericResponse;
  12. use KlarnaPayment\Components\Client\Struct\Attachment;
  13. use KlarnaPayment\Components\ConfigReader\ConfigReaderInterface;
  14. use KlarnaPayment\Components\Converter\CustomOrderConverter;
  15. use KlarnaPayment\Components\Extension\ErrorMessageExtension;
  16. use KlarnaPayment\Components\Extension\SessionDataExtension;
  17. use KlarnaPayment\Components\Factory\MerchantDataFactoryInterface;
  18. use KlarnaPayment\Components\Helper\OrderFetcherInterface;
  19. use KlarnaPayment\Components\Helper\PaymentHelper\PaymentHelperInterface;
  20. use KlarnaPayment\Installer\Modules\PaymentMethodInstaller;
  21. use Shopware\Core\Checkout\Cart\Cart;
  22. use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
  23. use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
  24. use Shopware\Core\Checkout\Order\OrderEntity;
  25. use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
  26. use Shopware\Core\Framework\Context;
  27. use Shopware\Core\Framework\Struct\Struct;
  28. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  29. use Shopware\Core\System\SystemConfig\SystemConfigService;
  30. use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
  31. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
  32. use Shopware\Storefront\Page\Page;
  33. use Shopware\Storefront\Page\PageLoadedEvent;
  34. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  35. use Symfony\Component\HttpFoundation\RequestStack;
  36. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  37. class SessionEventListener implements EventSubscriberInterface
  38. {
  39.     /** @var PaymentHelperInterface */
  40.     private $paymentHelper;
  41.     /** @var CreateSessionRequestHydratorInterface */
  42.     private $requestHydrator;
  43.     /** @var UpdateSessionRequestHydratorInterface */
  44.     private $requestUpdateHydrator;
  45.     /** @var AddressStructHydratorInterface */
  46.     private $addressHydrator;
  47.     /** @var CustomerStructHydratorInterface */
  48.     private $customerHydrator;
  49.     /** @var ClientInterface */
  50.     private $client;
  51.     /** @var CartHasherInterface */
  52.     private $cartHasher;
  53.     /** @var MerchantDataFactoryInterface */
  54.     private $merchantDataFactory;
  55.     /** @var CustomOrderConverter */
  56.     private $orderConverter;
  57.     /** @var OrderFetcherInterface */
  58.     private $orderFetcher;
  59.     /** @var SessionInterface */
  60.     private $session;
  61.     /** @var SystemConfigService */
  62.     // TODO: Remove once Klarna enables the payment method
  63.     /** @phpstan-ignore-next-line  */
  64.     private $systemConfigService;
  65.     /** @var ConfigReaderInterface */
  66.     // TODO: Enable once Klarna enables the payment method
  67.     /** @phpstan-ignore-next-line  */
  68.     private $configReader;
  69.     public function __construct(
  70.         PaymentHelperInterface $paymentHelper,
  71.         CreateSessionRequestHydratorInterface $requestHydrator,
  72.         UpdateSessionRequestHydratorInterface $requestUpdateHydrator,
  73.         AddressStructHydratorInterface $addressHydrator,
  74.         CustomerStructHydratorInterface $customerHydrator,
  75.         ClientInterface $client,
  76.         CartHasherInterface $cartHasher,
  77.         MerchantDataFactoryInterface $merchantDataFactory,
  78.         CustomOrderConverter $orderConverter,
  79.         OrderFetcherInterface $orderFetcher,
  80.         SystemConfigService $systemConfigService,
  81.         ConfigReaderInterface $configReader,
  82.         ?SessionInterface $session,
  83.         RequestStack $requestStack
  84.     ) {
  85.         $this->paymentHelper         $paymentHelper;
  86.         $this->requestHydrator       $requestHydrator;
  87.         $this->requestUpdateHydrator $requestUpdateHydrator;
  88.         $this->addressHydrator       $addressHydrator;
  89.         $this->customerHydrator      $customerHydrator;
  90.         $this->client                $client;
  91.         $this->cartHasher            $cartHasher;
  92.         $this->merchantDataFactory   $merchantDataFactory;
  93.         $this->orderConverter        $orderConverter;
  94.         $this->orderFetcher          $orderFetcher;
  95.         $this->systemConfigService   $systemConfigService;
  96.         $this->configReader          $configReader;
  97.         // TODO: Remove me if compatibility is at least 6.4.2.0
  98.         /** @phpstan-ignore-next-line */
  99.         $this->session $session ?? $requestStack->getSession();
  100.     }
  101.     public static function getSubscribedEvents(): array
  102.     {
  103.         return [
  104.             CheckoutConfirmPageLoadedEvent::class  => 'startKlarnaSession',
  105.             AccountEditOrderPageLoadedEvent::class => 'startKlarnaSession',
  106.             CheckoutOrderPlacedEvent::class        => 'resetKlarnaSession',
  107.             CustomerLoginEvent::class              => 'resetKlarnaSession',
  108.         ];
  109.     }
  110.     public function startKlarnaSession(PageLoadedEvent $event): void
  111.     {
  112.         $context $event->getSalesChannelContext();
  113.         if (!$this->paymentHelper->isKlarnaPaymentsEnabled($context)) {
  114.             return;
  115.         }
  116.         if ($event instanceof CheckoutConfirmPageLoadedEvent) {
  117.             $cart $event->getPage()->getCart();
  118.         } elseif ($event instanceof AccountEditOrderPageLoadedEvent) {
  119.             /** @phpstan-ignore-next-line */
  120.             $cart $this->convertCartFromOrder($event->getPage()->getOrder(), $event->getContext());
  121.         } else {
  122.             return;
  123.         }
  124.         if ($this->hasValidKlarnaSession($context)) {
  125.             $response $this->updateKlarnaSession($this->session->get(UpdateSessionRequestHydratorInterface::KLARNA_SESSION_ID), $cart$context);
  126.         } else {
  127.             $response $this->createKlarnaSession($cart$context);
  128.         }
  129.         $invalidResponseStatus $response->getHttpStatus() !== 200 && $response->getHttpStatus() !== 204;
  130.         if ($invalidResponseStatus) {
  131.             if ($this->paymentHelper->isKlarnaPaymentsSelected($context)) {
  132.                 $this->createErrorMessageExtension($event);
  133.             }
  134.             $this->removeAllKlarnaPaymentMethods($event->getPage());
  135.             return;
  136.         }
  137.         if ($response->getHttpStatus() === 200 && !$this->hasValidKlarnaSession($context)) {
  138.             $this->addKlarnaSessionToShopwareSession($response->getResponse(), $context);
  139.         }
  140.         $this->createSessionDataExtension($response$event->getPage(), $cart$context);
  141.         $this->removeDisabledKlarnaPaymentMethods($event->getPage());
  142.         $this->filterPayNowMethods($event->getPage());
  143.         // TODO: Enable once Klarna enables the payment method
  144.         //$this->updateGlobalPurchaseFlowSystemConfig($event->getPage(), $context);
  145.     }
  146.     public function resetKlarnaSession(): void
  147.     {
  148.         $this->session->remove(UpdateSessionRequestHydratorInterface::KLARNA_SESSION_ID);
  149.         $this->session->remove(UpdateSessionRequestHydratorInterface::KLARNA_CLIENT_TOKEN);
  150.         $this->session->remove(UpdateSessionRequestHydratorInterface::KLARNA_PAYMENT_METHOD_CATEGORIES);
  151.     }
  152.     private function filterPayNowMethods(Struct $page): void
  153.     {
  154.         if (!($page instanceof Page)) {
  155.             return;
  156.         }
  157.         /** @var null|SessionDataExtension $sessionData */
  158.         $sessionData $page->getExtension(SessionDataExtension::EXTENSION_NAME);
  159.         if ($sessionData === null) {
  160.             return;
  161.         }
  162.         foreach ($sessionData->getPaymentMethodCategories() as $paymentCategory) {
  163.             if ($paymentCategory['identifier'] === PaymentMethodInstaller::KLARNA_PAYMENTS_PAY_NOW_CODE) {
  164.                 $this->removeSeparatePayNowKlarnaPaymentMethods($page);
  165.                 return;
  166.             }
  167.         }
  168.         $this->removeCombinedKlarnaPaymentPayNowMethod($page);
  169.     }
  170.     private function createErrorMessageExtension(PageLoadedEvent $event): void
  171.     {
  172.         $errorMessageExtension = new ErrorMessageExtension(ErrorMessageExtension::GENERIC_ERROR);
  173.         $event->getPage()->addExtension(ErrorMessageExtension::EXTENSION_NAME$errorMessageExtension);
  174.     }
  175.     private function createSessionDataExtension(GenericResponse $responseStruct $pageCart $cartSalesChannelContext $context): void
  176.     {
  177.         if (!($page instanceof Page)) {
  178.             return;
  179.         }
  180.         $sessionData = new SessionDataExtension();
  181.         $sessionData->assign([
  182.             'selectedPaymentMethodCategory' => $this->getKlarnaCodeFromPaymentMethod($context),
  183.             'cartHash'                      => $this->cartHasher->generate($cart$context),
  184.         ]);
  185.         if ($this->hasValidKlarnaSession($context)) {
  186.             $sessionData->assign([
  187.                 'sessionId'               => $this->session->get(UpdateSessionRequestHydratorInterface::KLARNA_SESSION_ID),
  188.                 'clientToken'             => $this->session->get(UpdateSessionRequestHydratorInterface::KLARNA_CLIENT_TOKEN),
  189.                 'paymentMethodCategories' => $this->session->get(UpdateSessionRequestHydratorInterface::KLARNA_PAYMENT_METHOD_CATEGORIES),
  190.             ]);
  191.         } else {
  192.             $sessionData->assign([
  193.                 'sessionId'               => $response->getResponse()['session_id'],
  194.                 'clientToken'             => $response->getResponse()['client_token'],
  195.                 'paymentMethodCategories' => $response->getResponse()['payment_method_categories'],
  196.             ]);
  197.         }
  198.         if ($this->paymentHelper->isKlarnaPaymentsSelected($context)) {
  199.             $extraMerchantData $this->merchantDataFactory->getExtraMerchantData($sessionData$cart$context);
  200.             if (!empty($extraMerchantData->getAttachment())) {
  201.                 $attachment = new Attachment();
  202.                 $attachment->assign([
  203.                     'data' => $extraMerchantData->getAttachment(),
  204.                 ]);
  205.             } else {
  206.                 $attachment null;
  207.             }
  208.             $sessionData->assign([
  209.                 'customerData' => [
  210.                     'billing_address'  => $this->addressHydrator->hydrateFromContext($contextAddressStructHydrator::TYPE_BILLING),
  211.                     'shipping_address' => $this->addressHydrator->hydrateFromContext($contextAddressStructHydrator::TYPE_SHIPPING),
  212.                     'customer'         => $this->customerHydrator->hydrate($context),
  213.                     'merchant_data'    => $extraMerchantData->getMerchantData(),
  214.                     'attachment'       => $attachment,
  215.                 ],
  216.             ]);
  217.         }
  218.         $page->addExtension(SessionDataExtension::EXTENSION_NAME$sessionData);
  219.     }
  220.     private function removeDisabledKlarnaPaymentMethods(Struct $page): void
  221.     {
  222.         if (!($page instanceof Page)) {
  223.             return;
  224.         }
  225.         /** @var null|SessionDataExtension $sessionData */
  226.         $sessionData $page->getExtension(SessionDataExtension::EXTENSION_NAME);
  227.         if ($sessionData === null) {
  228.             return;
  229.         }
  230.         if (!method_exists($page'setPaymentMethods') || !method_exists($page'getPaymentMethods')) {
  231.             return;
  232.         }
  233.         $page->setPaymentMethods(
  234.             $page->getPaymentMethods()->filter(
  235.                 static function (PaymentMethodEntity $paymentMethod) use ($sessionData) {
  236.                     if (!array_key_exists($paymentMethod->getId(), PaymentMethodInstaller::KLARNA_PAYMENTS_CODES)) {
  237.                         return true;
  238.                     }
  239.                     foreach ($sessionData->getPaymentMethodCategories() as $paymentCategory) {
  240.                         if ($paymentCategory['identifier'] === PaymentMethodInstaller::KLARNA_PAYMENTS_CODES[$paymentMethod->getId()]) {
  241.                             $paymentMethod->setName($paymentCategory['name']);
  242.                             $paymentMethod->setTranslated(['name' => $paymentCategory['name']]);
  243.                             return true;
  244.                         }
  245.                     }
  246.                     return false;
  247.                 }
  248.             )
  249.         );
  250.     }
  251.     private function removeSeparatePayNowKlarnaPaymentMethods(Page $page): void
  252.     {
  253.         if (!method_exists($page'setPaymentMethods') || !method_exists($page'getPaymentMethods')) {
  254.             return;
  255.         }
  256.         $page->setPaymentMethods(
  257.             $page->getPaymentMethods()->filter(
  258.                 static function (PaymentMethodEntity $paymentMethod) {
  259.                     if (!array_key_exists($paymentMethod->getId(), PaymentMethodInstaller::KLARNA_PAYMENTS_CODES)) {
  260.                         return true;
  261.                     }
  262.                     return in_array($paymentMethod->getId(), PaymentMethodInstaller::KLARNA_PAYMENTS_CODES_WITH_PAY_NOW_COMBINEDtrue);
  263.                 }
  264.             )
  265.         );
  266.     }
  267.     private function removeCombinedKlarnaPaymentPayNowMethod(Page $page): void
  268.     {
  269.         if (!method_exists($page'setPaymentMethods') || !method_exists($page'getPaymentMethods')) {
  270.             return;
  271.         }
  272.         $page->setPaymentMethods(
  273.             $page->getPaymentMethods()->filter(
  274.                 static function (PaymentMethodEntity $paymentMethod) {
  275.                     if (!array_key_exists($paymentMethod->getId(), PaymentMethodInstaller::KLARNA_PAYMENTS_CODES)) {
  276.                         return true;
  277.                     }
  278.                     return $paymentMethod->getId() !== PaymentMethodInstaller::KLARNA_PAY_NOW;
  279.                 }
  280.             )
  281.         );
  282.     }
  283.     private function removeAllKlarnaPaymentMethods(Struct $page): void
  284.     {
  285.         if (!($page instanceof Page) || !method_exists($page'setPaymentMethods') || !method_exists($page'getPaymentMethods')) {
  286.             return;
  287.         }
  288.         $page->setPaymentMethods(
  289.             $page->getPaymentMethods()->filter(
  290.                 static function (PaymentMethodEntity $paymentMethod) {
  291.                     if (array_key_exists($paymentMethod->getId(), PaymentMethodInstaller::KLARNA_PAYMENTS_CODES)) {
  292.                         return false;
  293.                     }
  294.                     return true;
  295.                 }
  296.             )
  297.         );
  298.     }
  299.     private function createKlarnaSession(Cart $cartSalesChannelContext $context): GenericResponse
  300.     {
  301.         $request $this->requestHydrator->hydrate($cart$context);
  302.         return $this->client->request($request$context->getContext());
  303.     }
  304.     private function updateKlarnaSession(string $sessionIdCart $cartSalesChannelContext $context): GenericResponse
  305.     {
  306.         $request $this->requestUpdateHydrator->hydrate($sessionId$cart$context);
  307.         return $this->client->request($request$context->getContext());
  308.     }
  309.     private function getKlarnaCodeFromPaymentMethod(SalesChannelContext $context): string
  310.     {
  311.         if (!array_key_exists($context->getPaymentMethod()->getId(), PaymentMethodInstaller::KLARNA_PAYMENTS_CODES)) {
  312.             return '';
  313.         }
  314.         return PaymentMethodInstaller::KLARNA_PAYMENTS_CODES[$context->getPaymentMethod()->getId()];
  315.     }
  316.     private function convertCartFromOrder(OrderEntity $orderEntityContext $context): Cart
  317.     {
  318.         $order $this->orderFetcher->getOrderFromOrder($orderEntity->getId(), $context);
  319.         if ($order === null) {
  320.             throw new \LogicException('could not find order via id');
  321.         }
  322.         return $this->orderConverter->convertOrderToCart($order$context);
  323.     }
  324.     /**
  325.      * @param array<string,mixed> $klarnaSession
  326.      */
  327.     private function addKlarnaSessionToShopwareSession(array $klarnaSessionSalesChannelContext $context): void
  328.     {
  329.         $this->session->set(UpdateSessionRequestHydratorInterface::KLARNA_SESSION_ID$klarnaSession['session_id']);
  330.         $this->session->set(UpdateSessionRequestHydratorInterface::KLARNA_CLIENT_TOKEN$klarnaSession['client_token']);
  331.         $this->session->set(UpdateSessionRequestHydratorInterface::KLARNA_PAYMENT_METHOD_CATEGORIES$klarnaSession['payment_method_categories']);
  332.         $this->session->set(UpdateSessionRequestHydratorInterface::KLARNA_BILLING_COUNTRY$this->paymentHelper->getShippingCountry($context)->getIso());
  333.     }
  334.     private function hasValidKlarnaSession(SalesChannelContext $context): bool
  335.     {
  336.         return $this->session->has(UpdateSessionRequestHydratorInterface::KLARNA_SESSION_ID)
  337.             && $this->session->has(UpdateSessionRequestHydratorInterface::KLARNA_CLIENT_TOKEN)
  338.             && $this->session->has(UpdateSessionRequestHydratorInterface::KLARNA_PAYMENT_METHOD_CATEGORIES)
  339.             && ($this->session->has(UpdateSessionRequestHydratorInterface::KLARNA_BILLING_COUNTRY) && $this->session->get(UpdateSessionRequestHydratorInterface::KLARNA_BILLING_COUNTRY) === $this->paymentHelper->getShippingCountry($context)->getIso());
  340.     }
  341.     // TODO: Enable once Klarna enables the payment method
  342. //    private function updateGlobalPurchaseFlowSystemConfig(Struct $page, SalesChannelContext $context): void
  343. //    {
  344. //        if (!($page instanceof Page)) {
  345. //            return;
  346. //        }
  347. //
  348. //        if (!method_exists($page, 'getPaymentMethods')) {
  349. //            return;
  350. //        }
  351. //
  352. //        $paymentMethodIds = $page->getPaymentMethods()->getIds();
  353. //
  354. //        if (!$this->hasKlarnaPayment($paymentMethodIds)) {
  355. //            return;
  356. //        }
  357. //
  358. //        $configRelatedSalesChannelId = $context->getSalesChannel()->getId();
  359. //
  360. //        $currentConfig = $this->configReader->read($configRelatedSalesChannelId, false)
  361. //            ->get(ConfigReaderInterface::CONFIG_ACTIVE_GLOBALPURCHASEFLOW, null);
  362. //
  363. //        if ($currentConfig === null) {
  364. //            $currentConfig               = $this->configReader->read()->get(ConfigReaderInterface::CONFIG_ACTIVE_GLOBALPURCHASEFLOW);
  365. //            $configRelatedSalesChannelId = null;
  366. //        }
  367. //
  368. //        $newConfig = \in_array(PaymentMethodInstaller::KLARNA_PAY, $paymentMethodIds, true);
  369. //
  370. //        if ($currentConfig === $newConfig) {
  371. //            return;
  372. //        }
  373. //
  374. //        $settingKey = \sprintf('%s%s', ConfigReaderInterface::SYSTEM_CONFIG_DOMAIN, ConfigReaderInterface::CONFIG_ACTIVE_GLOBALPURCHASEFLOW);
  375. //
  376. //        $this->systemConfigService->set($settingKey, $newConfig, $configRelatedSalesChannelId);
  377. //    }
  378. //
  379. //    private function hasKlarnaPayment(array $paymentMethodIds): bool
  380. //    {
  381. //        $klarnaPaymentIds = \array_keys(PaymentMethodInstaller::KLARNA_PAYMENTS_CODES);
  382. //
  383. //        foreach ($klarnaPaymentIds as $klarnaPaymentId) {
  384. //            if (\in_array($klarnaPaymentId, $paymentMethodIds, true)) {
  385. //                return true;
  386. //            }
  387. //        }
  388. //
  389. //        return false;
  390. //    }
  391. }