Skip to content

Commit

Permalink
RATESWSX-306: installment: prevent fatal error message in checkout on…
Browse files Browse the repository at this point in the history
… unreachable gateway (#65)
  • Loading branch information
rommelfreddy authored Sep 13, 2024
1 parent 6733ab0 commit 86086bd
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
<tag name="kernel.event_subscriber"/>
</defaults>

<service id="Ratepay\RpayPayments\Components\InstallmentCalculator\Subscriber\BuildPaymentSubscriber"/>
<service id="Ratepay\RpayPayments\Components\InstallmentCalculator\Subscriber\CheckoutSubscriber"/>
<service id="Ratepay\RpayPayments\Components\InstallmentCalculator\Subscriber\BuildPaymentSubscriber">
<argument key="$translator" type="service" id="Shopware\Core\Framework\Adapter\Translation\Translator"/>
</service>
<service id="Ratepay\RpayPayments\Components\InstallmentCalculator\Subscriber\CheckoutSubscriber">
<argument key="$translator" type="service" id="Shopware\Core\Framework\Adapter\Translation\Translator"/>
</service>
<service id="Ratepay\RpayPayments\Components\InstallmentCalculator\Subscriber\ProductPageSubscriber">
<argument key="$logger" id="monolog.logger.ratepay" type="service"/>
<argument key="$countryRepository" id="country.repository" type="service"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
}

Expand All @@ -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();

Expand All @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,37 @@

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
) {
}

public static function getSubscribedEvents(): array
{
return [
PaymentDataExtensionBuilt::class => 'buildCheckoutExtension',
AccountEditOrderPageLoadedEvent::class => ['onAccountEditOrderPageLoaded', 300],
];
}

Expand All @@ -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(),
]));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
14 changes: 11 additions & 3 deletions src/Components/RatepayApi/Event/RequestBuilderFailedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 7 additions & 7 deletions src/Components/RatepayApi/Service/Request/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -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.');
}
Expand All @@ -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));
Expand Down
1 change: 1 addition & 0 deletions src/Resources/snippet/de_DE/messages.de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions src/Resources/snippet/en_GB/messages.en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}

0 comments on commit 86086bd

Please sign in to comment.