Покупатель заплатил, банк деньги списал, а в админке заказ висит неоплаченным. Чаще всего причина в обработчике уведомлений от банка. В таких обработчиках я раз за разом нахожу одни и те же четыре ошибки. Есть и пятая ситуация, которую никакой обработчик не исправит.
Речь про интернет-эквайринг Альфа-банка и Сбербанка: у них один платежный шлюз (RBS) и одинаковые правила уведомлений. Документация Сбера подробнее, и она подходит для Альфы.
Как банк сообщает об оплате
После оплаты шлюз отправляет на адрес магазина обычный GET-запрос:
?orderNumber=12345&mdOrder=057a6b30-...&operation=deposited&status=1
| Параметр | Что значит |
|---|---|
mdOrder | номер заказа в системе банка, по нему нужно перепроверять статус |
orderNumber | номер заказа на сайте |
operation | approved (деньги заблокированы), deposited (списаны), reversed (отмена), refunded (возврат) |
status | 1 успех, 0 неуспех |
checksum | подпись, приходит, только если в кабинете банка задан ключ |
Банк считает уведомление доставленным, только если сайт ответил кодом 200. На любой другой ответ шлюз повторяет запрос каждые 10 минут, пока не получит 200 или пока не наберется четыре неудачи подряд. После этого он прекращает попытки.
Ошибка 1: обработчик всегда отвечает 200
Самая дорогая из четырех. Обработчик получил уведомление, что-то не сошлось, он записал строчку в лог и завершился. А завершение скрипта без ошибки и есть ответ 200. Банк считает, что все доставлено, и больше не повторяет.
Получается, что механизм повторов, придуманный ровно на такой случай, выключен руками. Любой сбой превращается в потерянную оплату.
Нужно различать два вида причин:
- временные: банк не ответил на проверочный запрос, моргнула сеть, недоступна база. Здесь надо отвечать не 200, тогда банк повторит;
- устойчивые: суммы не сходятся, заказа нет. Повтор не поможет, поэтому 200, но случай надо громко залогировать и показать человеку, например письмом менеджеру.
Ошибка 2: обработчик верит параметрам запроса
Если в кабинете банка не задан ключ подписи, уведомление приходит без нее. Тогда адрес обработчика это просто ссылка, которую может открыть кто угодно, зная mdOrder. А mdOrder покупатель видит в адресной строке платежной формы.
Минимальная защита: на каждое уведомление перезапрашивать статус у банка методом getOrderStatus.do и проверять, что OrderStatus равен 2.
Проверки "заказ в банке существует и у него есть сумма" мало: у неоплаченного заказа статус 0, а сумма есть. В одном из проектов была ровно эта дыра, и оплату можно было проставить себе из браузера.
Коды статуса: 0 не оплачен, 1 деньги заблокированы, 2 оплачен, 3 отменен, 4 возврат, 6 отклонен. Надежнее всего включить ключ подписи в кабинете и проверять checksum через hash_hmac('sha256', ...).
Ошибка 3: сумма должна совпасть до копейки
Условие вида
intval($order['PRICE'] * 100) == $answer['Amount']
отбрасывает все, что не совпало до копейки: доплату отдельным платежом, частичную оплату, счет, который менеджер выставил вручную на другую сумму. В разобранном случае так потерялись оплаты по двум заказам на несколько тысяч рублей каждая.
Правильнее накапливать оплаченную сумму заказа и проводить заказ, когда набралась полная цена, а расхождение отправлять человеку письмом.
Ошибка 4: чек пробивается внутри обработчика
Если обработчик сам обращается в облачную кассу за чеком, ответ банку задерживается. Я замерял 5-7,5 секунды на одно уведомление, и все это время шлюз ждет. Фискализацию надо выносить в отложенную задачу, а банку отвечать сразу.
Где задается адрес уведомлений
Есть два способа, и из-за этого код может выглядеть так, будто уведомлений нет вообще, а они приходят:
- в кабинете банка: раздел настроек callback-уведомлений, поле со ссылкой, список событий и ключ подписи. Работает, даже если в коде сайта про уведомления ничего нет;
- при создании платежа: параметр
dynamicCallbackUrl. Он работает, только если банк включил эту опцию, иначе молча игнорируется.
Прежде чем править обработчик, посмотрите в кабинете банка, какой адрес там стоит.
Чего не исправит никакой обработчик
Банк не отправил уведомление, а покупатель после оплаты не вернулся на сайт. Обработчик в этом случае просто не запускается.
Я видел это на живом заказе: по неудачной попытке покупателя уведомление пришло, а по успешной оплате через четыре минуты в логах сервера тишина.
Страхует только сверка на своей стороне. Раз в сутки забирать у банка список оплат методом getLastOrdersForMerchants.do и сравнивать со своими заказами. Две детали, на которых легко споткнуться:
- параметр
merchantsобязателен и требует логин магазина, а не логин API. С логином API банк ответитerrorCode 5, Отсутствует обязательное поле [merchants]; - суммировать надо все операции по заказу, а не первую. Доплата приходит отдельной операцией и иначе дает ложное расхождение.
Короткий список для проверки
- Отвечает ли обработчик не 200 при временных сбоях?
- Перезапрашивается ли статус у банка и проверяется ли
OrderStatus == 2? - Учитываются ли частичные оплаты и доплаты?
- Не ждет ли банк, пока пробьется чек?
- Есть ли ежедневная сверка оплат с банком?
Если хотя бы на один вопрос ответ "нет" или "не знаю", оплаты рано или поздно будут теряться. Могу проверить ваш обработчик: обычно для этого хватает доступа к коду и журналу сервера.
Источники: уведомления обратного вызова, документация Сбербанка, callback-уведомления, developer.sberbank.ru.