Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## WIP

* RATESWX-306: installment: prevent fatal error message in checkout on unreachable gateway
* RATESWSX-306: installment: prevent fatal error message in checkout on unreachable gateway
* RATESWSX-307: add support for login as (third-party modules and Shopware standard since 6.6.5.x)

## Version 7.0.1 - Released on 2024-06-07

Expand Down
2 changes: 1 addition & 1 deletion src/Components/Account/Subscriber/AccountSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public function onPaymentOrderRouteRequest(SetPaymentOrderRouteRequestEvent $eve
$requestData = new DataBag($event->getStorefrontRequest()->request->all());
$ratepayData = RequestHelper::getRatepayData($requestData);

$validationDefinitions = $paymentHandler->getValidationDefinitions($requestData, $orderEntity);
$validationDefinitions = $paymentHandler->getValidationDefinitions($requestData, $event->getSalesChannelContext(), $orderEntity);
$definition = new DataValidationDefinition();
$definition->addSub(RequestHelper::RATEPAY_DATA_KEY, DataValidationHelper::addSubConstraints(new DataValidationDefinition(), $validationDefinitions));
try {
Expand Down
3 changes: 3 additions & 0 deletions src/Components/AdminOrders/DependencyInjection/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<service id="Ratepay\RpayPayments\Components\AdminOrders\Service\DfpService"
decorates="Ratepay\RpayPayments\Components\DeviceFingerprint\DfpService">
<argument key="$decorated" type="service" id=".inner"/>
</service>

<service id="Ratepay\RpayPayments\Components\AdminOrders\Service\SessionService">
<argument key="$sessionKey">%ratepay.admin.storefront-login.token%</argument>
</service>
</services>
Expand Down
11 changes: 3 additions & 8 deletions src/Components/AdminOrders/DependencyInjection/subscriber.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@
<tag name="kernel.event_subscriber"/>
</defaults>

<service id="Ratepay\RpayPayments\Components\AdminOrders\Subscriber\ProfileConfigSubscriber">
<argument key="$sessionKey">%ratepay.admin.storefront-login.token%</argument>
</service>

<service id="Ratepay\RpayPayments\Components\AdminOrders\Subscriber\PageSubscriber">
<argument key="$sessionKey">%ratepay.admin.storefront-login.token%</argument>
<tag name="kernel.event_subscriber"/>
</service>
<service id="Ratepay\RpayPayments\Components\AdminOrders\Subscriber\ProfileConfigSubscriber"/>
<service id="Ratepay\RpayPayments\Components\AdminOrders\Subscriber\PageSubscriber"/>
<service id="Ratepay\RpayPayments\Components\AdminOrders\Subscriber\LoginSubscriber"/>

</services>

Expand Down
20 changes: 10 additions & 10 deletions src/Components/AdminOrders/Service/DfpService.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@ class DfpService implements DfpServiceInterface
public function __construct(
private readonly DfpServiceInterface $decorated,
private readonly RequestStack $requestStack,
private readonly string $sessionKey
private readonly SessionService $sessionService
) {
}

public function generatedDfpId(Request $request, OrderEntity|SalesChannelContext $baseData): ?string
public function generatedDfpId(Request $request, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): ?string
{
return $this->isDfpRequired($baseData) ? $this->decorated->generatedDfpId($request, $baseData) : null;
return $this->isDfpRequired($salesChannelContext, $orderEntity) ? $this->decorated->generatedDfpId($request, $salesChannelContext, $orderEntity) : null;
}

public function getDfpSnippet(Request $request, OrderEntity|SalesChannelContext $baseData): ?string
public function getDfpSnippet(Request $request, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): ?string
{
return $this->isDfpRequired($baseData) ? $this->decorated->getDfpSnippet($request, $baseData) : null;
return $this->isDfpRequired($salesChannelContext, $orderEntity) ? $this->decorated->getDfpSnippet($request, $salesChannelContext, $orderEntity) : null;
}

public function isDfpIdValid(OrderEntity|SalesChannelContext $baseData, string $dfpId = null): bool
public function isDfpIdValid(SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null, string $dfpId = null): bool
{
return !$this->isDfpRequired($baseData) || $this->decorated->isDfpIdValid($baseData, $dfpId);
return !$this->isDfpRequired($salesChannelContext, $orderEntity) || $this->decorated->isDfpIdValid($salesChannelContext, $orderEntity, $dfpId);
}

public function isDfpRequired(OrderEntity|SalesChannelContext $object): bool
public function isDfpRequired(SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): bool
{
$session = $this->requestStack->getMainRequest()->getSession();
if ($session->get($this->sessionKey)) {
if ($this->sessionService->isAdminSession($salesChannelContext, $session)) {
return false;
}

return $this->decorated->isDfpRequired($object);
return $this->decorated->isDfpRequired($salesChannelContext, $orderEntity);
}
}
85 changes: 85 additions & 0 deletions src/Components/AdminOrders/Service/SessionService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);
/*
* Copyright (c) Ratepay GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Ratepay\RpayPayments\Components\AdminOrders\Service;

use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class SessionService
{
public function __construct(
private readonly string $sessionKey
) {
}

public function isAdminSession(SalesChannelContext $context, SessionInterface $session): bool
{
return $this->isRatepayAdminSession($session) || $this->isLoggedInAsCustomer($context, $session);
}

public function canLogout(SalesChannelContext $context, SessionInterface $session): bool
{
// allow only session logout if the session has been created by ratepay
return $this->isRatepayAdminSession($session) && !$this->isLoggedInAsCustomer($context, $session);
}

public function isLoggedInAsCustomer(SalesChannelContext $context, SessionInterface $session): bool
{
// supported since SW 6.6.5.x - TODO remove this check if compatibility has been change to Shopware >= 6.6.5
if (method_exists($context, 'getImitatingUserId') && $context->getImitatingUserId() !== null) {
return true;
}

if ($context->getCustomerId() === null) {
return false;
}

foreach ($this->getThirdPartyLoginAsSessionKeys() as $key) {
if ($session->has($key)) {
return true;
}
}

return false;
}

public function destroy(SessionInterface $session): void
{
$session->remove($this->sessionKey);

// make sure that the third-party modules did not left any data, which we will check
foreach ($this->getThirdPartyLoginAsSessionKeys() as $key) {
$session->remove($key);
}
}

private function isRatepayAdminSession(SessionInterface $session): bool
{
return $session->get($this->sessionKey) === true;
}

private function getThirdPartyLoginAsSessionKeys(): array
{
$keys = [];

// login as (module: https://store.shopware.com/de/jlau706451421896/als-kunde-einloggen.html)
if (defined('Jlau\LoginAsCustomer\Controller\LoginAsCustomer::SESSION_NAME')) {
$keys[] = constant('Jlau\LoginAsCustomer\Controller\LoginAsCustomer::SESSION_NAME');
}

// login as (module: https://store.shopware.com/de/swpa452746080451m/als-kunde-einloggen.html)
if (defined('Swpa\SwpaLoginAsCustomer\Service\LoginService::ADMIN_CUSTOMER_CONTEXT_EXTENSION')) {
$keys[] = constant('Swpa\SwpaLoginAsCustomer\Service\LoginService::ADMIN_CUSTOMER_CONTEXT_EXTENSION');
}

return $keys;
}
}
43 changes: 43 additions & 0 deletions src/Components/AdminOrders/Subscriber/LoginSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);
/*
* Copyright (c) Ratepay GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Ratepay\RpayPayments\Components\AdminOrders\Subscriber;

use Ratepay\RpayPayments\Components\AdminOrders\Service\SessionService;
use Shopware\Core\Checkout\Customer\Event\CustomerLogoutEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class LoginSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly SessionService $sessionService,
private readonly RequestStack $requestStack
) {
}

public static function getSubscribedEvents(): array
{
return [
CustomerLogoutEvent::class => ['onLogout', -3000], // as late as possible to prioritize thirdparty modules
];
}

public function onLogout(CustomerLogoutEvent $event): void
{
$session = $this->requestStack->getMainRequest()?->getSession();
if (!$session instanceof SessionInterface) {
return;
}

$this->sessionService->destroy($session);
}
}
9 changes: 7 additions & 2 deletions src/Components/AdminOrders/Subscriber/PageSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@

namespace Ratepay\RpayPayments\Components\AdminOrders\Subscriber;

use Ratepay\RpayPayments\Components\AdminOrders\Service\SessionService;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class PageSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly string $sessionKey
private readonly SessionService $sessionService
) {
}

Expand All @@ -31,6 +32,10 @@ public static function getSubscribedEvents(): array
public function onPage(StorefrontRenderEvent $event): void
{
$session = $event->getRequest()->getSession();
$event->setParameter('ratepayAdminOrderSession', $session->get($this->sessionKey) === true);
$event->setParameter('ratepayAdminOrderSession', [
'active' => $this->sessionService->isAdminSession($event->getSalesChannelContext(), $session),
'canLogout' => $this->sessionService->canLogout($event->getSalesChannelContext(), $session),
'isLoggedInAsCustomer' => $this->sessionService->isLoggedInAsCustomer($event->getSalesChannelContext(), $session),
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Ratepay\RpayPayments\Components\AdminOrders\Subscriber;

use Ratepay\RpayPayments\Components\AdminOrders\Service\SessionService;
use Ratepay\RpayPayments\Components\ProfileConfig\Event\CreateProfileConfigCriteriaEvent;
use Ratepay\RpayPayments\Components\ProfileConfig\Model\ProfileConfigEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
Expand All @@ -21,7 +22,7 @@ class ProfileConfigSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly RequestStack $requestStack,
private readonly string $sessionKey
private readonly SessionService $sessionService
) {
}

Expand All @@ -34,9 +35,7 @@ public static function getSubscribedEvents(): array

public function onLoadConfig(CreateProfileConfigCriteriaEvent $event): void
{
$session = $this->requestStack->getMainRequest()->getSession();

if ($session->get($this->sessionKey) === true) {
if ($this->sessionService->isAdminSession($event->getSalesChannelContext(), $this->requestStack->getMainRequest()->getSession())) {
$event->getCriteria()->addFilter(new EqualsFilter(ProfileConfigEntity::FIELD_ONLY_ADMIN_ORDERS, true));
} else {
$event->getCriteria()->addFilter(new EqualsFilter(ProfileConfigEntity::FIELD_ONLY_ADMIN_ORDERS, false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function load(Request $request, SalesChannelContext $context): HandlePaym
if ($request->request->getBoolean('updatePayment')) {
$orderId = $request->request->get('orderId');

/** @var OrderEntity|null $order */
$order = $this->orderRepository->search(CriteriaHelper::getCriteriaForOrder($orderId), $context->getContext())->first();
if ($order instanceof OrderEntity && ($transaction = $order->getTransactions()->last()) instanceof OrderTransactionEntity) {
$paymentHandlerIdentifier = $transaction->getPaymentMethod()->getHandlerIdentifier();
Expand All @@ -57,7 +58,7 @@ public function load(Request $request, SalesChannelContext $context): HandlePaym
}

if ($paymentHandlerIdentifier !== null && is_subclass_of($paymentHandlerIdentifier, AbstractPaymentHandler::class)) {
$this->dataValidationService->validatePaymentData(new DataBag($request->request->all()), $order ?? $context);
$this->dataValidationService->validatePaymentData(new DataBag($request->request->all()), $context, $order ?? null);
}

return $this->innerService->load($request, $context);
Expand Down
10 changes: 5 additions & 5 deletions src/Components/Checkout/Service/DataValidationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ public function __construct(
) {
}

public function validatePaymentData(DataBag $parameterBag, SalesChannelContext|OrderEntity $validationScope): void
public function validatePaymentData(DataBag $parameterBag, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): void
{
if ($validationScope instanceof OrderEntity) {
$paymentMethodId = $validationScope->getTransactions()->last()->getPaymentMethodId();
if ($orderEntity instanceof OrderEntity) {
$paymentMethodId = $orderEntity->getTransactions()->last()->getPaymentMethodId();
} else {
$paymentMethodId = $validationScope->getPaymentMethod()->getId();
$paymentMethodId = $salesChannelContext->getPaymentMethod()->getId();
}

$paymentHandler = $this->paymentHandlerRegistry->getPaymentMethodHandler($paymentMethodId);
Expand All @@ -46,7 +46,7 @@ public function validatePaymentData(DataBag $parameterBag, SalesChannelContext|O

/** @var DataBag $_parameterBag */
$_parameterBag = $parameterBag->get(RequestHelper::WRAPPER_KEY, $parameterBag); // paymentDetails is using for pwa request
$validationDefinitions = $paymentHandler->getValidationDefinitions(new RequestDataBag($_parameterBag->all()), $validationScope);
$validationDefinitions = $paymentHandler->getValidationDefinitions(new RequestDataBag($_parameterBag->all()), $salesChannelContext, $orderEntity);

$definitions = new DataValidationDefinition();
DataValidationHelper::addSubConstraints($definitions, $validationDefinitions);
Expand Down
3 changes: 2 additions & 1 deletion src/Components/Checkout/Service/ExtensionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ public function buildPaymentDataExtension(

$searchService = $order instanceof OrderEntity ? $this->profileByOrderEntity : $this->profileBySalesChannelContext;
$profileConfig = $searchService->search(
$searchService->createSearchObject($order ?? $salesChannelContext)->setPaymentMethodId($paymentMethod->getId())
$searchService->createSearchObject($order ?? $salesChannelContext)->setPaymentMethodId($paymentMethod->getId()),
$salesChannelContext
)->first();

if ($profileConfig === null) {
Expand Down
3 changes: 2 additions & 1 deletion src/Components/Checkout/Service/PaymentFilterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public function filterPaymentMethods(PaymentMethodCollection $paymentMethodColle

$searchService = $order instanceof OrderEntity ? $this->profileByOrderEntity : $this->profileBySalesChannelContext;
$profileConfig = $searchService->search(
$searchService->createSearchObject($order ?? $salesChannelContext)->setPaymentMethodId($paymentMethod->getId())
$searchService->createSearchObject($order ?? $salesChannelContext)->setPaymentMethodId($paymentMethod->getId()),
$salesChannelContext
)->first();

if ($profileConfig === null) {
Expand Down
12 changes: 9 additions & 3 deletions src/Components/DeviceFingerprint/Constraint/DfpConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class DfpConstraint extends Constraint

public function __construct(
private readonly DfpServiceInterface $dfpService,
private readonly OrderEntity|SalesChannelContext $object
private readonly SalesChannelContext $salesChannelContext,
private readonly ?OrderEntity $orderEntity = null
) {
parent::__construct();
}
Expand All @@ -38,8 +39,13 @@ public function getDfpService(): DfpServiceInterface
return $this->dfpService;
}

public function getObject(): OrderEntity|SalesChannelContext
public function getOrderEntity(): ?OrderEntity
{
return $this->object;
return $this->orderEntity;
}

public function getSalesChannelContext(): SalesChannelContext
{
return $this->salesChannelContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class DfpConstraintValidator extends ConstraintValidator
*/
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint->getDfpService()->isDfpIdValid($constraint->getObject(), $value)) {
if (!$constraint->getDfpService()->isDfpIdValid($constraint->getSalesChannelContext(), $constraint->getOrderEntity(), $value)) {
$this->context->buildViolation('Provided DFP Token is not valid.')
->setCode(DfpConstraint::ERROR_CODE)
->addViolation();
Expand Down
Loading
Loading