Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RATESWSX-306: installment: prevent fatal error message in checkout on unreachable gateway #65

Merged
merged 2 commits into from
Sep 13, 2024
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
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 %}
Loading