Все записи
6 мин

Купил у себя же токены на проде — а транзакции нет. Дело было в URL ЮKassa

Building in Publicденьги

Если у вас платёж в ЮKassa вроде как «прошёл», деньги с карты ушли, а в админке транзакция не появилась и пользователю ничего не начислилось — почти наверняка дело в URL, которые прописаны в самой кассе. Конкретно в return-URL и в адресе для коллбэка, куда касса стучится после оплаты. Если там лежат старые адреса, ещё с этапа разработки, то деньги-то спишутся, а вот уведомление об успешной оплате до вашего приложения просто не доедет, и для системы платежа как будто и не было. Ровно на это я и наступил после запуска, и нашёл я это не по логам и не по алертам, а тем что сам пошёл и купил у себя же платный пакет токенов на проде.

Зачем вообще покупать у самого себя

Когда запускаешь приём оплаты, первое что хочется проверить — что вся цепочка реально работает от начала до конца, а не только на бумаге. Тесты зелёные, песочница отвечает, но это всё ещё не «человек нажал кнопку и у него начислились токены». И тут есть один честный способ убедиться, не самый дешёвый, зато без иллюзий: открыть прод как обычный пользователь, выбрать платный пакет и оплатить его своей же картой, не тестовой, а настоящей, с реальным списанием. Я так и сделал, прошёл всю воронку оплаты как любой клиент, деньги списались, касса показала что всё ок.

А потом я зашёл в админку посмотреть на свою же транзакцию, и её там не было. Совсем. С момента запуска ни одной записи о том что кто-то платил, хотя платил как минимум я сам и только что. Я для себя это так и зафиксировал: покупал на проде токены когда тестил, но в админке нет этой транзакции с момента запуска. То есть деньги ушли, а система их не увидела, и это самое неприятное состояние, потому что снаружи всё выглядит рабочим.

Почему касса «молчала»

Дальше начинается разбор, и тут важно понимать как вообще устроен приём оплаты через бота или мини-приложение. Вы же не просто отправляете человека на страницу оплаты и забываете про него. После того как он заплатил, должно произойти две вещи: его надо вернуть обратно в приложение по return-URL, и сама касса должна прислать вашему серверу уведомление на отдельный адрес — мол, платёж номер такой-то успешно прошёл, начисляй. Вот это второе уведомление и есть тот момент, где транзакция превращается из «деньги где-то списались» в «пользователю начислены токены, запись в базе создана».

А у меня в настройках кассы лежали не те URL. Старые, наши же, но с прошлого этапа, когда адреса были другими. И получилось что касса честно пыталась достучаться, только стучалась в пустоту, потому что по тем адресам уже никто не отвечал нужным образом. Своими словами это звучало примерно так: мой платёж как бы и не «прошёл», потому что в кассе были указаны не те наши урлы, вот так мы баг и нашли. Деньги при этом реально списываются, касса своё дело делает, а вот цепочка обратно к приложению рвётся именно на коллбэке.

И вот это, кстати, главная ловушка таких багов. Он не падает с ошибкой, не кидает пятисотку, не светится красным в мониторинге. Всё «работает». Просто тихо не доезжает один HTTP-запрос, и из-за этого целый кусок продукта, приём денег, фактически мёртв, а вы об этом узнаёте только если сами пойдёте и заплатите. Никакой тест в песочнице это бы не поймал так наглядно, потому что в песочнице у вас одни адреса, а на проде совсем другие, и расхождение живёт именно в конфиге внешнего сервиса, а не в коде.

Что я сделал в моменте и что было настоящим фиксом

Сразу встал вопрос — а токены-то я себе купил, начислить надо. Их можно докинуть руками, через смену статуса операции, я так и сделал, чтобы баланс сошёлся. Но это вообще не решение проблемы, это просто латание дырки на одном конкретном платеже. Корень в URL, и пока они не те, у любого следующего клиента будет ровно та же история: заплатил, деньги ушли, токенов нет, а в админке тишина.

Тут был момент, который я хочу оставить как есть, потому что он про приоритеты. Мне предложили вернуть начисленные токены, раз уж это был тестовый платёж, как бы по-честному откатить. И реакция была однозначной: не надо мне ничего возвращать, чините урл. Не потому что грубость ради грубости, а потому что деньги тут вообще не точка боли. Точка боли — что интеграция оплаты сломана и каждый реальный клиент сейчас платит в никуда. Свои токены и потерянная на тесте сумма это вообще не масштаб проблемы, это копейки на фоне того что приём денег в продукте не работает.

Настоящий фикс был тупой и быстрый: зайти в личный кабинет кассы, прописать правильные актуальные адреса для return и для коллбэка, те что соответствуют боевому окружению, и снова провести покупку от начала до конца. После этого транзакция в админке появилась, токены начислились сами, без всякого ручного вмешательства, цепочка замкнулась. Вот тогда уже можно говорить что оплата действительно сделана, а не «вроде работает».

Что я из этого вынес

Первое и самое практичное: настройка приёма оплаты в телеграм-боте или мини-приложении это не только код на вашей стороне, это ещё и конфиг во внешнем сервисе, и он живёт своей отдельной жизнью. Можно идеально написать обработчик коллбэка, покрыть его тестами, и всё равно не получить ни одной транзакции, потому что в кабинете кассы стоит не тот адрес. Среди всех «почему платёж не доходит до приложения» это, пожалуй, самая частая и самая обидная причина, потому что искать её начинаешь в коде, а она в чужой админке.

Второе: проверять приём денег надо реальной покупкой на проде, своими руками и своим кошельком. Да, это стоит денег, да, неприятно платить самому себе. Но это единственный способ убедиться что вся цепочка от кнопки до начисления реально замкнута на боевых адресах, а не на тех что остались с разработки. Я нашёл этот баг ровно потому что не поленился купить, и хорошо что нашёл я, а не первый живой клиент, который бы молча ушёл, решив что у меня всё сломано.

И третье, чуть в сторону от техники. Когда что-то в продукте ломается на деньгах, очень легко зациклиться на сумме: вернуть, компенсировать, посчитать кто кому должен. А правильный вопрос всегда один — что именно сломано в системе и как сделать чтобы оно больше не ломалось. Токены можно начислить за секунду. А вот рабочую интеграцию оплаты, которой доверяешь, вы получаете только когда сами прошли всю воронку и увидели свою транзакцию в админке. Вот и делайте выводы.

Если интересно, про похожие тихие косяки, которые не падают с ошибкой, а просто молча не работают, я уже писал — например про пустой экран на деплое, когда Claude Code забыл подключить отправку. А ещё был случай, когда один LLM-вызов внутри транзакции через advisory lock положил всё приложение — снаружи тоже выглядело как «всё сломалось», а корень был спрятан. Сценарий тот же по духу: всё зелёное, а фичи нет.