From 9ddc223d35115c154012c7c22ba44d50dd12b422 Mon Sep 17 00:00:00 2001 From: Justas Date: Fri, 24 Jul 2026 10:44:33 +0300 Subject: [PATCH 1/2] fix infinite spinner on fail --- controllers/front/fail.php | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/controllers/front/fail.php b/controllers/front/fail.php index 6161fbba..013d158f 100644 --- a/controllers/front/fail.php +++ b/controllers/front/fail.php @@ -21,8 +21,10 @@ *@license SIX Payment Services */ +use Invertus\SaferPay\Config\SaferPayConfig; use Invertus\SaferPay\Controller\AbstractSaferPayController; use Invertus\SaferPay\Factory\OrderPresenterFactory; +use Invertus\SaferPay\Repository\SaferPayOrderRepository; use Invertus\SaferPay\Service\CartDuplicationService; use Invertus\SaferPay\Logger\LoggerInterface; @@ -84,6 +86,8 @@ public function initContent() $logger->debug(sprintf('%s - Controller called', self::FILE_NAME)); + $this->markOrderAsFailed($logger); + $this->warning[] = $this->module->l('We couldn\'t authorize your payment. Please try again.', self::FILE_NAME); $logger->debug(sprintf('%s - Controller action ended', self::FILE_NAME)); @@ -102,4 +106,76 @@ public function initContent() ) ); } + + /** + * When the "Order creation rule" is "Before authorization", the order row already exists by the time + * the customer lands here after a rejected/failed authorization. Transition it to the failed state and + * flag the Saferpay order as canceled so it does not stay stuck on "Awaiting Saferpay payment" forever + * (and so the awaiting-status poller stops spinning). Mirrors the failure handling in notify.php. + * + * @param LoggerInterface $logger + * + * @return void + */ + private function markOrderAsFailed($logger) + { + /** @var SaferPayOrderRepository $orderRepo */ + $orderRepo = $this->module->getService(SaferPayOrderRepository::class); + + $saferPayOrderId = (int) $orderRepo->getIdByCartId($this->id_cart); + + if (!$saferPayOrderId) { + // "After authorization" mode: no order was created for a failed payment, nothing to update. + return; + } + + $saferPayOrder = new SaferPayOrder($saferPayOrderId); + + if (!Validate::isLoadedObject($saferPayOrder)) { + return; + } + + // Payment already succeeded through another path (e.g. the notify webhook); never override it. + if ($saferPayOrder->authorized || $saferPayOrder->captured) { + return; + } + + $orderId = (int) Order::getIdByCartId($this->id_cart); + $failedStatus = (int) _SAFERPAY_PAYMENT_AUTHORIZATION_FAILED_; + + if ($orderId && $failedStatus) { + $order = new Order($orderId); + $currentState = (int) $order->current_state; + + $authorizedStatus = (int) Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_AUTHORIZED); + $capturedStatus = (int) Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_COMPLETED); + + // Do not override a success state, and avoid duplicate history entries if already failed. + if ($currentState !== $authorizedStatus + && $currentState !== $capturedStatus + && $currentState !== $failedStatus + ) { + $order->setCurrentState($failedStatus); + + $logger->debug(sprintf('%s - Order transitioned to authorization failed', self::FILE_NAME), [ + 'context' => [ + 'id_order' => $orderId, + 'id_cart' => $this->id_cart, + ], + ]); + } + } + + if (!$saferPayOrder->canceled) { + $saferPayOrder->authorized = false; + $saferPayOrder->pending = false; + $saferPayOrder->canceled = true; + + if ($orderId) { + $saferPayOrder->id_order = $orderId; + } + + $saferPayOrder->update(); + } + } } From 159fabd5641fe23714c0caeb709a9c7fb5d5c52a Mon Sep 17 00:00:00 2001 From: Justas Date: Fri, 24 Jul 2026 11:56:52 +0300 Subject: [PATCH 2/2] guard order load before state transition in fail controller Wrap the order state transition in Validate::isLoadedObject() so a missing/unloaded order row can no longer trigger setCurrentState() on an invalid object during the fail redirect flow, matching the guard already used in failValidation.php. --- controllers/front/fail.php | 39 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/controllers/front/fail.php b/controllers/front/fail.php index 013d158f..ab47fa36 100644 --- a/controllers/front/fail.php +++ b/controllers/front/fail.php @@ -145,24 +145,27 @@ private function markOrderAsFailed($logger) if ($orderId && $failedStatus) { $order = new Order($orderId); - $currentState = (int) $order->current_state; - - $authorizedStatus = (int) Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_AUTHORIZED); - $capturedStatus = (int) Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_COMPLETED); - - // Do not override a success state, and avoid duplicate history entries if already failed. - if ($currentState !== $authorizedStatus - && $currentState !== $capturedStatus - && $currentState !== $failedStatus - ) { - $order->setCurrentState($failedStatus); - - $logger->debug(sprintf('%s - Order transitioned to authorization failed', self::FILE_NAME), [ - 'context' => [ - 'id_order' => $orderId, - 'id_cart' => $this->id_cart, - ], - ]); + + if (Validate::isLoadedObject($order)) { + $currentState = (int) $order->current_state; + + $authorizedStatus = (int) Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_AUTHORIZED); + $capturedStatus = (int) Configuration::get(SaferPayConfig::SAFERPAY_PAYMENT_COMPLETED); + + // Do not override a success state, and avoid duplicate history entries if already failed. + if ($currentState !== $authorizedStatus + && $currentState !== $capturedStatus + && $currentState !== $failedStatus + ) { + $order->setCurrentState($failedStatus); + + $logger->debug(sprintf('%s - Order transitioned to authorization failed', self::FILE_NAME), [ + 'context' => [ + 'id_order' => $orderId, + 'id_cart' => $this->id_cart, + ], + ]); + } } }