Как добавить проверку статуса 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&params[paymentId]=' . $_REQUEST['merchant_id'] . '&params[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 без валидации.

    Часто задаваемые вопросы