Как добавить проверку статуса expired в обработчик оплаты UnitPay
Проблема: ложные успешные платежи при статусе expired
При использовании платежного шлюза UnitPay разработчики часто сталкиваются с ситуацией, когда после отмены оплаты пользователем (статус expired) система отправляет коллбэк с признаком успеха (success). Это приводит к зачислению средств на аккаунт без реального поступления денег. В исходном коде отсутствует проверка текущего статуса транзакции, что является уязвимостью.
Как добавить проверку на статус expired
Для корректной обработки необходимо перед выполнением любых операций (запись в БД, начисление кредитов) проверить статус платежа через API UnitPay. Рекомендуется использовать метод getPayment или обрабатывать параметр status из запроса (если он передается). В большинстве случаев шлюз передает статус в переменной $_REQUEST['status'].
Пример проверки:
if (isset($_REQUEST['status']) && $_REQUEST['status'] === 'expired') { die('payment expired'); }Если статус не передается, выполните запрос к API UnitPay для верификации:
$paymentInfo = file_get_contents('https://unitpay.ru/api?method=getPayment¶ms[paymentId]=' . $_REQUEST['merchant_id'] . '¶ms[secretKey]=' . $secret_word2); $data = json_decode($paymentInfo, true); if ($data['result']['status'] === 'expired') { die('payment expired'); }Почему не выполняется INSERT в базу данных
Вторая частая проблема - запрос INSERT не выполняется, хотя код доходит до этой строки. Основные причины:
- Ошибка подключения к БД: проверьте, что
$server->connectionинициализирован и работает. Используйтеtry-catchдля отлова исключений. - Некорректные типы данных: в запросе используются плейсхолдеры
?. Убедитесь, что порядок и количество полей совпадает с массивом данных. Например, полеstatusожидает целое число (1), а передаётся строка. - Дублирование первичного ключа: если
unitpayIdявляется уникальным, повторная попытка вставки с тем же ID вызовет ошибку. Добавьте проверку на существование записи:INSERT IGNOREилиON DUPLICATE KEY UPDATE. - Отсутствие транзакции: если используется InnoDB, оберните запрос в
beginTransactionиcommit.
Полный исправленный код обработчика
Ниже представлен доработанный скрипт с проверкой статуса и улучшенной обработкой ошибок:
<?php if (!defined('FLUX_ROOT')) exit; if (empty($_REQUEST['merchant_id'])) { die('bad request!'); } $merchant = Flux::config('enot_merchant_id'); $secret_word2 = Flux::config('enot_secret_key2'); $sign = md5($merchant.':'.$_REQUEST['amount'].':'.$secret_word2.':'.$_REQUEST['merchant_id']); if ($sign != $_REQUEST['sign_2']) { die('bad sign!'); } // Проверка статуса if (isset($_REQUEST['status']) && $_REQUEST['status'] === 'expired') { die('payment expired'); } $credits = floor($_REQUEST['credited'] + $_REQUEST['commission']); $account_id = $_REQUEST['custom_field']; $order_id = $_REQUEST['intid']; try { $sql = 'INSERT INTO ' . $server->loginDatabase . '.unitpay_payments (`unitpayId`, `account`, `sum`, `itemsCount`, `dateCreate`, `dateComplete`, `status`, `state`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; $sth = $server->connection->getStatement($sql); $sth->execute(array($order_id, $account_id, $credits, $credits, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), 1, 'completed')); $server->loginServer->depositCredits($account_id, $credits, $credits); } catch (Exception $e) { error_log('UnitPay payment error: ' . $e->getMessage()); die('error'); } die('success'); ?>Рекомендации по безопасности
- Всегда проверяйте цифровую подпись (
sign_2) перед обработкой. - Используйте HTTPS для коллбэков.
- Логируйте все запросы для аудита.
- Не доверяйте данным из
$_REQUESTбез валидации.