145 lines
6.5 KiB
PHP
145 lines
6.5 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Hutko - Платіжний сервіс, який рухає бізнеси вперед.
|
||
*
|
||
* Запускайтесь, набирайте темп, масштабуйтесь – ми підстрахуємо всюди.
|
||
*
|
||
* @author panariga
|
||
* @copyright 2025 Hutko
|
||
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
|
||
*/
|
||
|
||
|
||
if (!defined('_PS_VERSION_')) {
|
||
exit;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Class HutkoCallbackModuleFrontController
|
||
*
|
||
* This front controller handles the asynchronous callback notifications from the Hutko payment gateway.
|
||
* It is responsible for validating the payment response, updating the order status in PrestaShop,
|
||
* and handling various payment statuses (approved, declined, expired, processing).
|
||
* It also incorporates logic to mitigate race conditions with the customer's return to the result page.
|
||
*
|
||
* @property \Hutko $module An instance of the Hutko module.
|
||
*/
|
||
class HutkoCallbackModuleFrontController extends ModuleFrontController
|
||
{
|
||
|
||
public function postProcess(): void
|
||
{
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||
exit;
|
||
}
|
||
try {
|
||
// 1. Parse the incoming request body.
|
||
$requestBody = $this->getRequestBody();
|
||
|
||
// If request body is empty, log and exit.
|
||
if (empty($requestBody)) {
|
||
PrestaShopLogger::addLog('Hutko Callback: Empty request body received.', 2, null, 'Cart', null, true);
|
||
exit('Empty request');
|
||
}
|
||
// Assuming validateResponse returns true on success, or a string error message on failure.
|
||
$isSignatureValid = $this->module->validateResponse($requestBody);
|
||
if ($isSignatureValid !== true) {
|
||
PrestaShopLogger::addLog('Hutko Callback: Invalid signature. Error: ' . $isSignatureValid, 2, null, 'Cart', null, true);
|
||
exit('Invalid signature');
|
||
}
|
||
// PrestaShopLogger::addLog('Hutko Callback: ' . json_encode($requestBody), 1);
|
||
|
||
|
||
$transaction_id = $requestBody['order_id'];
|
||
$orderIdParamParts = explode($this->module->order_separator, $transaction_id);
|
||
$orderId = (int)$orderIdParamParts[0]; // Ensure it's an integer
|
||
|
||
// If we reached here, an order should exist. Load it.
|
||
$order = new Order($orderId);
|
||
if (!Validate::isLoadedObject($order)) {
|
||
PrestaShopLogger::addLog('Hutko Callback: Order could not be loaded for ID: ' . $orderId, 3, null, 'Order', $orderId, true);
|
||
exit('Order not found after validation');
|
||
}
|
||
|
||
// 7. Handle payment status from the callback.
|
||
$orderStatusCallback = $requestBody['order_status'];
|
||
$currentOrderState = (int)$order->getCurrentState();
|
||
|
||
switch ($orderStatusCallback) {
|
||
case 'approved':
|
||
// Only success state if no refunds was done.
|
||
if ($requestBody['response_status'] == 'success' && $requestBody['reversal_amount'] == '0') {
|
||
$expectedState = (int)Configuration::get('HUTKO_SUCCESS_STATUS_ID');
|
||
// Only change state if it's not already the success state or "Payment accepted".
|
||
// "Payment accepted" (PS_OS_PAYMENT) might be set by validateOrderFromCart.
|
||
if ($currentOrderState !== $expectedState && $currentOrderState !== (int)Configuration::get('PS_OS_PAYMENT')) {
|
||
$this->module->addPayment($requestBody, $order);
|
||
$order->setCurrentState($expectedState);
|
||
}
|
||
} else {
|
||
PrestaShopLogger::addLog('Hutko Callback: Unhandled response_status: ' . $requestBody['response_status']);
|
||
}
|
||
exit('OK');
|
||
break;
|
||
|
||
case 'declined':
|
||
$expectedState = (int)Configuration::get('PS_OS_ERROR');
|
||
// Only change state if it's not already the error state.
|
||
if ($currentOrderState !== $expectedState) {
|
||
$order->setCurrentState($expectedState);
|
||
}
|
||
exit('Order ' . $orderStatusCallback);
|
||
break;
|
||
case 'expired':
|
||
$expectedState = (int)Configuration::get('PS_OS_ERROR');
|
||
// Only change state if it's not already the error state.
|
||
if ($currentOrderState !== $expectedState) {
|
||
$order->setCurrentState($expectedState);
|
||
}
|
||
exit('Order ' . $orderStatusCallback);
|
||
break;
|
||
|
||
case 'processing':
|
||
// If the order is still processing, we might want to update its status
|
||
// to a specific 'processing' state if available, or just acknowledge.
|
||
// For now, if it's not already in a success/error state, set it to 'processing'.
|
||
$processingState = (int)Configuration::get('PS_OS_PAYMENT'); // Or a custom 'processing' state
|
||
if ($currentOrderState !== $processingState && $currentOrderState !== (int)Configuration::get('HUTKO_SUCCESS_STATUS_ID') && $currentOrderState !== (int)Configuration::get('PS_OS_ERROR')) {
|
||
$order->setCurrentState($processingState);
|
||
}
|
||
exit('Processing');
|
||
break;
|
||
|
||
default:
|
||
// Log unexpected status and exit with an error.
|
||
PrestaShopLogger::addLog('Hutko Callback: Unexpected order status received: ' . $orderStatusCallback . ' for order ID: ' . $orderId, 3, null, 'Order', $orderId, true);
|
||
exit('Unexpected status');
|
||
break;
|
||
}
|
||
} catch (Exception $e) {
|
||
// Log any uncaught exceptions and exit with the error message.
|
||
PrestaShopLogger::addLog('Hutko Callback Error: ' . $e->getMessage(), 3, null, 'HutkoCallbackModuleFrontController', null, true);
|
||
exit($e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Helper method to parse the request body from POST or raw input.
|
||
*
|
||
* @return array The parsed request body.
|
||
*/
|
||
private function getRequestBody(): array
|
||
{
|
||
|
||
$jsonBody = json_decode(file_get_contents("php://input"), true);
|
||
if (is_array($jsonBody)) {
|
||
return $jsonBody;
|
||
}
|
||
|
||
return [];
|
||
}
|
||
}
|