
Спойлер: кажется SHA1 устарел, а туториалы этого еще не знают
Привет всем.
Для контекста: я не программист. Я гуманитарий и до безумия боюсь цифрового неравенства, которое уже наступает. Поэтому, вооружившись AI‑агентом (Cursor) и бесплатными Claude, Gemini, DeepSeek, пытаюсь быть в тренде (некоторые называют это вайб‑кодингом). Признаться, я тоже думал, что это что‑то легкое, «вайбовое» — главное, всё внятно описать, что ты хочешь. Но по факту в том, что происходит, всё равно приходится разбираться самому.
Свежий пример моих «грабель» — решил прикрутить оплату ЮМани.
Казалось бы, ЮМани, всё придумано до меня. Но есть нюансы...
Что хотел сделать
Пользователь нажимает «Купить» → переходит на форму оплаты ЮМани → платит → ЮМани уведомляет мой сервер → сервер создаёт одноразовую ссылку на скачивание → пользователь получает файл. Все счастливы.
Насколько я понял и разобрался, то ЮМани шлет HTTP‑уведомления на мой сервер когда приходит платёж. Я указываю URL своего PHP‑скрипта в настройках кошелька — и при каждой оплате ЮМани делает POST‑запрос на этот адрес с данными о платеже.
То есть задача сводится к тому, что нужен скрипт, который принимает запрос, проверяет подпись (чтобы убедиться что это реально ЮМани, а не кто‑то левый), и если всё ок — записывает покупку в базу данных.
Пользователь тем временем попадает на страницу /success.php, нажимает «Проверить оплату» — страница опрашивает базу данных и если запись есть — выдаёт одноразовую ссылку на скачивание.
Проблема 1 — скрипт не отвечал
Когда я открыл адрес скрипта в браузере, получил ошибку 405 Method Not Allowed.
Паниковать еще рано! Оказалось, что это нормально — скрипт принимает только POST-запросы (которые шлёт ЮМани), а браузер открывает через GET. Сказал Cursor‑у добавить обработку GET с ответом «OK» — чисто для ручной проверки в браузере. ЮМани всегда шлёт POST и только POST.
Проблема 2 — уведомления доходят, но не записываются
После подсказки от Claude добавил логирование, чтобы смотреть по логам, что происходит. По логам уведомления приходят, но скрипт пишет «invalid sha1_hash». Это блин, что такое? Оказалось, что скрипт читал подпись из поля «sha1_hash», а ЮМани присылает в поле «sign» (зачем?!). Поменял название поля — в логе «received» что‑то появилось. Но хеши всё равно не совпадали.
Проблема 3 — неправильный алгоритм. Главная
Пока не полез в документацию ЮМани ничего не выходило. В общем оказалось, что я использовал в скрипте старый протокол... Скрипт считал подпись старым способом — SHA1 от строки параметров через & в фиксированном порядке.
А сейчас подпись — это HMAC‑SHA256 от URL‑кодированной строки всех параметров уведомления кроме sign. Параметры отсортированы по алфавиту.
То есть отличие от скрипта:
— Не SHA1, а HMAC‑SHA256 — принципиально другой алгоритм
— Параметры сортируются по алфавиту (раньше был фиксированный порядок)
Насколько я понял ЮМани обновили протокол — теперь только «sign» и HMAC‑SHA256.
Вот итоговый код проверки подписи:
// Берём все POST параметры кроме 'sign' $params = $_POST; $receivedSign = (string)($params['sign'] ?? ''); unset($params['sign']); // Сортируем по алфавиту ksort($params); // Собираем строку key=urlencoded_value&key=urlencoded_value $parts = []; foreach ($params as $key => $value) { $parts[] = $key . '=' . rawurlencode((string)$value); } $hashString = implode('&', $parts); // Считаем HMAC-SHA256 с секретным ключом из настроек ЮMoney $calculatedSign = hash_hmac('sha256', $hashString, YOOMONEY_SECRET); // Сравниваем через hash_equals (защита от timing attack) if (!hash_equals($calculatedSign, $receivedSign)) { // Подпись не совпала — это точно не ЮMoney, можно смело 400 http_response_code(400); exit; }
Проблема 4 — card‑incoming
Но и это еще не все.
Провёл тестовый платёж картой. Деньги списались, но запись в БД не появилась.
Смотрю лог: YooMoney skip: notification rejected, type=card-incoming
Скрипт принимал только «p2p‑incoming» (перевод из кошелька), а оплата картой приходит как «card‑incoming». Попросил Cursor добавить оба типа:
$notification_type = $_POST['notification_type'] ?? ''; $allowedTypes = ['p2p-incoming', 'card-incoming']; if (!in_array($notification_type, $allowedTypes)) { // Подпись верная — запрос точно от ЮМани. // Просто этот тип мы не обрабатываем (может быть новый тип в будущем). // Отвечаем 200, чтобы ЮМани не слала повторы и не считала сервер упавшим. http_response_code(200); exit; }
Фух, блин, вроде заработало. Перевел сам себе 10 рублей. Победа!
В итоге, если кому понадобится — краткая инфо.
SHA1 в ЮМани не работает — теперь «sign» и HMAC‑SHA256. Если нашел инструкцию с «sha1_hash» и фиксированным порядком полей — он устарел.
«p2p‑incoming» и «card‑incoming» — надо оба, и карта, и кошелек.
Если подпись не совпала — отвечаем «400» (это чужой запрос, не ЮМани). Но если подпись верна, а тип платежа тебе просто не подходит — отвечаем «200», чтобы ЮМани не считала твой сервер упавшим и не отключила уведомления.
Логирование — при любом удобном случае. Нужен error_log в скрипте, чтобы реально смотреть, что приходит.
Тестовые уведомления от ЮМани не создают запись в БД — они только проверяют доступность скрипта и правильность подписи.
Ну, вот так...
vbatalov
Почему это здесь, а не Пикабу?
Скрытый текст
!?
MrSmitix
По тому что пикабу уже здесь...
ИИ написала статью (спасибо —) о том как без знании программирования через ИИ прикручивать эквайринг. С каждым днём мы всё дальше от бога. Спасибо что прошивки для мед оборудования ещё не вайбкодят.
У них там в документации есть ещё operation_id и unaccepted из важных параметров которые не плохо было бы учитывать. Попросите агента добавить их обработку и напишите вторую часть. А ещё они уведомление присылают максимум 3 раза, так что
есликогда ваш сервис ляжет больше чем на час, а кому-то вздумается что-то купить, у вас будут проблемы. Это уже идея для 3 части