module->order_separator, $transaction_id); $cartId = (int)$cartIdParts[0]; // Validate extracted cart ID. It must be a numeric value. if (!is_numeric($cartId)) { $this->errors[] = Tools::displayError($this->trans('Invalid cart ID received.', [], 'Modules.Hutko.Shop')); $this->redirectWithNotifications($this->context->link->getPageLink('order', true, $this->context->language->id)); return; // Stop execution after redirection } // Load the cart object. $cart = new Cart($cartId); // Verify that the cart belongs to the current customer to prevent unauthorized access. if (!Validate::isLoadedObject($cart) || $cart->id_customer != $this->context->customer->id) { $this->errors[] = Tools::displayError($this->trans('Access denied to this order.', [], 'Modules.Hutko.Shop')); Tools::redirect('/'); // Redirect to home or a more appropriate error page } // Handle different payment statuses. switch ($orderStatus) { case 'declined': $this->errors[] = Tools::displayError($this->trans('Your payment was declined. Please try again or use a different payment method.', [], 'Modules.Hutko.Shop')); $this->redirectWithNotifications($this->context->link->getPageLink('order', true, $this->context->language->id)); break; case 'expired': $this->errors[] = Tools::displayError($this->trans('Your payment has expired. Please try again.', [], 'Modules.Hutko.Shop')); $this->redirectWithNotifications($this->context->link->getPageLink('order', true, $this->context->language->id)); break; case 'processing': // For 'processing' status, we need to poll for order creation. // This loop will try to find the order for a limited time to avoid // exceeding PHP execution limits. $maxAttempts = 10; // Max 10 attempts $sleepTime = 5; // Sleep 5 seconds between attempts (total max 50 seconds) $orderFound = false; $orderId = 0; for ($i = 0; $i < $maxAttempts; $i++) { $orderId = Order::getIdByCartId($cart->id); if ($orderId) { $orderFound = true; break; // Order found, exit loop } // If not found, wait for a few seconds before retrying. sleep($sleepTime); } if ($orderFound) { // Order found, redirect to confirmation page. Tools::redirect($this->context->link->getPageLink('order-confirmation', true, $this->context->language->id, [ 'id_cart' => $cart->id, 'id_module' => $this->module->id, 'id_order' => $orderId, 'key' => $this->context->customer->secure_key, ])); } else { // Order not found after multiple attempts, assume it's still processing or failed silently. $this->errors[] = Tools::displayError($this->trans('Your payment is still processing. Please check your order history later.', [], 'Modules.Hutko.Shop')); $this->redirectWithNotifications($this->context->link->getPageLink('order-history', true, $this->context->language->id)); } break; case 'approved': $orderId = Order::getIdByCartId($cart->id); // If the order doesn't exist yet, validate it. // The postponeCallback is used here to avoid race conditions with the callback controller // (which might be trying to validate the order on seconds ending in 8, while this // controller tries on seconds ending in 3). if (!$orderId) { // Define the validation logic to be executed by postponeCallback. // This callback will first check if the order exists, and only // validate if it doesn't, to avoid race conditions. $validationCallback = function () use ($cart, $amountReceived, $transaction_id) { // Re-check if the order exists right before validation in case the callback // controller created it in the interim while we were waiting for the second digit. if (Order::getIdByCartId($cart->id)) { return true; // Order already exists, no need to validate again. } $idState = (int)Configuration::get('HUTKO_SUCCESS_STATUS_ID'); // If order still doesn't exist, proceed with validation. return $this->module->validateOrderFromCart((int)$cart->id, $amountReceived, $transaction_id, $idState); }; // Postpone the execution of the validation callback until the second ends in 3. $validationResult = $this->module->postponeCallback($validationCallback, 3); // After the postponed callback has run, try to get the order ID again. $orderId = Order::getIdByCartId($cart->id); // If validation failed or order still not found, add an error. if (!$orderId || !$validationResult) { $this->errors[] = Tools::displayError($this->trans('Payment approved but order could not be created. Please contact support.', [], 'Modules.Hutko.Shop')); $this->redirectWithNotifications($this->context->link->getPageLink('order', true, $this->context->language->id)); break; } } // If order exists (either found initially or created by validation), redirect to confirmation. Tools::redirect($this->context->link->getPageLink('order-confirmation', true, $this->context->language->id, [ 'id_cart' => $cart->id, 'id_module' => $this->module->id, 'id_order' => $orderId, 'key' => $this->context->customer->secure_key, ])); break; default: // For any unexpected status, redirect to order history with a generic error. $this->errors[] = Tools::displayError($this->trans('An unexpected payment status was received. Please check your order history.', [], 'Modules.Hutko.Shop')); $this->redirectWithNotifications($this->context->link->getPageLink('order-history', true, $this->context->language->id)); break; } // This part should ideally not be reached if all cases are handled with redirects. // However, as a fallback, if any errors were accumulated without a specific redirect, // redirect to the order page. if (count($this->errors)) { $this->redirectWithNotifications($this->context->link->getPageLink('order', true, $this->context->language->id)); } } }