139 lines
6.2 KiB
PHP
139 lines
6.2 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.
|
|
$calbackContent = $this->getCallbackContent();
|
|
|
|
|
|
|
|
|
|
$id_order_parts = explode($this->module->order_separator, $calbackContent['order_id']);
|
|
$id_order = (int)$id_order_parts[0]; // Ensure it's an integer
|
|
|
|
// If we reached here, an order should exist. Load it.
|
|
$order = new Order($id_order);
|
|
if (!Validate::isLoadedObject($order)) {
|
|
PrestaShopLogger::addLog('Hutko Callback: Order could not be loaded for ID: ' . $id_order, 3, null, 'Order', $id_order, true);
|
|
throw new Exception('Order not found after validation');
|
|
}
|
|
$this->context->currency = new Currency($order->id_currency);
|
|
$this->context->customer = new Customer($order->id_customer);
|
|
$this->context->language = new Language($order->id_lang);
|
|
|
|
// 7. Handle payment status from the callback.
|
|
$orderStatusCallback = $calbackContent['order_status'];
|
|
$currentOrderState = (int)$order->getCurrentState();
|
|
|
|
switch ($orderStatusCallback) {
|
|
case 'approved':
|
|
// Only success state if no refunds was done.
|
|
if ($calbackContent['response_status'] == 'success' && (int)$calbackContent['reversal_amount'] === 0) {
|
|
$expectedState = (int)Configuration::get('HUTKO_SUCCESS_STATUS_ID', null, null, null, Configuration::get('PS_OS_PAYMENT'));
|
|
// Only change state if it's not already the success state or "Payment accepted".
|
|
|
|
if ($currentOrderState !== $expectedState) {
|
|
$callbackAmount = $calbackContent['actual_amount'] ?? $calbackContent['amount'];
|
|
$amountFloat = round($callbackAmount / 100, 2);
|
|
$order->addOrderPayment($amountFloat, $this->module->displayName, $calbackContent['order_id'], $this->context->currency);
|
|
$order->setCurrentState($expectedState);
|
|
$this->module->addOrderMessage($order, $this->trans('Pay via Hutko: ', [], 'Modules.Hutko.Admin') . ' ' . $calbackContent['currency'] . $amountFloat);
|
|
}
|
|
}
|
|
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':
|
|
//no need to change status
|
|
exit('Order ' . $orderStatusCallback);
|
|
break;
|
|
|
|
default:
|
|
// Log unexpected status and exit with an error.
|
|
PrestaShopLogger::addLog('Hutko Callback: Unexpected order status received: ' . $orderStatusCallback . ' for order ID: ' . $id_order, 3, null, 'Order', $id_order, true);
|
|
throw new Exception('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);
|
|
throw new Exception('Unknown error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to parse the request body from raw input.
|
|
*
|
|
* @return array The parsed request body.
|
|
*/
|
|
private function getCallbackContent(): array
|
|
{
|
|
$calbackContent = json_decode(file_get_contents("php://input"), true);
|
|
if (!is_array($calbackContent) || !count($calbackContent)) {
|
|
PrestaShopLogger::addLog('Hutko Callback: Empty request body received.', 2, null, 'Cart', null, true);
|
|
throw new Exception('Empty request');
|
|
}
|
|
// Assuming validateResponse returns true on success, or a string error message on failure.
|
|
$isSignatureValid = $this->module->validateResponse($calbackContent);
|
|
if ($isSignatureValid !== true) {
|
|
PrestaShopLogger::addLog('Hutko Callback: Invalid signature. Error: ' . $isSignatureValid, 2, null, 'Cart', null, true);
|
|
throw new Exception('Invalid signature');
|
|
}
|
|
if (Configuration::get('HUTKO_SAVE_LOGS')) {
|
|
$this->module->log('CalbackContent: ' . json_encode($calbackContent));
|
|
}
|
|
return $calbackContent;
|
|
}
|
|
}
|