Платежи в Telegram

Как отправлять платёжную ссылку в Telegram-боте

Ссылка должна создаваться для сохранённого заказа, а не собираться строкой в коде бота; тогда сумму и назначение нельзя случайно подменить.

Платёжная ссылка — самый короткий способ начать продавать из бота. Вам не нужны Mini App, витрина и корзина: достаточно сообщения «Счёт на 3 200 ₽ за консультацию» с кнопкой, которая ведёт на форму оплаты. Первый рабочий вариант делается за час, потому что со стороны бота это один HTTP-запрос и одна inline-кнопка.

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

Минимальный старт без витрины

Фрилансер-дизайнер держал бота на четыре команды: показать портфолио, узнать цену, написать в личку, оплатить. Оплата была реквизитами в текстовом сообщении, и каждый второй перевод приходил без комментария — потом полчаса разбираться, кто это и за что. Замена на ссылку убрала разбор целиком: назначение и сумма зашиты в платёж, а колбэк приносит order_id.

Что нужно, чтобы повторить:

  1. Команда бота. /pay или кнопка «Оплатить» после диалога о цене.
  2. Запись в базе. Строка с tg_user_id, услугой, суммой и своим order_id.
  3. Запрос на создание платежа. POST /api/v1/payments, заголовки X-API-Key и X-Nonce.
  4. Ответ. Из него берёте pay_url, payment_id и expires_at.
  5. Сообщение. Inline-кнопка с текстом «Оплатить 3 200 ₽» и адресом pay_url.

Сумму передавайте строкой: "amount": "3200.00". Дробные числа в JSON — источник копеечных расхождений при сверке.

Соблазн собрать адрес шаблоном — «а вдруг там просто сумма в параметре» — возникает почти у всех. Так делать нельзя по двум причинам. Первая: адрес с суммой в открытом виде правится в браузере, и человек оплатит 10 ₽ вместо 10 000 ₽. Вторая: у вас не останется своей записи о заказе, и деньги придут ниоткуда.

Правильный порядок — сначала строка в orders со статусом pending, потом платёж на её основе, потом ссылка из ответа. Тогда order_id связывает три мира: ваш бот, платёжный сервис и бухгалтерию. Он же обеспечивает идемпотентность: повторный запрос с тем же order_id не создаст второй платёж, если покупатель дважды ткнул в кнопку. Подробнее этот механизм разобран в статье об идемпотентности платёжных запросов.

Сколько живёт ссылка

Ответ приходит в поле expires_at вместе с самой ссылкой. Сохраняйте его в базу рядом с заказом — без этого бот не сможет отличить «ещё можно платить» от «уже поздно».

Что продаётеРазумный срок жизниПочему
Цифровой товар с моментальной выдачей15–30 минутРешение принимается сразу, длинный срок только копит мусор
Услуга по договорённости, счёт от менеджера1–3 дняЧеловеку нужно свериться с календарём или бюджетом
Бронь места, тура, слотаСтолько, сколько держите броньСрок ссылки и срок брони обязаны совпадать

Когда время вышло, сервис присылает событие payment.expired. Обработайте его: переведите заказ в expired, освободите бронь и отредактируйте сообщение с кнопкой, чтобы человек не тыкал в мёртвый адрес. Сценарий с бронью подробно расписан в материале про оплату бронирования в Telegram-боте.

Человек не оплатил: что делать дальше

Считайте, что 40–60 % открывших ссылку не доведут дело до конца с первого раза. Это нормальная воронка, а не поломка. Разберите её на понятные состояния и ведите каждое отдельно.

  • Ссылку не открывали. Скорее всего, сообщение утонуло в непрочитанных. Помогает короткое напоминание через несколько часов.
  • Открыл, но не выбрал банк. Часто человек не был готов платить прямо сейчас. Предложите вторую попытку и уточните, остаётся ли заказ в силе.
  • Дошёл до банка и вернулся. Возможен отказ банка или нехватка средств. Тут уместно предложить другой способ оплаты — например, картой вместо СБП.
  • Пришло payment.canceled. Заказ закрывайте и не напоминайте больше: человек отказался осознанно.

Диагностика по кодам ошибок и типичным отказам собрана в статье что делать, если платёж не прошёл.

Напоминания, которые не бесят

Напоминание в личных сообщениях — тонкий инструмент: перегнёте, и человек нажмёт «Заблокировать бота», после чего вы потеряете и заказ, и канал связи. Рабочая схема — два касания и стоп.

  1. Через 2–3 часа. Одно предложение и свежая кнопка: «Заказ на курс ещё держится, ссылка действует до 21:00».
  2. За час до истечения. Прямо скажите, что будет дальше: место освободится, цена вернётся к обычной, доступ не откроется.
  3. Больше не пишите. Третье сообщение приносит отписки, а не деньги.

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

Чем подтверждается оплата

Скриншот из банка, слова «я всё отправил» и даже возврат человека в чат — это не подтверждение. Заказ переводит в paid только колбэк с событием payment.paid, у которого проверена подпись X-Signature: HMAC-SHA256 от X-Timestamp, точки и сырого тела запроса, ключ — signing_secret вашей кассы. Считайте её до разбора JSON и сравнивайте constant-time.

Отдельно про success_redirect_url: он нужен, чтобы человеку было куда вернуться после банка, и только для этого. Страница «Спасибо, оплата прошла» может открыться и у того, кто просто подставил адрес в браузере.

Когда одной ссылки уже мало

Ссылка отлично закрывает разовые продажи и счета от менеджера. Она начинает мешать, когда:

  • у вас каталог на несколько десятков позиций — покупателю нужна витрина, а не переписка;
  • вы продаёте доступ на период и его нужно продлевать — тогда смотрите в сторону оплаты подписки в Telegram-боте;
  • оплату принимают несколько менеджеров и нужны разные кассы и разграничение доступа.

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

Частые вопросы

Можно ли собрать адрес оплаты строкой в коде бота?

Нет. Адрес с суммой в открытом виде правится прямо в браузере, и покупатель заплатит 10 ₽ вместо 10 000 ₽. Сначала сохраните заказ у себя, потом создайте платёж запросом и возьмите готовый pay_url из ответа.

На сколько выставлять срок жизни ссылки?

Для цифрового товара с моментальной выдачей хватает 15–30 минут, для счёта от менеджера разумны 1–3 дня, а для брони срок ссылки должен совпадать со сроком удержания места. Точное время приходит в поле expires_at — сохраняйте его рядом с заказом.

Сколько напоминаний отправлять, если человек не заплатил?

Двух достаточно: короткое через 2–3 часа и одно за час до истечения ссылки с честным описанием последствий. Третье сообщение приносит блокировки бота, а не оплаты. Перед отправкой каждого проверяйте актуальный статус заказа, иначе напоминание уйдёт тем, кто уже заплатил.

Что делать, когда пришло событие payment.expired?

Переведите заказ в статус expired, освободите бронь или зарезервированное место и отредактируйте сообщение с кнопкой, чтобы покупатель не нажимал мёртвый адрес. Если человек всё ещё заинтересован, выпустите новую ссылку с актуальной суммой.

Считается ли страница «спасибо за оплату» подтверждением?

Нет. success_redirect_url нужен только для того, чтобы человеку было куда вернуться из банка, и открывается он даже при ручном вводе адреса. Статус заказа меняет колбэк payment.paid с проверенной подписью X-Signature.

Источники и документация