From 86086bd42e6877b6e54a22c03ed54b0283558450 Mon Sep 17 00:00:00 2001
From: Frederik Rommel <15031079+rommelfreddy@users.noreply.github.com>
Date: Fri, 13 Sep 2024 18:45:51 +0200
Subject: [PATCH] RATESWSX-306: installment: prevent fatal error message in
checkout on unreachable gateway (#65)
---
CHANGELOG.md | 2 +
.../DependencyInjection/subscriber.xml | 8 ++-
.../Subscriber/BuildPaymentSubscriber.php | 21 ++++--
.../Subscriber/CheckoutSubscriber.php | 69 ++++++++++++++++---
.../RequestBuilderFailedSubscriber.php | 2 +-
.../InstallmentPaymentHandler.php | 2 +-
.../Event/RequestBuilderFailedEvent.php | 14 +++-
.../Service/Request/AbstractRequest.php | 14 ++--
.../snippet/de_DE/messages.de-DE.json | 1 +
.../snippet/en_GB/messages.en-GB.json | 1 +
.../checkout/ratepay/installment.html.twig | 4 +-
11 files changed, 110 insertions(+), 28 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 826c55f9..1e75a76c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## WIP
+* RATESWX-306: installment: prevent fatal error message in checkout on unreachable gateway
+
## Version 7.0.1 - Released on 2024-06-07
RATESWSX-303: fix admin-session logout endless redirect & make admin-session urls more unified
diff --git a/src/Components/InstallmentCalculator/DependencyInjection/subscriber.xml b/src/Components/InstallmentCalculator/DependencyInjection/subscriber.xml
index 927f6c1f..c966af9e 100644
--- a/src/Components/InstallmentCalculator/DependencyInjection/subscriber.xml
+++ b/src/Components/InstallmentCalculator/DependencyInjection/subscriber.xml
@@ -16,8 +16,12 @@
-
-
+
+
+
+
+
+
diff --git a/src/Components/InstallmentCalculator/Subscriber/BuildPaymentSubscriber.php b/src/Components/InstallmentCalculator/Subscriber/BuildPaymentSubscriber.php
index 039c0e7b..62ebf1c0 100644
--- a/src/Components/InstallmentCalculator/Subscriber/BuildPaymentSubscriber.php
+++ b/src/Components/InstallmentCalculator/Subscriber/BuildPaymentSubscriber.php
@@ -31,13 +31,16 @@
use Ratepay\RpayPayments\Util\RequestHelper;
use RuntimeException;
use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
+use Shopware\Core\Framework\Adapter\Translation\AbstractTranslator;
use Shopware\Core\Framework\Validation\DataBag\DataBag;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Throwable;
class BuildPaymentSubscriber implements EventSubscriberInterface
{
public function __construct(
- private readonly InstallmentService $installmentService
+ private readonly InstallmentService $installmentService,
+ private readonly AbstractTranslator $translator
) {
}
@@ -54,7 +57,9 @@ public function buildPayment(BuildEvent $event): void
/** @var PaymentRequestData $requestData */
$requestData = $event->getRequestData();
- if (MethodHelper::isInstallmentMethod($requestData->getTransaction()->getPaymentMethod()->getHandlerIdentifier())) {
+ $paymentMethod = $requestData->getTransaction()->getPaymentMethod();
+
+ if (MethodHelper::isInstallmentMethod($paymentMethod->getHandlerIdentifier())) {
/** @var Payment $paymentObject */
$paymentObject = $event->getBuildData();
@@ -71,9 +76,15 @@ public function buildPayment(BuildEvent $event): void
);
$calcContext->setTotalAmount($paymentObject->getAmount());
$calcContext->setOrder($requestData->getOrder());
- $calcContext->setPaymentMethod($requestData->getTransaction()->getPaymentMethod());
-
- $plan = $this->installmentService->getInstallmentPlanData($calcContext);
+ $calcContext->setPaymentMethod($paymentMethod);
+
+ try {
+ $plan = $this->installmentService->getInstallmentPlanData($calcContext);
+ } catch (Throwable $exception) {
+ throw new RuntimeException($this->translator->trans('checkout.error.RATEPAY_INSTALLMENT_CAN_NOT_BE_LOADED', [
+ '%method%' => $paymentMethod->getTranslated()['name'] ?? $paymentMethod->getName(),
+ ]), $exception->getCode(), previous: $exception);
+ }
if (PlanHasher::isPlanEqualWithHash($requestedInstallment->get('hash'), $plan)) {
throw new Exception('the hash value of the calculated plan does not match the given hash');
diff --git a/src/Components/InstallmentCalculator/Subscriber/CheckoutSubscriber.php b/src/Components/InstallmentCalculator/Subscriber/CheckoutSubscriber.php
index f41977e7..367f4afc 100644
--- a/src/Components/InstallmentCalculator/Subscriber/CheckoutSubscriber.php
+++ b/src/Components/InstallmentCalculator/Subscriber/CheckoutSubscriber.php
@@ -11,19 +11,29 @@
namespace Ratepay\RpayPayments\Components\InstallmentCalculator\Subscriber;
+use Psr\Log\LoggerInterface;
use Ratepay\RpayPayments\Components\Checkout\Event\PaymentDataExtensionBuilt;
use Ratepay\RpayPayments\Components\InstallmentCalculator\Model\InstallmentCalculatorContext;
use Ratepay\RpayPayments\Components\InstallmentCalculator\Service\InstallmentService;
use Ratepay\RpayPayments\Util\MethodHelper;
+use Shopware\Core\Checkout\Cart\Error\Error;
+use Shopware\Core\Checkout\Cart\Error\GenericCartError;
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
use Shopware\Core\Checkout\Order\OrderEntity;
+use Shopware\Core\Framework\Adapter\Translation\AbstractTranslator;
+use Shopware\Core\Framework\Struct\ArrayStruct;
+use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface;
+use Throwable;
class CheckoutSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly InstallmentService $installmentService,
- private readonly CartService $cartService
+ private readonly CartService $cartService,
+ private readonly LoggerInterface $logger,
+ private readonly AbstractTranslator $translator
) {
}
@@ -31,6 +41,7 @@ public static function getSubscribedEvents(): array
{
return [
PaymentDataExtensionBuilt::class => 'buildCheckoutExtension',
+ AccountEditOrderPageLoadedEvent::class => ['onAccountEditOrderPageLoaded', 300],
];
}
@@ -45,20 +56,62 @@ public function buildCheckoutExtension(PaymentDataExtensionBuilt $event): void
$calcContext = (new InstallmentCalculatorContext($salesChannelContext, '', null))
->setPaymentMethodId($paymentMethod->getId())
->setOrder($order);
+ $cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
if (!$order instanceof OrderEntity) {
- $calcContext->setTotalAmount($this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext)->getPrice()->getTotalPrice());
+ $calcContext->setTotalAmount($cart->getPrice()->getTotalPrice());
}
- $installmentCalculator = $this->installmentService->getInstallmentCalculatorData($calcContext);
+ try {
+ $installmentCalculator = $this->installmentService->getInstallmentCalculatorData($calcContext);
- $calcContext->setCalculationType($installmentCalculator['defaults']['type']);
- $calcContext->setCalculationValue($installmentCalculator['defaults']['value']);
+ $calcContext->setCalculationType($installmentCalculator['defaults']['type']);
+ $calcContext->setCalculationValue($installmentCalculator['defaults']['value']);
- $vars = $this->installmentService->getInstallmentPlanTwigVars($calcContext);
- $vars['calculator'] = $installmentCalculator;
+ $vars = $this->installmentService->getInstallmentPlanTwigVars($calcContext);
+ $vars['calculator'] = $installmentCalculator;
+ $extension->offsetSet('installment', $vars);
+ } catch (Throwable $exception) {
+ $this->logger->error('Ratepay installment can not be loaded. ' . $exception->getMessage(), [
+ 'order_id' => $order?->getId(),
+ 'payment_method_id' => $calcContext->getPaymentMethodId(),
+ 'total_amount' => $calcContext->getTotalAmount(),
+ 'calculation_type' => $calcContext->getCalculationType(),
+ 'calculation_value' => $calcContext->getCalculationValue(),
+ ]);
- $extension->offsetSet('installment', $vars);
+ if (!$order instanceof OrderEntity) {
+ $cart->addErrors(new GenericCartError(
+ 'RATEPAY::INSTALLMENT_CAN_NOT_BE_LOADED',
+ 'error.RATEPAY_INSTALLMENT_CAN_NOT_BE_LOADED',
+ [
+ 'method' => $paymentMethod->getTranslated()['name'] ?? $paymentMethod->getName(),
+ ],
+ Error::LEVEL_ERROR,
+ true,
+ false,
+ true
+ ));
+ }
+ }
+ }
+ }
+
+ public function onAccountEditOrderPageLoaded(AccountEditOrderPageLoadedEvent $event): void
+ {
+ $paymentMethod = $event->getSalesChannelContext()->getPaymentMethod();
+
+ if (MethodHelper::isInstallmentMethod($paymentMethod->getHandlerIdentifier())) {
+ /** @var ArrayStruct|null $ratepayData */
+ $ratepayData = $event->getPage()->getExtension('ratepay');
+ if ($ratepayData?->get('installment') === null) {
+ $session = $event->getRequest()->getSession();
+ if ($session instanceof FlashBagAwareSessionInterface) {
+ $session->getFlashbag()->add('danger', $this->translator->trans('checkout.error.RATEPAY_INSTALLMENT_CAN_NOT_BE_LOADED', [
+ '%method%' => $paymentMethod->getTranslated()['name'] ?? $paymentMethod->getName(),
+ ]));
+ }
+ }
}
}
}
diff --git a/src/Components/Logging/Subscriber/RequestBuilderFailedSubscriber.php b/src/Components/Logging/Subscriber/RequestBuilderFailedSubscriber.php
index b4253fdc..3cffdf10 100755
--- a/src/Components/Logging/Subscriber/RequestBuilderFailedSubscriber.php
+++ b/src/Components/Logging/Subscriber/RequestBuilderFailedSubscriber.php
@@ -32,7 +32,7 @@ public static function getSubscribedEvents(): array
public function onRequestBuilderFailed(RequestBuilderFailedEvent $event): void
{
// $requestData = $event->getRequestData();
- $exception = $event->getException();
+ $exception = $event->getThrowable();
$this->fileLogger->error('RequestBuilder failed', [
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
diff --git a/src/Components/PaymentHandler/InstallmentPaymentHandler.php b/src/Components/PaymentHandler/InstallmentPaymentHandler.php
index afb75fd0..64ea0393 100644
--- a/src/Components/PaymentHandler/InstallmentPaymentHandler.php
+++ b/src/Components/PaymentHandler/InstallmentPaymentHandler.php
@@ -62,7 +62,7 @@ public function getValidationDefinitions(DataBag $requestDataBag, $baseData): ar
$ratepayData = RequestHelper::getRatepayData($requestDataBag);
$installmentData = $ratepayData->get('installment');
- if ($installmentData->get('paymentType') && $installmentData->get('paymentType') === 'DIRECT-DEBIT') {
+ if ($installmentData && $installmentData->get('paymentType') && $installmentData->get('paymentType') === 'DIRECT-DEBIT') {
$validations = array_merge($validations, $this->getDebitConstraints($baseData));
}
diff --git a/src/Components/RatepayApi/Event/RequestBuilderFailedEvent.php b/src/Components/RatepayApi/Event/RequestBuilderFailedEvent.php
index 7b382ed0..6d34b736 100644
--- a/src/Components/RatepayApi/Event/RequestBuilderFailedEvent.php
+++ b/src/Components/RatepayApi/Event/RequestBuilderFailedEvent.php
@@ -11,23 +11,31 @@
namespace Ratepay\RpayPayments\Components\RatepayApi\Event;
-use Exception;
use Ratepay\RpayPayments\Components\RatepayApi\Dto\AbstractRequestData;
use Symfony\Contracts\EventDispatcher\Event;
+use Throwable;
class RequestBuilderFailedEvent extends Event
{
public function __construct(
- private readonly Exception $exception,
+ private readonly Throwable $exception,
private readonly AbstractRequestData $requestData
) {
}
- public function getException(): Exception
+ public function getThrowable(): Throwable
{
return $this->exception;
}
+ /**
+ * @deprecated use getThrowable
+ */
+ public function getException(): Throwable
+ {
+ return $this->getThrowable();
+ }
+
public function getRequestData(): AbstractRequestData
{
return $this->requestData;
diff --git a/src/Components/RatepayApi/Service/Request/AbstractRequest.php b/src/Components/RatepayApi/Service/Request/AbstractRequest.php
index 6d369bfa..bb724f00 100644
--- a/src/Components/RatepayApi/Service/Request/AbstractRequest.php
+++ b/src/Components/RatepayApi/Service/Request/AbstractRequest.php
@@ -12,7 +12,6 @@
namespace Ratepay\RpayPayments\Components\RatepayApi\Service\Request;
use InvalidArgumentException;
-use RatePAY\Exception\ExceptionAbstract;
use RatePAY\Exception\RequestException;
use RatePAY\Model\Request\SubModel\Content;
use RatePAY\Model\Request\SubModel\Head;
@@ -28,6 +27,7 @@
use Ratepay\RpayPayments\Components\RatepayApi\Factory\HeadFactory;
use Ratepay\RpayPayments\Exception\RatepayException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Throwable;
abstract class AbstractRequest
{
@@ -113,10 +113,10 @@ final public function doRequest(AbstractRequestData $requestData): RequestBuilde
$this->_initRequest($requestData);
- $head = $this->_getRequestHead($requestData);
- $content = $this->_getRequestContent($requestData);
-
try {
+ $head = $this->_getRequestHead($requestData);
+ $content = $this->_getRequestContent($requestData);
+
if ($this->isRequestBlockedByFeatureFlag($requestData)) {
throw new RequestException('Request has been blocked by feature flag.');
}
@@ -126,9 +126,9 @@ final public function doRequest(AbstractRequestData $requestData): RequestBuilde
if ($this->_subType) {
$requestBuilder = $requestBuilder->subtype($this->_subType);
}
- } catch (ExceptionAbstract $exception) {
- $this->eventDispatcher->dispatch(new RequestBuilderFailedEvent($exception, $requestData));
- throw new RatepayException($exception->getMessage(), $exception->getCode(), $exception);
+ } catch (Throwable $throwable) {
+ $this->eventDispatcher->dispatch(new RequestBuilderFailedEvent($throwable, $requestData));
+ throw new RatepayException($throwable->getMessage(), $throwable->getCode(), $throwable);
}
$this->eventDispatcher->dispatch(new RequestDoneEvent($requestData, $requestBuilder));
diff --git a/src/Resources/snippet/de_DE/messages.de-DE.json b/src/Resources/snippet/de_DE/messages.de-DE.json
index 68e8aa49..f21c14f1 100644
--- a/src/Resources/snippet/de_DE/messages.de-DE.json
+++ b/src/Resources/snippet/de_DE/messages.de-DE.json
@@ -11,6 +11,7 @@
"error.VIOLATION::METHOD_NOT_AVAILABLE": "Leider ist eine Bezahlung mit der gewählten Zahlungsart nicht möglich.",
"error.VIOLATION::RP_MISSING_BANK_ACCOUNT_HOLDER": "Bitte geben Sie den Kontoinhaber an.",
"error.VIOLATION::RP_INVALID_BANK_ACCOUNT_HOLDER": "Bitte geben Sie einen gültigen Kontoinhaber an.",
+ "checkout.error.RATEPAY_INSTALLMENT_CAN_NOT_BE_LOADED": "Leider kann der Ratenrechner der Zahlungsart %method% nicht geladen und damit die Zahlungsmethode nicht zur Verfügung gestellt werden. Bitte laden Sie die Seite neu, versuchen Sie es später erneut, oder wählen eine andere Zahlungsart.",
"ratepay": {
"storefront": {
"admin-order": {
diff --git a/src/Resources/snippet/en_GB/messages.en-GB.json b/src/Resources/snippet/en_GB/messages.en-GB.json
index b21af6e0..4628b018 100644
--- a/src/Resources/snippet/en_GB/messages.en-GB.json
+++ b/src/Resources/snippet/en_GB/messages.en-GB.json
@@ -11,6 +11,7 @@
"error.VIOLATION::METHOD_NOT_AVAILABLE": "Unfortunately, it is not possible to use the selected payment method.",
"error.VIOLATION::RP_MISSING_BANK_ACCOUNT_HOLDER": "Please provide a bank account owner.",
"error.VIOLATION::RP_INVALID_BANK_ACCOUNT_HOLDER": "Please provide a valid bank account owner.",
+ "checkout.error.RATEPAY_INSTALLMENT_CAN_NOT_BE_LOADED": "Unfortunately the instalment calculator for the payment method %method% could not be loaded and so the payment method could not be provided to you. Please reload the page, try again later, or choose another payment method.",
"ratepay": {
"storefront": {
"admin-order": {
diff --git a/src/Resources/views/storefront/page/checkout/ratepay/installment.html.twig b/src/Resources/views/storefront/page/checkout/ratepay/installment.html.twig
index 8283bc6b..7f06c5ae 100644
--- a/src/Resources/views/storefront/page/checkout/ratepay/installment.html.twig
+++ b/src/Resources/views/storefront/page/checkout/ratepay/installment.html.twig
@@ -9,5 +9,7 @@
{% block ratepay_checkout_fields %}
{{ parent() }}
- {% sw_include '@RatepayPaments/storefront/page/checkout/ratepay/common/installment-calculator.html.twig' %}
+ {% if page.extensions.ratepay.installment.calculator %}
+ {% sw_include '@RatepayPaments/storefront/page/checkout/ratepay/common/installment-calculator.html.twig' %}
+ {% endif %}
{% endblock %}