From 999f4f2ffa7f516eb26262e274508fac290a6ed2 Mon Sep 17 00:00:00 2001 From: theluckystrike <51033404+theluckystrike@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:16:42 +0000 Subject: [PATCH] Show the requested payment on the order revise page reviseAction loaded the payment from the paymentId query parameter into $payment and then reused $payment as the loop variable over findForPayable(), which is ordered by id DESC. After the loop $payment always held the order's oldest payment, so a customer returning from a cancelled or failed payment saw the note or state message of an earlier attempt instead of the one that just happened. Pick the requested payment while iterating over the order's own payments. This also stops paymentId from loading a payment of a different order. An unknown order token passed null to findForPayable() and ended in a TypeError (HTTP 500). Return a 404 instead. Add a Behat scenario that creates a failed and then a cancelled payment and checks the revise page shows the cancelled message, plus a scenario for the unknown token. --- .../payment/revise_order_payment.feature | 37 ++++++++++ .../Context/Ui/Domain/PaymentController.php | 69 +++++++++++++++++++ .../Resources/config/services/contexts/ui.yml | 1 + .../Controller/OrderController.php | 18 ++--- 4 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 features/ui/frontend/payment/revise_order_payment.feature diff --git a/features/ui/frontend/payment/revise_order_payment.feature b/features/ui/frontend/payment/revise_order_payment.feature new file mode 100644 index 0000000000..52a44e40a2 --- /dev/null +++ b/features/ui/frontend/payment/revise_order_payment.feature @@ -0,0 +1,37 @@ +@ui @ui_domain +Feature: Revise the payment of an order + In order to retry a payment that did not go through + As a customer + I want the revise page to tell me what happened to my last payment + + Background: + Given the site operates on a store in "Austria" + And the store "Austria" is the default store + And the site operates on locale "en" + And the site has a tax rate "AT" with "20%" rate + And the site has a tax rule group "AT" + And the tax rule group has a tax rule for country "Austria" with tax rate "AT" + And the site has a product "T-Shirt" priced at 2000 + And the product is active and published and available for store "Austria" + And the product has the tax rule group "AT" + And the site has a customer "some-customer@something.com" + And the customer "some-customer@something.com" has an address with country "Austria", "4600", "Wels", "Freiung", "9-11/N3" + And the cart belongs to customer "some-customer@something.com" + And I add the product "T-Shirt" to my cart + And the cart ships to customer "some-customer@something.com" address with postcode "4600" + And the cart invoices to customer "some-customer@something.com" address with postcode "4600" + And I create an order from my cart + And the site has a payment provider "Bankwire" using factory "offline" + + Scenario: The revise page shows the state of the payment that was just attempted + Given I create a payment for my order with payment provider "Bankwire" + And I apply payment transition "fail" to latest order payment + And I create a payment for my order with payment provider "Bankwire" + And I apply payment transition "cancel" to latest order payment + When I open the revise page of my order for the latest order payment + Then the revise page should show the message "coreshop.ui.order.revise.transaction_cancelled" + And the revise page should not show the message "coreshop.ui.order.revise.transaction_failed" + + Scenario: The revise page is not found for an unknown order token + When I open the revise page for the order token "this-token-does-not-exist" + Then the revise page should not be found diff --git a/src/CoreShop/Behat/Context/Ui/Domain/PaymentController.php b/src/CoreShop/Behat/Context/Ui/Domain/PaymentController.php index 3db974fce9..e57f3887a1 100644 --- a/src/CoreShop/Behat/Context/Ui/Domain/PaymentController.php +++ b/src/CoreShop/Behat/Context/Ui/Domain/PaymentController.php @@ -19,6 +19,7 @@ use Behat\Behat\Context\Context; use CoreShop\Bundle\WorkflowBundle\StateManager\WorkflowStateInfoManagerInterface; +use CoreShop\Component\Core\Model\OrderInterface; use CoreShop\Component\Core\Model\PaymentInterface; use CoreShop\Component\Core\Model\PaymentProvider; use GuzzleHttp\Client; @@ -27,16 +28,22 @@ use GuzzleHttp\Psr7\Response; use Payum\Core\Payum; use Pimcore\Model\DataObject\Concrete; +use Psr\Http\Message\ResponseInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouterInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use Webmozart\Assert\Assert; final class PaymentController implements Context { + private ?ResponseInterface $revisePageResponse = null; + public function __construct( private Payum $payum, private RouterInterface $router, private WorkflowStateInfoManagerInterface $workflowStateInfoManager, + private TranslatorInterface $translator, ) { } @@ -103,4 +110,66 @@ public function iOpenCartSummaryPage(PaymentInterface $payment): void ), ); } + + /** + * @When /^I open the revise page of (my order) for the (latest order payment)$/ + */ + public function iOpenTheRevisePageOfMyOrderForThePayment(OrderInterface $order, PaymentInterface $payment): void + { + $this->openRevisePage((string) $order->getToken(), ['paymentId' => $payment->getId()]); + } + + /** + * @When /^I open the revise page for the order token "([^"]+)"$/ + */ + public function iOpenTheRevisePageForTheOrderToken(string $token): void + { + $this->openRevisePage($token); + } + + /** + * @Then /^the revise page should show the message "([^"]+)"$/ + */ + public function theRevisePageShouldShowTheMessage(string $message): void + { + Assert::contains($this->getRevisePageContent(), htmlspecialchars($this->translator->trans($message, [], null, 'en'), \ENT_QUOTES)); + } + + /** + * @Then /^the revise page should not show the message "([^"]+)"$/ + */ + public function theRevisePageShouldNotShowTheMessage(string $message): void + { + Assert::notContains($this->getRevisePageContent(), htmlspecialchars($this->translator->trans($message, [], null, 'en'), \ENT_QUOTES)); + } + + /** + * @Then the revise page should not be found + */ + public function theRevisePageShouldNotBeFound(): void + { + Assert::notNull($this->revisePageResponse, 'The revise page has not been opened.'); + Assert::eq($this->revisePageResponse->getStatusCode(), 404); + } + + private function openRevisePage(string $token, array $query = []): void + { + $this->router->setContext(RequestContext::fromUri(getenv('PANTHER_EXTERNAL_BASE_URI'))); + + $url = $this->router->generate( + 'coreshop_order_revise', + array_merge(['_locale' => 'en', 'token' => $token], $query), + UrlGeneratorInterface::ABSOLUTE_URL, + ); + + $this->revisePageResponse = (new Client())->request('GET', $url, ['http_errors' => false]); + } + + private function getRevisePageContent(): string + { + Assert::notNull($this->revisePageResponse, 'The revise page has not been opened.'); + Assert::eq($this->revisePageResponse->getStatusCode(), 200); + + return (string) $this->revisePageResponse->getBody(); + } } diff --git a/src/CoreShop/Behat/Resources/config/services/contexts/ui.yml b/src/CoreShop/Behat/Resources/config/services/contexts/ui.yml index e3ff3bc75e..bb052bc0be 100644 --- a/src/CoreShop/Behat/Resources/config/services/contexts/ui.yml +++ b/src/CoreShop/Behat/Resources/config/services/contexts/ui.yml @@ -110,6 +110,7 @@ services: - '@payum' - '@router' - '@CoreShop\Bundle\WorkflowBundle\StateManager\WorkflowStateInfoManagerInterface' + - '@translator' tags: - { name: fob.context_service } diff --git a/src/CoreShop/Bundle/FrontendBundle/Controller/OrderController.php b/src/CoreShop/Bundle/FrontendBundle/Controller/OrderController.php index b533159cad..b56eb76409 100644 --- a/src/CoreShop/Bundle/FrontendBundle/Controller/OrderController.php +++ b/src/CoreShop/Bundle/FrontendBundle/Controller/OrderController.php @@ -34,22 +34,24 @@ public function reviseAction(Request $request): Response $token = $this->getParameterFromRequest($request, 'token'); $payment = null; - /** @var OrderInterface $order */ $order = $this->getOrderRepository()->findOneBy(['token' => $token]); - if ($request->query->has('paymentId')) { - $paymentObject = $this->getPaymentRepository()->find($request->query->get('paymentId')); - if ($paymentObject instanceof PaymentInterface) { - $payment = $paymentObject; - } + if (!$order instanceof OrderInterface) { + throw $this->createNotFoundException(); } - foreach ($this->getPaymentRepository()->findForPayable($order) as $payment) { - if ($payment->getState() === PaymentInterface::STATE_COMPLETED) { + $paymentId = $request->query->get('paymentId'); + + foreach ($this->getPaymentRepository()->findForPayable($order) as $orderPayment) { + if ($orderPayment->getState() === PaymentInterface::STATE_COMPLETED) { $this->addFlash('error', $this->container->get('translator')->trans('coreshop.ui.error.order_already_paid')); return $this->redirectToRoute('coreshop_index'); } + + if (null !== $paymentId && (string) $orderPayment->getId() === (string) $paymentId) { + $payment = $orderPayment; + } } $form = $this->getFormFactory()->createNamed('coreshop', PaymentType::class, $order, [