From b37f9f84c7d3f05dad9e27f906ee1c1eaac339cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernest=20Wi=C5=9Bniewski?= Date: Mon, 15 Sep 2025 08:53:10 +0200 Subject: [PATCH 1/2] Fix retry failed payment creates not needed canceled payment entry for the order --- src/Controller/RetryPaymentAction.php | 9 ++ .../Shop/RetryPayment/RetryingPaymentTest.php | 2 +- .../Controller/RetryPaymentActionTest.php | 126 ++++++++++++++++++ translations/flashes.en.yaml | 1 + translations/flashes.pl.yaml | 1 + 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Controller/RetryPaymentActionTest.php diff --git a/src/Controller/RetryPaymentAction.php b/src/Controller/RetryPaymentAction.php index 323939dd..4fc5a2ea 100644 --- a/src/Controller/RetryPaymentAction.php +++ b/src/Controller/RetryPaymentAction.php @@ -7,6 +7,7 @@ use CommerceWeavers\SyliusTpayPlugin\Command\CancelLastPayment; use CommerceWeavers\SyliusTpayPlugin\Payment\Exception\PaymentCannotBeCancelledException; use Sylius\Component\Core\Model\OrderInterface; +use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -49,6 +50,14 @@ public function __invoke(Request $request, string $orderToken): Response $order = $this->findOrderOr404($orderToken); Assert::notNull($order->getTokenValue()); + if (null !== $order->getLastPayment(PaymentInterface::STATE_NEW)) { + $this->addFlashMessage(self::INFO_FLASH_TYPE, 'commerce_weavers_sylius_tpay.shop.retry_payment.ready_to_retry'); + + return new RedirectResponse( + $this->router->generate('sylius_shop_order_show', ['tokenValue' => $orderToken]), + ); + } + try { $this->messageBus->dispatch(new CancelLastPayment($order->getTokenValue())); } catch (HandlerFailedException $exception) { diff --git a/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php b/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php index dee39494..bf14e564 100644 --- a/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php +++ b/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php @@ -30,7 +30,7 @@ public function test_it_retries_payment(): void $this->retryPayment(); $this->assertPageTitleContains('Summary of your order'); - $this->assertSelectorWillContain('.alert', 'The previous payment has been cancelled'); + $this->assertSelectorWillContain('.alert', 'You can retry the payment now'); } public function test_it_prevents_retrying_not_qualifying_payments(): void diff --git a/tests/Unit/Controller/RetryPaymentActionTest.php b/tests/Unit/Controller/RetryPaymentActionTest.php new file mode 100644 index 00000000..9961df57 --- /dev/null +++ b/tests/Unit/Controller/RetryPaymentActionTest.php @@ -0,0 +1,126 @@ +csrf = $this->prophesize(CsrfTokenManagerInterface::class); + $this->bus = $this->prophesize(MessageBusInterface::class); + $this->orders = $this->prophesize(OrderRepositoryInterface::class); + $this->router = $this->prophesize(RouterInterface::class); + $this->requestStack = new RequestStack(); + + $this->csrf->isTokenValid(Argument::any())->willReturn(true); + } + + private function createRequest(): Request + { + $request = new Request([], ['_csrf_token' => 'token']); + $session = new Session(new MockArraySessionStorage()); + $session->start(); + $request->setSession($session); + $this->requestStack->push($request); + + return $request; + } + + private function createAction(): RetryPaymentAction + { + return new RetryPaymentAction( + $this->csrf->reveal(), + $this->bus->reveal(), + $this->orders->reveal(), + $this->router->reveal(), + $this->requestStack + ); + } + + public function test_it_reuses_existing_new_payment_and_skips_cancellation(): void + { + $order = $this->prophesize(OrderInterface::class); + $order->getTokenValue()->willReturn('ORD'); + $order->getLastPayment(PaymentInterface::STATE_NEW)->willReturn($this->prophesize(PaymentInterface::class)->reveal()); + + $this->orders->findOneByTokenValue('ORD')->willReturn($order->reveal()); + $this->router->generate('sylius_shop_order_show', ['tokenValue' => 'ORD'])->willReturn('/order'); + $this->bus->dispatch(Argument::any())->shouldNotBeCalled(); + + $response = $this->createAction()($this->createRequest(), 'ORD'); + + self::assertSame(302, $response->getStatusCode()); + self::assertSame('/order', $response->headers->get('Location')); + } + + public function test_it_cancels_when_no_new_payment_exists(): void + { + $order = $this->prophesize(OrderInterface::class); + $order->getTokenValue()->willReturn('ORD'); + $order->getLastPayment(PaymentInterface::STATE_NEW)->willReturn(null); + + $this->orders->findOneByTokenValue('ORD')->willReturn($order->reveal()); + $this->router->generate('sylius_shop_order_show', ['tokenValue' => 'ORD'])->willReturn('/order'); + + $this->bus->dispatch(Argument::that( + fn($msg) => $msg instanceof CancelLastPayment && $msg->orderToken === 'ORD' + ))->willReturn(new Envelope(new \stdClass()))->shouldBeCalled(); + + $response = $this->createAction()($this->createRequest(), 'ORD'); + + self::assertSame(302, $response->getStatusCode()); + self::assertSame('/order', $response->headers->get('Location')); + } + + public function test_it_handles_cannot_be_cancelled(): void + { + $order = $this->prophesize(OrderInterface::class); + $order->getTokenValue()->willReturn('ORD'); + $order->getLastPayment(PaymentInterface::STATE_NEW)->willReturn(null); + + $this->orders->findOneByTokenValue('ORD')->willReturn($order->reveal()); + $this->router->generate('sylius_shop_homepage')->willReturn('/'); + + $this->bus->dispatch(Argument::any())->willThrow( + new HandlerFailedException( + new Envelope(new \stdClass()), + [new PaymentCannotBeCancelledException($this->prophesize(PaymentInterface::class)->reveal())] + ) + ); + + $response = $this->createAction()($this->createRequest(), 'ORD'); + + self::assertSame(302, $response->getStatusCode()); + self::assertSame('/', $response->headers->get('Location')); + } +} diff --git a/translations/flashes.en.yaml b/translations/flashes.en.yaml index 03258d09..43337dbc 100644 --- a/translations/flashes.en.yaml +++ b/translations/flashes.en.yaml @@ -6,3 +6,4 @@ commerce_weavers_sylius_tpay: retry_payment: cannot_be_retried: 'This payment cannot be retried' previous_payment_cancelled: 'The previous payment has been cancelled' + ready_to_retry: 'You can retry the payment now' diff --git a/translations/flashes.pl.yaml b/translations/flashes.pl.yaml index e145f5dc..43ef21d8 100644 --- a/translations/flashes.pl.yaml +++ b/translations/flashes.pl.yaml @@ -6,3 +6,4 @@ commerce_weavers_sylius_tpay: retry_payment: cannot_be_retried: 'Wybrana płatność nie może być ponowiona' previous_payment_cancelled: 'Poprzednia płatność została anulowana' + ready_to_retry: 'Możesz teraz ponowić płatność' From 15ce32accb18e6755d53ab06fd5392c5c466ed09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernest=20Wi=C5=9Bniewski?= Date: Wed, 17 Sep 2025 10:34:56 +0200 Subject: [PATCH 2/2] Remove retry payment action, switch to redirect-only flow --- config/routes/routes_shop.php | 6 - config/services/controller.php | 12 -- src/Controller/RetryPaymentAction.php | 101 -------------- src/Routing.php | 4 - .../cart/complete/payment_failed.html.twig | 9 +- tests/E2E/Helper/Order/RetryPaymentTrait.php | 2 +- .../Shop/RetryPayment/RetryingPaymentTest.php | 3 +- .../Controller/RetryPaymentActionTest.php | 126 ------------------ translations/flashes.en.yaml | 5 - translations/flashes.pl.yaml | 5 - 10 files changed, 5 insertions(+), 268 deletions(-) delete mode 100644 src/Controller/RetryPaymentAction.php delete mode 100644 tests/Unit/Controller/RetryPaymentActionTest.php diff --git a/config/routes/routes_shop.php b/config/routes/routes_shop.php index 2afd7c71..c1e8ad37 100644 --- a/config/routes/routes_shop.php +++ b/config/routes/routes_shop.php @@ -7,7 +7,6 @@ use CommerceWeavers\SyliusTpayPlugin\Controller\DisplayPaymentFailedPageAction; use CommerceWeavers\SyliusTpayPlugin\Controller\DisplayThankYouPageAction; use CommerceWeavers\SyliusTpayPlugin\Controller\DisplayWaitingForPaymentPage; -use CommerceWeavers\SyliusTpayPlugin\Controller\RetryPaymentAction; use CommerceWeavers\SyliusTpayPlugin\Routing; use Symfony\Component\HttpFoundation\Request; @@ -17,11 +16,6 @@ ->methods([Request::METHOD_GET]) ; - $routes->add(Routing::SHOP_RETRY_PAYMENT, Routing::SHOP_RETRY_PAYMENT_PATH) - ->controller(RetryPaymentAction::class) - ->methods([Request::METHOD_POST]) - ; - $routes->add(Routing::SHOP_THANK_YOU, Routing::SHOP_THANK_YOU_PATH) ->controller(DisplayThankYouPageAction::class) ->methods([Request::METHOD_GET]) diff --git a/config/services/controller.php b/config/services/controller.php index 24ec7803..84a1f20c 100644 --- a/config/services/controller.php +++ b/config/services/controller.php @@ -9,7 +9,6 @@ use CommerceWeavers\SyliusTpayPlugin\Controller\DisplayThankYouPageAction; use CommerceWeavers\SyliusTpayPlugin\Controller\DisplayWaitingForPaymentPage; use CommerceWeavers\SyliusTpayPlugin\Controller\PaymentNotificationAction; -use CommerceWeavers\SyliusTpayPlugin\Controller\RetryPaymentAction; use CommerceWeavers\SyliusTpayPlugin\Controller\TpayGetChannelsAction; use CommerceWeavers\SyliusTpayPlugin\Controller\TpayNotificationAction; @@ -52,17 +51,6 @@ ->tag('controller.service_arguments') ; - $services->set(RetryPaymentAction::class) - ->args([ - service('security.csrf.token_manager'), - service('sylius.command_bus'), - service('sylius.repository.order'), - service('router'), - service('request_stack'), - ]) - ->tag('controller.service_arguments') - ; - $services->set(TpayNotificationAction::class) ->args([ service('commerce_weavers_sylius_tpay.tpay.security.notification.verifier.signature'), diff --git a/src/Controller/RetryPaymentAction.php b/src/Controller/RetryPaymentAction.php deleted file mode 100644 index 4fc5a2ea..00000000 --- a/src/Controller/RetryPaymentAction.php +++ /dev/null @@ -1,101 +0,0 @@ -request->get('_csrf_token'); - - if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($orderToken, $csrfToken))) { - throw new BadRequestException('Invalid CSRF token'); - } - - $order = $this->findOrderOr404($orderToken); - Assert::notNull($order->getTokenValue()); - - if (null !== $order->getLastPayment(PaymentInterface::STATE_NEW)) { - $this->addFlashMessage(self::INFO_FLASH_TYPE, 'commerce_weavers_sylius_tpay.shop.retry_payment.ready_to_retry'); - - return new RedirectResponse( - $this->router->generate('sylius_shop_order_show', ['tokenValue' => $orderToken]), - ); - } - - try { - $this->messageBus->dispatch(new CancelLastPayment($order->getTokenValue())); - } catch (HandlerFailedException $exception) { - if ($exception->getPrevious() instanceof PaymentCannotBeCancelledException) { - $this->addFlashMessage(self::ERROR_FLASH_TYPE, 'commerce_weavers_sylius_tpay.shop.retry_payment.cannot_be_retried'); - - return new RedirectResponse( - $this->router->generate('sylius_shop_homepage'), - ); - } - - throw $exception; - } - - $this->addFlashMessage(self::INFO_FLASH_TYPE, 'commerce_weavers_sylius_tpay.shop.retry_payment.previous_payment_cancelled'); - - return new RedirectResponse( - $this->router->generate('sylius_shop_order_show', ['tokenValue' => $orderToken]), - ); - } - - private function findOrderOr404(string $orderToken): OrderInterface - { - /** @var OrderInterface|null $order */ - $order = $this->orderRepository->findOneByTokenValue($orderToken); - - if (null === $order) { - throw new NotFoundHttpException(sprintf('Order with token "%s" does not exist.', $orderToken)); - } - - return $order; - } - - private function addFlashMessage(string $type, string $message): void - { - /** @var Session $session */ - $session = $this->requestStack->getSession(); - - $session->getFlashBag()->add($type, $message); - } -} diff --git a/src/Routing.php b/src/Routing.php index 1ce46130..49f8d234 100644 --- a/src/Routing.php +++ b/src/Routing.php @@ -30,10 +30,6 @@ final class Routing public const SHOP_PAYMENT_FAILED_PATH = '/tpay/order/{orderToken}/payment-failed'; - public const SHOP_RETRY_PAYMENT = 'commerce_weavers_sylius_tpay_retry_payment'; - - public const SHOP_RETRY_PAYMENT_PATH = '/tpay/order/{orderToken}/retry-payment'; - public const SHOP_THANK_YOU = 'commerce_weavers_sylius_tpay_thank_you'; public const SHOP_THANK_YOU_PATH = '/tpay/order/{orderToken}/thank-you'; diff --git a/templates/shop/cart/complete/payment_failed.html.twig b/templates/shop/cart/complete/payment_failed.html.twig index c0cd9c8f..310a1f70 100644 --- a/templates/shop/cart/complete/payment_failed.html.twig +++ b/templates/shop/cart/complete/payment_failed.html.twig @@ -21,12 +21,9 @@
-
- - -
+ + {{ 'commerce_weavers_sylius_tpay.shop.payment_failed.retry_payment'|trans }} +
diff --git a/tests/E2E/Helper/Order/RetryPaymentTrait.php b/tests/E2E/Helper/Order/RetryPaymentTrait.php index e7cc2cfa..e97a064a 100644 --- a/tests/E2E/Helper/Order/RetryPaymentTrait.php +++ b/tests/E2E/Helper/Order/RetryPaymentTrait.php @@ -18,6 +18,6 @@ public function showPaymentFailedPage(string $orderToken): void public function retryPayment(): void { - $this->client->submitForm('Retry payment'); + $this->client->clickLink('Retry payment'); } } diff --git a/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php b/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php index bf14e564..aa3c4f9c 100644 --- a/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php +++ b/tests/E2E/Shop/RetryPayment/RetryingPaymentTest.php @@ -30,7 +30,6 @@ public function test_it_retries_payment(): void $this->retryPayment(); $this->assertPageTitleContains('Summary of your order'); - $this->assertSelectorWillContain('.alert', 'You can retry the payment now'); } public function test_it_prevents_retrying_not_qualifying_payments(): void @@ -41,6 +40,6 @@ public function test_it_prevents_retrying_not_qualifying_payments(): void $this->showPaymentFailedPage('t0k3n'); $this->retryPayment(); - $this->assertSelectorWillContain('.alert', 'This payment cannot be retried'); + $this->assertPageTitleContains('Summary of your order'); } } diff --git a/tests/Unit/Controller/RetryPaymentActionTest.php b/tests/Unit/Controller/RetryPaymentActionTest.php deleted file mode 100644 index 9961df57..00000000 --- a/tests/Unit/Controller/RetryPaymentActionTest.php +++ /dev/null @@ -1,126 +0,0 @@ -csrf = $this->prophesize(CsrfTokenManagerInterface::class); - $this->bus = $this->prophesize(MessageBusInterface::class); - $this->orders = $this->prophesize(OrderRepositoryInterface::class); - $this->router = $this->prophesize(RouterInterface::class); - $this->requestStack = new RequestStack(); - - $this->csrf->isTokenValid(Argument::any())->willReturn(true); - } - - private function createRequest(): Request - { - $request = new Request([], ['_csrf_token' => 'token']); - $session = new Session(new MockArraySessionStorage()); - $session->start(); - $request->setSession($session); - $this->requestStack->push($request); - - return $request; - } - - private function createAction(): RetryPaymentAction - { - return new RetryPaymentAction( - $this->csrf->reveal(), - $this->bus->reveal(), - $this->orders->reveal(), - $this->router->reveal(), - $this->requestStack - ); - } - - public function test_it_reuses_existing_new_payment_and_skips_cancellation(): void - { - $order = $this->prophesize(OrderInterface::class); - $order->getTokenValue()->willReturn('ORD'); - $order->getLastPayment(PaymentInterface::STATE_NEW)->willReturn($this->prophesize(PaymentInterface::class)->reveal()); - - $this->orders->findOneByTokenValue('ORD')->willReturn($order->reveal()); - $this->router->generate('sylius_shop_order_show', ['tokenValue' => 'ORD'])->willReturn('/order'); - $this->bus->dispatch(Argument::any())->shouldNotBeCalled(); - - $response = $this->createAction()($this->createRequest(), 'ORD'); - - self::assertSame(302, $response->getStatusCode()); - self::assertSame('/order', $response->headers->get('Location')); - } - - public function test_it_cancels_when_no_new_payment_exists(): void - { - $order = $this->prophesize(OrderInterface::class); - $order->getTokenValue()->willReturn('ORD'); - $order->getLastPayment(PaymentInterface::STATE_NEW)->willReturn(null); - - $this->orders->findOneByTokenValue('ORD')->willReturn($order->reveal()); - $this->router->generate('sylius_shop_order_show', ['tokenValue' => 'ORD'])->willReturn('/order'); - - $this->bus->dispatch(Argument::that( - fn($msg) => $msg instanceof CancelLastPayment && $msg->orderToken === 'ORD' - ))->willReturn(new Envelope(new \stdClass()))->shouldBeCalled(); - - $response = $this->createAction()($this->createRequest(), 'ORD'); - - self::assertSame(302, $response->getStatusCode()); - self::assertSame('/order', $response->headers->get('Location')); - } - - public function test_it_handles_cannot_be_cancelled(): void - { - $order = $this->prophesize(OrderInterface::class); - $order->getTokenValue()->willReturn('ORD'); - $order->getLastPayment(PaymentInterface::STATE_NEW)->willReturn(null); - - $this->orders->findOneByTokenValue('ORD')->willReturn($order->reveal()); - $this->router->generate('sylius_shop_homepage')->willReturn('/'); - - $this->bus->dispatch(Argument::any())->willThrow( - new HandlerFailedException( - new Envelope(new \stdClass()), - [new PaymentCannotBeCancelledException($this->prophesize(PaymentInterface::class)->reveal())] - ) - ); - - $response = $this->createAction()($this->createRequest(), 'ORD'); - - self::assertSame(302, $response->getStatusCode()); - self::assertSame('/', $response->headers->get('Location')); - } -} diff --git a/translations/flashes.en.yaml b/translations/flashes.en.yaml index 43337dbc..8b7f60ff 100644 --- a/translations/flashes.en.yaml +++ b/translations/flashes.en.yaml @@ -2,8 +2,3 @@ commerce_weavers_sylius_tpay: admin: payment_method: image_has_been_removed: 'Payment method image has been removed' - shop: - retry_payment: - cannot_be_retried: 'This payment cannot be retried' - previous_payment_cancelled: 'The previous payment has been cancelled' - ready_to_retry: 'You can retry the payment now' diff --git a/translations/flashes.pl.yaml b/translations/flashes.pl.yaml index 43ef21d8..46bcc47e 100644 --- a/translations/flashes.pl.yaml +++ b/translations/flashes.pl.yaml @@ -2,8 +2,3 @@ commerce_weavers_sylius_tpay: admin: payment_method: image_has_been_removed: 'Logo metody płatności zostało usunięte' - shop: - retry_payment: - cannot_be_retried: 'Wybrana płatność nie może być ponowiona' - previous_payment_cancelled: 'Poprzednia płatność została anulowana' - ready_to_retry: 'Możesz teraz ponowić płatność'