diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e75a76c..e9360f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/Components/Account/Subscriber/AccountSubscriber.php b/src/Components/Account/Subscriber/AccountSubscriber.php index ac7b6d98..9debaba1 100644 --- a/src/Components/Account/Subscriber/AccountSubscriber.php +++ b/src/Components/Account/Subscriber/AccountSubscriber.php @@ -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 { diff --git a/src/Components/AdminOrders/DependencyInjection/services.xml b/src/Components/AdminOrders/DependencyInjection/services.xml index e5ce2b37..7f49c2e3 100644 --- a/src/Components/AdminOrders/DependencyInjection/services.xml +++ b/src/Components/AdminOrders/DependencyInjection/services.xml @@ -13,6 +13,9 @@ + + + %ratepay.admin.storefront-login.token% diff --git a/src/Components/AdminOrders/DependencyInjection/subscriber.xml b/src/Components/AdminOrders/DependencyInjection/subscriber.xml index 7deb4fc2..fec141b3 100644 --- a/src/Components/AdminOrders/DependencyInjection/subscriber.xml +++ b/src/Components/AdminOrders/DependencyInjection/subscriber.xml @@ -9,14 +9,9 @@ - - %ratepay.admin.storefront-login.token% - - - - %ratepay.admin.storefront-login.token% - - + + + diff --git a/src/Components/AdminOrders/Service/DfpService.php b/src/Components/AdminOrders/Service/DfpService.php index de2b1f22..3444c147 100644 --- a/src/Components/AdminOrders/Service/DfpService.php +++ b/src/Components/AdminOrders/Service/DfpService.php @@ -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); } } diff --git a/src/Components/AdminOrders/Service/SessionService.php b/src/Components/AdminOrders/Service/SessionService.php new file mode 100644 index 00000000..e34bf146 --- /dev/null +++ b/src/Components/AdminOrders/Service/SessionService.php @@ -0,0 +1,85 @@ +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; + } +} diff --git a/src/Components/AdminOrders/Subscriber/LoginSubscriber.php b/src/Components/AdminOrders/Subscriber/LoginSubscriber.php new file mode 100644 index 00000000..000c31d0 --- /dev/null +++ b/src/Components/AdminOrders/Subscriber/LoginSubscriber.php @@ -0,0 +1,43 @@ + ['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); + } +} diff --git a/src/Components/AdminOrders/Subscriber/PageSubscriber.php b/src/Components/AdminOrders/Subscriber/PageSubscriber.php index a9fb4623..306afb82 100644 --- a/src/Components/AdminOrders/Subscriber/PageSubscriber.php +++ b/src/Components/AdminOrders/Subscriber/PageSubscriber.php @@ -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 ) { } @@ -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), + ]); } } diff --git a/src/Components/AdminOrders/Subscriber/ProfileConfigSubscriber.php b/src/Components/AdminOrders/Subscriber/ProfileConfigSubscriber.php index c3aa2622..3c1c2276 100644 --- a/src/Components/AdminOrders/Subscriber/ProfileConfigSubscriber.php +++ b/src/Components/AdminOrders/Subscriber/ProfileConfigSubscriber.php @@ -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; @@ -21,7 +22,7 @@ class ProfileConfigSubscriber implements EventSubscriberInterface { public function __construct( private readonly RequestStack $requestStack, - private readonly string $sessionKey + private readonly SessionService $sessionService ) { } @@ -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)); diff --git a/src/Components/Checkout/SalesChannel/HandlePaymentMethodRoute.php b/src/Components/Checkout/SalesChannel/HandlePaymentMethodRoute.php index aedd2124..965248d3 100644 --- a/src/Components/Checkout/SalesChannel/HandlePaymentMethodRoute.php +++ b/src/Components/Checkout/SalesChannel/HandlePaymentMethodRoute.php @@ -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(); @@ -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); diff --git a/src/Components/Checkout/Service/DataValidationService.php b/src/Components/Checkout/Service/DataValidationService.php index 7bb12494..e182a913 100644 --- a/src/Components/Checkout/Service/DataValidationService.php +++ b/src/Components/Checkout/Service/DataValidationService.php @@ -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); @@ -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); diff --git a/src/Components/Checkout/Service/ExtensionService.php b/src/Components/Checkout/Service/ExtensionService.php index 906c63d7..99a959c6 100644 --- a/src/Components/Checkout/Service/ExtensionService.php +++ b/src/Components/Checkout/Service/ExtensionService.php @@ -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) { diff --git a/src/Components/Checkout/Service/PaymentFilterService.php b/src/Components/Checkout/Service/PaymentFilterService.php index 9177eb45..e37b513f 100644 --- a/src/Components/Checkout/Service/PaymentFilterService.php +++ b/src/Components/Checkout/Service/PaymentFilterService.php @@ -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) { diff --git a/src/Components/DeviceFingerprint/Constraint/DfpConstraint.php b/src/Components/DeviceFingerprint/Constraint/DfpConstraint.php index a8a88e61..18706bf0 100644 --- a/src/Components/DeviceFingerprint/Constraint/DfpConstraint.php +++ b/src/Components/DeviceFingerprint/Constraint/DfpConstraint.php @@ -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(); } @@ -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; } } diff --git a/src/Components/DeviceFingerprint/Constraint/DfpConstraintValidator.php b/src/Components/DeviceFingerprint/Constraint/DfpConstraintValidator.php index 6c577b3a..52ac344d 100644 --- a/src/Components/DeviceFingerprint/Constraint/DfpConstraintValidator.php +++ b/src/Components/DeviceFingerprint/Constraint/DfpConstraintValidator.php @@ -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(); diff --git a/src/Components/DeviceFingerprint/DfpService.php b/src/Components/DeviceFingerprint/DfpService.php index e2de6323..03ddd4ab 100644 --- a/src/Components/DeviceFingerprint/DfpService.php +++ b/src/Components/DeviceFingerprint/DfpService.php @@ -40,13 +40,13 @@ public function __construct( * provide the user-agent via header or a request variable `userAgent` to generate a more unique device-identifier * the request-variable is prioritized */ - public function generatedDfpId(Request $request, OrderEntity|SalesChannelContext $baseData): ?string + public function generatedDfpId(Request $request, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): ?string { - if (!$this->isDfpRequired($baseData)) { + if (!$this->isDfpRequired($salesChannelContext, $orderEntity)) { return null; } - if ($baseData instanceof OrderEntity && $token = $this->getOrderDeviceToken($baseData)) { + if ($orderEntity instanceof OrderEntity && $token = $this->getOrderDeviceToken($orderEntity)) { // token has been used for (failed) payment. So we can reuse it. return $token; } @@ -57,29 +57,29 @@ public function generatedDfpId(Request $request, OrderEntity|SalesChannelContext $dataForId = [ ((string) $userAgent), ]; - if ($baseData instanceof SalesChannelContext) { - $dataForId[] = (string) $this->getCustomerFallBack($baseData); + if (!$orderEntity instanceof OrderEntity) { + $dataForId[] = (string) $this->getCustomerFallBack($salesChannelContext); } $generatedId = md5(implode('', $dataForId)); - $prefix = $this->getDfpPrefix($baseData); + $prefix = $this->getDfpPrefix($orderEntity ?: $salesChannelContext); // replace the beginning of the generated id with the generated prefix return $prefix . substr($generatedId, strlen($prefix)); } - public function isDfpIdValid(OrderEntity|SalesChannelContext $baseData, string $dfpId = null): bool + public function isDfpIdValid(SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null, string $dfpId = null): bool { - $prefix = $this->getDfpPrefix($baseData); + $prefix = $this->getDfpPrefix($salesChannelContext); // verify if the prefix is at the beginning of the id return str_starts_with((string) $dfpId, $prefix); } - public function getDfpSnippet(Request $request, OrderEntity|SalesChannelContext $baseData): ?string + public function getDfpSnippet(Request $request, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): ?string { - if ($id = $this->generatedDfpId($request, $baseData)) { + if ($id = $this->generatedDfpId($request, $salesChannelContext, $orderEntity)) { $dfpHelper = new DeviceFingerprint($this->configService->getDeviceFingerprintSnippetId()); return str_replace('\"', '"', $dfpHelper->getDeviceIdentSnippet($id)); } @@ -87,7 +87,7 @@ public function getDfpSnippet(Request $request, OrderEntity|SalesChannelContext return null; } - public function isDfpRequired(OrderEntity|SalesChannelContext $object): bool + public function isDfpRequired(SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): bool { return true; } diff --git a/src/Components/DeviceFingerprint/DfpServiceInterface.php b/src/Components/DeviceFingerprint/DfpServiceInterface.php index d4784267..159e0d55 100644 --- a/src/Components/DeviceFingerprint/DfpServiceInterface.php +++ b/src/Components/DeviceFingerprint/DfpServiceInterface.php @@ -16,14 +16,14 @@ interface DfpServiceInterface { - public function generatedDfpId(Request $request, OrderEntity|SalesChannelContext $baseData): ?string; + public function generatedDfpId(Request $request, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): ?string; - public function getDfpSnippet(Request $request, OrderEntity|SalesChannelContext $baseData): ?string; + public function getDfpSnippet(Request $request, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): ?string; /** * validates the dfp-id if the id has been generated by the plugin, or if it has been modified */ - public function isDfpIdValid(OrderEntity|SalesChannelContext $baseData, string $dfpId = null): bool; + public function isDfpIdValid(SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null, string $dfpId = null): bool; - public function isDfpRequired(OrderEntity|SalesChannelContext $object): bool; + public function isDfpRequired(SalesChannelContext $salesChannelContext, ?OrderEntity $orderEntity = null): bool; } diff --git a/src/Components/DeviceFingerprint/Subscriber/DeviceFingerprintSubscriber.php b/src/Components/DeviceFingerprint/Subscriber/DeviceFingerprintSubscriber.php index 5bc84d09..a36307c7 100644 --- a/src/Components/DeviceFingerprint/Subscriber/DeviceFingerprintSubscriber.php +++ b/src/Components/DeviceFingerprint/Subscriber/DeviceFingerprintSubscriber.php @@ -66,27 +66,25 @@ public function onPaymentRequest(BuildEvent $buildEvent): void public function addRatepayTemplateData(PaymentDataExtensionBuilt $event): void { - $baseData = $event->getOrderEntity() ?? $event->getSalesChannelContext(); - - $snippet = $this->dfpService->getDfpSnippet($this->requestStack->getCurrentRequest(), $baseData); + $snippet = $this->dfpService->getDfpSnippet($this->requestStack->getCurrentRequest(), $event->getSalesChannelContext(), $event->getOrderEntity()); if ($snippet) { $event->getExtension()->set('dfp', [ 'snippetId' => $this->configService->getDeviceFingerprintSnippetId(), 'html' => $snippet, - 'deviceIdentToken' => $this->dfpService->generatedDfpId($this->requestStack->getCurrentRequest(), $baseData), + 'deviceIdentToken' => $this->dfpService->generatedDfpId($this->requestStack->getCurrentRequest(), $event->getSalesChannelContext(), $event->getOrderEntity()), ]); } } public function addValidationDefinition(ValidationDefinitionCollectEvent $event): void { - if (!$this->dfpService->isDfpRequired($event->getBaseData())) { + if (!$this->dfpService->isDfpRequired($event->getSalesChannelContext(), $event->getOrderEntity())) { return; } $event->addDefinition('deviceIdentToken', [ new NotBlank(), - new DfpConstraint($this->dfpService, $event->getBaseData()), + new DfpConstraint($this->dfpService, $event->getSalesChannelContext(), $event->getOrderEntity()), ]); } diff --git a/src/Components/InstallmentCalculator/Service/InstallmentService.php b/src/Components/InstallmentCalculator/Service/InstallmentService.php index b6a4d0af..603451a6 100644 --- a/src/Components/InstallmentCalculator/Service/InstallmentService.php +++ b/src/Components/InstallmentCalculator/Service/InstallmentService.php @@ -254,12 +254,13 @@ protected function getInstallmentBuilders(InstallmentCalculatorContext $context) $shopwareContext = $context->getSalesChannelContext()->getContext(); if ($context->getProfileConfigSearch() instanceof ProfileConfigSearch) { - $profileConfigs = $this->profileSearchService->search($context->getProfileConfigSearch()); + $profileConfigs = $this->profileSearchService->search($context->getProfileConfigSearch(), $salesChannelContext); } else { $searchService = $context->getOrder() instanceof OrderEntity ? $this->profileByOrderEntity : $this->profileBySalesChannelContext; $profileConfigs = $searchService->search( $searchService->createSearchObject($context->getOrder() ?? $salesChannelContext) - ->setPaymentMethodId($context->getPaymentMethodId()) + ->setPaymentMethodId($context->getPaymentMethodId()), + $salesChannelContext ); } diff --git a/src/Components/PaymentHandler/AbstractPaymentHandler.php b/src/Components/PaymentHandler/AbstractPaymentHandler.php index 2f19dfa3..8e9bde76 100644 --- a/src/Components/PaymentHandler/AbstractPaymentHandler.php +++ b/src/Components/PaymentHandler/AbstractPaymentHandler.php @@ -12,7 +12,6 @@ namespace Ratepay\RpayPayments\Components\PaymentHandler; use DateTimeInterface; -use InvalidArgumentException; use RatePAY\Model\Response\PaymentRequest; use Ratepay\RpayPayments\Components\PaymentHandler\Constraint\Birthday; use Ratepay\RpayPayments\Components\PaymentHandler\Constraint\BirthdayNotBlank; @@ -22,6 +21,7 @@ use Ratepay\RpayPayments\Components\PaymentHandler\Event\PaymentSuccessfulEvent; use Ratepay\RpayPayments\Components\PaymentHandler\Event\ValidationDefinitionCollectEvent; use Ratepay\RpayPayments\Components\ProfileConfig\Exception\ProfileNotFoundException; +use Ratepay\RpayPayments\Components\ProfileConfig\Service\Search\ProfileByOrderEntity; use Ratepay\RpayPayments\Components\ProfileConfig\Service\Search\ProfileSearchService; use Ratepay\RpayPayments\Components\RatepayApi\Dto\PaymentRequestData; use Ratepay\RpayPayments\Components\RatepayApi\Service\Request\PaymentRequestService; @@ -61,7 +61,8 @@ public function __construct( private readonly EventDispatcherInterface $eventDispatcher, private readonly PluginConfigService $configService, private readonly RequestStack $requestStack, - private readonly ProfileSearchService $profileSearchService + private readonly ProfileSearchService $profileSearchService, + private readonly ProfileByOrderEntity $profileByOrderEntitySearchService ) { } @@ -89,13 +90,19 @@ public function pay(SyncPaymentTransactionStruct $transaction, RequestDataBag $d if ($ratepayData->has('profile_uuid')) { $profile = $this->profileSearchService->getProfileConfigById($ratepayData->get('profile_uuid')); - if (!$profile instanceof Entity) { - throw new ProfileNotFoundException(); - } + } else { + $profile = $this->profileByOrderEntitySearchService->search( + $this->profileByOrderEntitySearchService->createSearchObject($order), + $salesChannelContext + )->first(); + } - $paymentRequestData->setProfileConfig($profile); + if (!$profile instanceof Entity) { + throw new ProfileNotFoundException(); } + $paymentRequestData->setProfileConfig($profile); + $this->eventDispatcher->dispatch(new BeforePaymentEvent($paymentRequestData)); $requestBuilder = $this->paymentRequestService->doRequest($paymentRequestData); @@ -141,24 +148,19 @@ public function pay(SyncPaymentTransactionStruct $transaction, RequestDataBag $d } } - /** - * @param OrderEntity|SalesChannelContext $baseData - */ - public function getValidationDefinitions(DataBag $requestDataBag, $baseData): array + public function getValidationDefinitions(DataBag $requestDataBag, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): array { $validations = []; /** @var DataBag $ratepayData */ $ratepayData = RequestHelper::getRatepayData($requestDataBag) ?: new ParameterBag(); - if ($baseData instanceof SalesChannelContext) { - $birthday = $baseData->getCustomer()->getBirthday(); - $isCompany = !empty($baseData->getCustomer()->getActiveBillingAddress()->getCompany()); - } elseif ($baseData instanceof OrderEntity) { - $birthday = $baseData->getOrderCustomer()->getCustomer()->getBirthday(); - $isCompany = !empty($baseData->getAddresses()->get($baseData->getBillingAddressId())->getCompany()); + if ($orderEntity instanceof OrderEntity) { + $birthday = $orderEntity->getOrderCustomer()->getCustomer()->getBirthday(); + $isCompany = !empty($orderEntity->getAddresses()->get($orderEntity->getBillingAddressId())->getCompany()); } else { - throw new InvalidArgumentException('please provide a ' . SalesChannelContext::class . ' or an ' . OrderEntity::class . '. You provided a ' . $baseData::class . ' object'); + $birthday = $salesChannelContext->getCustomer()->getBirthday(); + $isCompany = !empty($salesChannelContext->getCustomer()->getActiveBillingAddress()->getCompany()); } if ($ratepayData->get('birthday') || (!$birthday instanceof DateTimeInterface && $isCompany === false)) { @@ -174,7 +176,7 @@ public function getValidationDefinitions(DataBag $requestDataBag, $baseData): ar } /** @var ValidationDefinitionCollectEvent $event */ - $event = $this->eventDispatcher->dispatch(new ValidationDefinitionCollectEvent($validations, $requestDataBag, $baseData)); + $event = $this->eventDispatcher->dispatch(new ValidationDefinitionCollectEvent($validations, $requestDataBag, $salesChannelContext, $orderEntity)); return $event->getDefinitions(); } diff --git a/src/Components/PaymentHandler/DebitPaymentHandler.php b/src/Components/PaymentHandler/DebitPaymentHandler.php index 7d048bd6..c4f2ee4a 100644 --- a/src/Components/PaymentHandler/DebitPaymentHandler.php +++ b/src/Components/PaymentHandler/DebitPaymentHandler.php @@ -11,7 +11,9 @@ namespace Ratepay\RpayPayments\Components\PaymentHandler; +use Shopware\Core\Checkout\Order\OrderEntity; use Shopware\Core\Framework\Validation\DataBag\DataBag; +use Shopware\Core\System\SalesChannel\SalesChannelContext; class DebitPaymentHandler extends AbstractPaymentHandler { @@ -22,11 +24,11 @@ class DebitPaymentHandler extends AbstractPaymentHandler */ final public const RATEPAY_METHOD = 'ELV'; - public function getValidationDefinitions(DataBag $requestDataBag, $baseData): array + public function getValidationDefinitions(DataBag $requestDataBag, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): array { return array_merge( - parent::getValidationDefinitions($requestDataBag, $baseData), - $this->getDebitConstraints($baseData) + parent::getValidationDefinitions($requestDataBag, $salesChannelContext, $orderEntity), + $this->getDebitConstraints($orderEntity ?: $salesChannelContext) ); } diff --git a/src/Components/PaymentHandler/Event/ValidationDefinitionCollectEvent.php b/src/Components/PaymentHandler/Event/ValidationDefinitionCollectEvent.php index c23d9a9d..9d322187 100644 --- a/src/Components/PaymentHandler/Event/ValidationDefinitionCollectEvent.php +++ b/src/Components/PaymentHandler/Event/ValidationDefinitionCollectEvent.php @@ -22,7 +22,8 @@ public function __construct( // the property needs to be writeable, because we are using reference on this property protected array $definitions, private readonly DataBag $requestDataBag, - private readonly OrderEntity|SalesChannelContext $baseData + private readonly SalesChannelContext $salesChannelContext, + private readonly ?OrderEntity $orderEntity = null ) { } @@ -49,13 +50,26 @@ public function addDefinition(string $field, array|Constraint $constraint): self return $this; } + /** + * @deprecated please use getOrderEntity or getSalesChannelContext + */ public function getBaseData(): OrderEntity|SalesChannelContext { - return $this->baseData; + return $this->orderEntity ?: $this->salesChannelContext; } public function getRequestDataBag(): DataBag { return $this->requestDataBag; } + + public function getSalesChannelContext(): SalesChannelContext + { + return $this->salesChannelContext; + } + + public function getOrderEntity(): ?OrderEntity + { + return $this->orderEntity; + } } diff --git a/src/Components/PaymentHandler/InstallmentPaymentHandler.php b/src/Components/PaymentHandler/InstallmentPaymentHandler.php index 64ea0393..61ee594d 100644 --- a/src/Components/PaymentHandler/InstallmentPaymentHandler.php +++ b/src/Components/PaymentHandler/InstallmentPaymentHandler.php @@ -12,8 +12,10 @@ namespace Ratepay\RpayPayments\Components\PaymentHandler; use Ratepay\RpayPayments\Util\RequestHelper; +use Shopware\Core\Checkout\Order\OrderEntity; use Shopware\Core\Framework\Validation\DataBag\DataBag; use Shopware\Core\Framework\Validation\DataValidationDefinition; +use Shopware\Core\System\SalesChannel\SalesChannelContext; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\NotBlank; @@ -29,9 +31,9 @@ class InstallmentPaymentHandler extends AbstractPaymentHandler /** * @return DataValidationDefinition[] */ - public function getValidationDefinitions(DataBag $requestDataBag, $baseData): array + public function getValidationDefinitions(DataBag $requestDataBag, SalesChannelContext $salesChannelContext, OrderEntity $orderEntity = null): array { - $validations = parent::getValidationDefinitions($requestDataBag, $baseData); + $validations = parent::getValidationDefinitions($requestDataBag, $salesChannelContext, $orderEntity); $installment = new DataValidationDefinition(); $installment->add( @@ -63,7 +65,7 @@ public function getValidationDefinitions(DataBag $requestDataBag, $baseData): ar $installmentData = $ratepayData->get('installment'); if ($installmentData && $installmentData->get('paymentType') && $installmentData->get('paymentType') === 'DIRECT-DEBIT') { - $validations = array_merge($validations, $this->getDebitConstraints($baseData)); + $validations = array_merge($validations, $this->getDebitConstraints($orderEntity ?: $salesChannelContext)); } $validations['installment'] = $installment; diff --git a/src/Components/ProfileConfig/Event/CreateProfileConfigCriteriaEvent.php b/src/Components/ProfileConfig/Event/CreateProfileConfigCriteriaEvent.php index 94316795..ada5e93e 100644 --- a/src/Components/ProfileConfig/Event/CreateProfileConfigCriteriaEvent.php +++ b/src/Components/ProfileConfig/Event/CreateProfileConfigCriteriaEvent.php @@ -13,6 +13,7 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\System\SalesChannel\SalesChannelContext; class CreateProfileConfigCriteriaEvent { @@ -26,7 +27,8 @@ public function __construct( private readonly bool $differentAddresses, private readonly bool $isB2b, private readonly float $totalAmount, - private readonly Context $context + private readonly Context $context, + private readonly SalesChannelContext $salesChannelContext ) { } @@ -79,4 +81,9 @@ public function getContext(): Context { return $this->context; } + + public function getSalesChannelContext(): SalesChannelContext + { + return $this->salesChannelContext; + } } diff --git a/src/Components/ProfileConfig/Service/Search/ProfileByOrderEntity.php b/src/Components/ProfileConfig/Service/Search/ProfileByOrderEntity.php index 6ea56a0f..626cd04e 100644 --- a/src/Components/ProfileConfig/Service/Search/ProfileByOrderEntity.php +++ b/src/Components/ProfileConfig/Service/Search/ProfileByOrderEntity.php @@ -15,6 +15,7 @@ use Ratepay\RpayPayments\Components\ProfileConfig\Util\AddressUtil; use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\System\SalesChannel\SalesChannelContext; class ProfileByOrderEntity implements ProfileSearchInterface { @@ -40,8 +41,8 @@ public function createSearchObject(OrderEntity $order): ProfileConfigSearch ->setTotalAmount($order->getPrice()->getTotalPrice()); } - public function search(ProfileConfigSearch $profileConfigSearch): ProfileConfigCollection + public function search(ProfileConfigSearch $profileConfigSearch, SalesChannelContext $salesChannelContext): ProfileConfigCollection { - return $this->searchService->search($profileConfigSearch); + return $this->searchService->search($profileConfigSearch, $salesChannelContext); } } diff --git a/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContext.php b/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContext.php index 56a096ce..c2d1fe56 100644 --- a/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContext.php +++ b/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContext.php @@ -30,8 +30,8 @@ public function createSearchObject(SalesChannelContext $salesChannelContext): ?P return $this->searchService->createSearchObject($salesChannelContext, $cart); } - public function search(ProfileConfigSearch $profileConfigSearch): ProfileConfigCollection + public function search(ProfileConfigSearch $profileConfigSearch, SalesChannelContext $salesChannelContext): ProfileConfigCollection { - return $this->searchService->search($profileConfigSearch); + return $this->searchService->search($profileConfigSearch, $salesChannelContext); } } diff --git a/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContextAndCart.php b/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContextAndCart.php index a8f82f8d..f746e4af 100644 --- a/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContextAndCart.php +++ b/src/Components/ProfileConfig/Service/Search/ProfileBySalesChannelContextAndCart.php @@ -45,8 +45,8 @@ public function createSearchObject(SalesChannelContext $salesChannelContext, Car ->setTotalAmount($cart->getPrice()->getTotalPrice()); } - public function search(ProfileConfigSearch $profileConfigSearch): ProfileConfigCollection + public function search(ProfileConfigSearch $profileConfigSearch, SalesChannelContext $salesChannelContext): ProfileConfigCollection { - return $this->searchService->search($profileConfigSearch); + return $this->searchService->search($profileConfigSearch, $salesChannelContext); } } diff --git a/src/Components/ProfileConfig/Service/Search/ProfileSearchInterface.php b/src/Components/ProfileConfig/Service/Search/ProfileSearchInterface.php index fd7a68a7..d922663a 100644 --- a/src/Components/ProfileConfig/Service/Search/ProfileSearchInterface.php +++ b/src/Components/ProfileConfig/Service/Search/ProfileSearchInterface.php @@ -12,8 +12,9 @@ use Ratepay\RpayPayments\Components\ProfileConfig\Dto\ProfileConfigSearch; use Ratepay\RpayPayments\Components\ProfileConfig\Model\Collection\ProfileConfigCollection; +use Shopware\Core\System\SalesChannel\SalesChannelContext; interface ProfileSearchInterface { - public function search(ProfileConfigSearch $profileConfigSearch): ProfileConfigCollection; + public function search(ProfileConfigSearch $profileConfigSearch, SalesChannelContext $salesChannelContext): ProfileConfigCollection; } diff --git a/src/Components/ProfileConfig/Service/Search/ProfileSearchService.php b/src/Components/ProfileConfig/Service/Search/ProfileSearchService.php index db9c37be..89609c65 100644 --- a/src/Components/ProfileConfig/Service/Search/ProfileSearchService.php +++ b/src/Components/ProfileConfig/Service/Search/ProfileSearchService.php @@ -23,6 +23,7 @@ use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter; +use Shopware\Core\System\SalesChannel\SalesChannelContext; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class ProfileSearchService implements ProfileSearchInterface @@ -36,7 +37,7 @@ public function __construct( $this->context = Context::createDefaultContext(); } - public function search(ProfileConfigSearch $profileConfigSearch): ProfileConfigCollection + public function search(ProfileConfigSearch $profileConfigSearch, SalesChannelContext $salesChannelContext): ProfileConfigCollection { $criteria = new Criteria(); $criteria->addAssociation(ProfileConfigEntity::FIELD_PAYMENT_METHOD_CONFIGS); @@ -126,7 +127,8 @@ public function search(ProfileConfigSearch $profileConfigSearch): ProfileConfigC $profileConfigSearch->isNeedsAllowDifferentAddress(), $profileConfigSearch->isB2b(), $profileConfigSearch->getTotalAmount(), - $this->context + $this->context, + $salesChannelContext )); // TODO implement diff --git a/src/Components/RatepayApi/Service/Request/PaymentRequestService.php b/src/Components/RatepayApi/Service/Request/PaymentRequestService.php index e1784567..887e8ae7 100644 --- a/src/Components/RatepayApi/Service/Request/PaymentRequestService.php +++ b/src/Components/RatepayApi/Service/Request/PaymentRequestService.php @@ -14,9 +14,6 @@ use RatePAY\Model\Request\SubModel\Content; use RatePAY\Model\Request\SubModel\Head; use RatePAY\RequestBuilder; -use Ratepay\RpayPayments\Components\ProfileConfig\Model\ProfileConfigEntity; -use Ratepay\RpayPayments\Components\ProfileConfig\Service\Search\ProfileByOrderEntity; -use Ratepay\RpayPayments\Components\ProfileConfig\Service\Search\ProfileSearchService; use Ratepay\RpayPayments\Components\RatepayApi\Dto\AbstractRequestData; use Ratepay\RpayPayments\Components\RatepayApi\Dto\PaymentRequestData; use Ratepay\RpayPayments\Components\RatepayApi\Factory\CustomerFactory; @@ -62,8 +59,6 @@ class PaymentRequestService extends AbstractRequest public function __construct( EventDispatcherInterface $eventDispatcher, HeadFactory $headFactory, - private readonly ProfileSearchService $profileConfigSearch, - private readonly ProfileByOrderEntity $profileConfigOrderSearch, private readonly ShoppingBasketFactory $shoppingBasketFactory, private readonly CustomerFactory $customerFactory, private readonly PaymentFactory $paymentFactory, @@ -90,20 +85,6 @@ protected function getRequestHead(AbstractRequestData $requestData): Head return $head; } - protected function getProfileConfig(AbstractRequestData $requestData): ProfileConfigEntity - { - if ($requestData->getProfileConfig() instanceof ProfileConfigEntity) { - // the given profile config should be prioritised - return $requestData->getProfileConfig(); - } - - /** @var PaymentRequestData $requestData */ - $search = $this->profileConfigOrderSearch->createSearchObject($requestData->getOrder()); - $search->setPaymentMethodId($requestData->getTransaction()->getPaymentMethodId()); - - return $this->profileConfigSearch->search($search)->first(); - } - protected function supportsRequestData(AbstractRequestData $requestData): bool { return $requestData instanceof PaymentRequestData; diff --git a/src/Resources/app/storefront/src/scss/base.scss b/src/Resources/app/storefront/src/scss/base.scss index 5a0e70a0..f2f1dc27 100644 --- a/src/Resources/app/storefront/src/scss/base.scss +++ b/src/Resources/app/storefront/src/scss/base.scss @@ -12,19 +12,50 @@ } .ratepay-admin-session-notice { + --border-color: #000; + --bg-color: #ccc; position: fixed; width: 320px; bottom: 0; left: 0; - background: #ccc; + background: var(--bg-color); padding: 20px; z-index: 3; + border-top: 1px solid var(--border-color); + border-right: 1px solid var(--border-color); + + p:last-child { + margin-bottom: 0; + } .close { cursor: pointer; .icon { - color: #000; + --scale: 1; + position: absolute; + top: 0; + right: 0; + transform: translate(50%, -50%) scale(var(--scale)); + transition: transform 0.25s; + background: var(--bg-color); + border-radius: 50%; + padding: 4px; + border: 1px solid var(--border-color); + display: flex; + align-items: center; + width: auto; + justify-content: center; + height: 1.5rem; + color: var(--border-color); + + &:hover { + --scale: 1.25; + } + + svg { + position: static; + } } } } diff --git a/src/Resources/snippet/de_DE/messages.de-DE.json b/src/Resources/snippet/de_DE/messages.de-DE.json index f21c14f1..fa8570db 100644 --- a/src/Resources/snippet/de_DE/messages.de-DE.json +++ b/src/Resources/snippet/de_DE/messages.de-DE.json @@ -15,7 +15,8 @@ "ratepay": { "storefront": { "admin-order": { - "notice": "Sie erstellen gerade eine Bestellung im Admin Modus. Ihnen stehen die Ratepay Profile zur Verfügung, die im Admin als 'für Admin-Bestellung' definiert sind. Die anderen Profile sind nicht verfügbar.", + "notice": "Sie erstellen gerade eine Bestellung im Admin Modus. Ihnen stehen nur die Ratepay Profile zur Verfügung, die im Admin als 'für Admin-Bestellung' definiert sind. Die anderen Profile sind nicht verfügbar.", + "noticeLoggedInAsCustomer": "Um wieder auf die regulären Profile zuzugreifen, loggen Sie sich bitte aus dem Kundenaccount aus.", "close-session": "Sitzung beenden", "session-destroyed": "Die Admin-Sitzung wurde beendet. Sie können nun wieder Bestellungen im normalen Modus erstellen." }, diff --git a/src/Resources/snippet/en_GB/messages.en-GB.json b/src/Resources/snippet/en_GB/messages.en-GB.json index 4628b018..3eb93f41 100644 --- a/src/Resources/snippet/en_GB/messages.en-GB.json +++ b/src/Resources/snippet/en_GB/messages.en-GB.json @@ -15,7 +15,8 @@ "ratepay": { "storefront": { "admin-order": { - "notice": "You are going create a new order as admin. So the Ratepay profiles, which are available for admin orders will be used. The other profiles will be not available.", + "notice": "You are going create a new order as admin. So the Ratepay profiles, which are available for admin orders only will be used. The other profiles will be not available.", + "noticeLoggedInAsCustomer": "To regain access to the regular profiles again, please log out of the customer account.", "close-session": "Terminate session", "session-destroyed": "The admin session has been terminated. Now you can create normal orders again." }, diff --git a/src/Resources/views/storefront/base.html.twig b/src/Resources/views/storefront/base.html.twig index 90a9a682..e2fd6d61 100644 --- a/src/Resources/views/storefront/base.html.twig +++ b/src/Resources/views/storefront/base.html.twig @@ -3,15 +3,20 @@ {% block base_main %} {{ parent() }} - {% if ratepayAdminOrderSession %} + {% if ratepayAdminOrderSession and ratepayAdminOrderSession.active %}
{% sw_icon 'x' %}
- {{ "ratepay.storefront.admin-order.notice"|trans }} -
-
- {{ "ratepay.storefront.admin-order.close-session"|trans }} +

{{ "ratepay.storefront.admin-order.notice"|trans }}

+ {% if ratepayAdminOrderSession.isLoggedInAsCustomer and not ratepayAdminOrderSession.canLogout %} +

{{ "ratepay.storefront.admin-order.noticeLoggedInAsCustomer"|trans }}

+ {% endif %}
+ {% if ratepayAdminOrderSession.canLogout %} + + {% endif %}
{% endif %} {% endblock %}