---
title: "Платежи: инвойсы и Telegram Stars"
description: "Платежи в telegram-боте на Go: tgbotapi.NewInvoice и NewLabeledPrice, Telegram Stars с валютой XTR, pre-checkout и тестовые платежи — обзорная карта курса."
course: "Telegram-боты на Go: от эха до полноценного сервиса"
url: "https://golangmaster.ru/courses/telegram-bot/lessons/20-platezhi-obzor/"
published: "2026-09-19"
updated: "2026-09-25"
minutes: 16
---

# Платежи: инвойсы и Telegram Stars

*Курс «Telegram-боты на Go: от эха до полноценного сервиса», урок 20 из 25. ~16 минут. Обновлено 25.09.2026. Источник: https://golangmaster.ru/courses/telegram-bot/lessons/20-platezhi-obzor/*

Платежи в telegram-боте на Go начинаются с одного вызова tgbotapi.NewInvoice: бот отправляет счёт, Telegram показывает кнопку «Оплатить», а дальше по цепочке приходят pre-checkout и successful_payment. Этот урок — обзорная карта всей схемы: инвойс с NewLabeledPrice, провайдеры и тестовые платежи, Telegram Stars с валютой XTR — честно без продакшен-глубины, но с точным списком того, куда копать дальше.

Деньги в боте — это подписки, разовые товары и донаты, и Bot API умеет всё три: платёжная схема встроена в Telegram давно, а с 2024 года к фиатным провайдерам добавились внутренние звёзды. Хорошая новость архитектурная: платежи живут в тех же апдейтах, что и сообщения, — новых серверов, портов и источников событий не появляется, бот просто обрабатывает ещё два типа обновлений. Схема одинаковая для любых денег: SendInvoice показывает счёт, пользователь жмёт «Оплатить», бот подтверждает pre-checkout, Telegram списывает и шлёт successful_payment. Меняется только источник списания: токен провайдера для реальных валют или звёзды для цифровых товаров. Сразу договоримся о глубине: это обзорная карта — кабинет провайдера, вебхуки, чеки и налоги останутся за кадром, зато весь путь от инвойса до выдачи товара будет виден целиком и каждый шаг будет с кодом.

### Инвойс: tgbotapi.NewInvoice и NewLabeledPrice

Инвойс — это особое сообщение с кнопкой «Оплатить», которое собирает конфигурация InvoiceConfig, а конструктор tgbotapi.NewInvoice заполняет обязательные поля. Их немного, но каждое работает в платёжной логике: title — название товара до 32 символов, description — строка на экране подтверждения, payload — ваш собственный идентификатор заказа, который Telegram хранит как есть и вернёт после оплаты; его длина — до 128 байт, и в него кладут id заказа из базы. Currency — код валюты, а Prices — срез tgbotapi.LabeledPrice, собранный через NewLabeledPrice(label, amount): сумма в минимальных единицах, для рублей и долларов это копейки и центы, для звёзд — целые штук. Провайдерские инвойсы ещё и разбиваются на компоненты — товар, доставка, скидка, — каждая своим LabeledPrice, и итог считается из этой таблицы. ProviderToken и StartParameter замыкают конфигурацию: токен решает, кто списывает деньги, о нём — следующий раздел.

### Отправка инвойса

```go
prices := []tgbotapi.LabeledPrice{
        tgbotapi.NewLabeledPrice("Подписка «Про», месяц", 29900),
}
invoice := tgbotapi.NewInvoice(
        chatID,
        "Подписка «Про»",                    // заголовок, до 32 символов
        "Отчёты и экспорт на 30 дней",       // описание на экране оплаты
        "sub-monthly-1024",                  // payload: ваш id заказа
        os.Getenv("PROVIDER_TOKEN"),         // токен провайдера из BotFather
        "sub-monthly",                       // startParameter
        "EUR",                               // валюта
        prices,
)
if _, err := bot.Send(invoice); err != nil {
        log.Println(err)
}
```

Сумма 29900 при валюте EUR — это 299 евро и 00 центов: LabeledPrice всегда в минимальных единицах, и перепутанная шкала — самая дорогая опечатка в этой теме. Payload sub-monthly-1024 — связка с вашей базой: в successful_payment придёт именно он, по нему выдаётся оплаченный товар. Send уходит обычным методом: инвойс — обычное сообщение с особым содержимым.

### Провайдеры и тестовые платежи

Фиатные деньги в боте идут через платёжного провайдера: Telegram передаёт инвойс подключённой системе, та списывает средства, а бот получает только подтверждение — сами деньги через Telegram не проходят. Токены выдаются в BotFather разделом /payments: поддерживаются Stripe и региональные провайдеры, список с примерами есть в документации Bot API, а выбор обычно диктуется страной и юридическим статусом продавца. Для разработки у большинства провайдеров есть тестовый режим: тестовые токены и тестовые карты дают возможность пройти всю схему — инвойс, pre-checkout, successful_payment — без единой копейки. Проверять удобно в тестовой среде Telegram Desktop, где клиент работает против песочницы. Честно про сложность: реальный фиат тянет за собой кабинет провайдера, вебхуки о статусах, возвраты и договор — это отдельный проект, и обзорный урок сознательно не притворяется, что покрывает его двадцатью строками. Карта пути та же самая, меняется только обвязка вокруг неё.

### Telegram Stars: XTR и пустой provider_token

Звёзды — внутренняя валюта Telegram, появившаяся в 2024 году, и для цифровых товаров в ботах она стала обязательной дорогой: правила магазинов приложений требуют, чтобы цифровой контент продавался через встроенную оплату, и звёзды — это та самая встроенная оплата. Схема для разработчика почти не меняется: в инвойсе ставится Currency XTR, ProviderToken пустая строка, а суммы в Prices считаются целыми звёздами — никакого деления на сотни. Подтверждение и successful_payment те же, возврат делается методом Bot API refundStarPayment, который свежие обёртки библиотеки дают соответствующей конфигурацией. Звёзды же обслуживают периодические подписки ботов и платные медиа — в этом уроке только отметим их существование, а инвойс в звёздах собирается так же, как обычный: те же поля, другая валюта. Для пет-проекта это идеальный вход в платежи: ни договора с провайдером, ни тестовых карт — минимальный инвойс оплачивается парой звёзд.

### Pre-checkout и successful_payment

```go
for update := range updates {
        switch {
        case update.PreCheckoutQuery != nil:
                // между нажатием «Оплатить» и списанием: 10 секунд на решение
                ok := tgbotapi.PreCheckoutQueryConfig{
                        PreCheckoutQueryID: update.PreCheckoutQuery.ID,
                        OK:                 true,
                }
                _, _ = bot.Request(ok)

        case update.Message != nil && update.Message.SuccessfulPayment != nil:
                p := update.Message.SuccessfulPayment
                log.Printf("оплачен заказ %s: %d %s, charge %s",
                        p.InvoicePayload, p.TotalAmount, p.Currency,
                        p.TelegramPaymentChargeId)
                grantPurchase(p.InvoicePayload, p.TelegramPaymentChargeId)
        }
}
```

PreCheckoutQuery приходит после нажатия кнопки и до списания: у бота десять секунд ответить OK или отклонить с сообщением об ошибке — молчание отмена трактует как отказ. В ветке успешной оплаты значимые поля: InvoicePayload, по которому выдаётся товар, и TelegramPaymentChargeId — уникальный идентификатор транзакции, он же ключ возврата и ключ идемпотентности выдачи.

> **Живое место: десять секунд на pre-checkout.** Хендлер pre-checkout пошёл в базу проверять наличие товара, база тормозит на фоне деплоя, десять секунд истекли — Telegram отменил платёж, пользователь увидел ошибку, и единственная информация о попытке — в логах. Проверка в этом хендлере обязана быть лёгкой: посмотреть в кэш или память, при сомнении — отказать с внятным сообщением и предложить попробовать снова, это нормальный исход. Вторая ловушка ждёт после оплаты: webhook-доставка повторяет апдейт при недоступности сервера, и без записи charge_id выданного товара повторный successful_payment выдаст подписку второй раз. Идемпотентность — запоминание TelegramPaymentChargeId и пропуск обработанного — ставится в тот же вечер, когда появляется первый платёж, а не после первого двойного.

### Что приходит после кнопки «Оплатить»

Весь диалог денег укладывается в два апдейта и один ответ. PreCheckoutQuery несёт ID запроса, валюту, сумму и ваш payload — достаточно данных, чтобы отказать по делу: товар сняли с продажи, лимит исчерпан, пользователь из заблокированной страны. Ответ конфигурацией PreCheckoutQueryConfig с OK true открывает списание; OK false с ErrorMessage до пары десятков слов объясняет причину — этот текст пользователь увидит в диалоге оплаты. Дальше успешная ветка: SuccessfulPayment внутри Message приносит InvoicePayload, TotalAmount, Currency и пару идентификаторов — TelegramPaymentChargeId и ProviderPaymentChargeId. По payload выдаётся товар, charge_id записывается как факт выдачи и страховка от повторов. Для звёзд на этом всё — деньги уже внутри Telegram; для провайдера начинается его половина работы: чеки, статусы и возвраты в его кабинете. Статусы заказов хранятся у себя — хэш в Redis на каждый payload отлично закрывает эту роль.

### Обзорная карта платежа

| Этап | Кто инициирует | Ваш код | Результат |
| --- | --- | --- | --- |
| Инвойс | Бот | NewInvoice + bot.Send | Сообщение с кнопкой «Оплатить» |
| Pre-checkout | Telegram | PreCheckoutQueryConfig, OK true/false | Разрешение или вежливый отказ до списания |
| Оплата | Пользователь | ничего — Telegram списывает | successful_payment в Message |
| Выдача | Бот | grantPurchase по payload | Товар у пользователя, charge_id записан |
| Возврат | Бот | refundStarPayment или кабинет провайдера | Деньги вернулись, статус обновлён |

Что осталось за кадром — и об этом честно. Реальный фиат: договор с провайдером, KYC, вебхуки о промежуточных статусах, чеки и налоги по юрисдикции — у каждого провайдера своя документация, и объём этой части выше всего курса про ботов. Подписки через звёзды: отдельные методы на выдачу, продление и отмену, плюс служебные апдейты об их изменении. Mini App со своей корзиной: интерфейс покупки переезжает в веб-вью, но бэкенд остаётся ровно этой схемой. Проверка платежа на своей стороне: суммы и валюты стоит сверять с ожидаемыми, payload валидировать — successful_payment верить, но перепроверять. Обзорная карта не делает вас платёжным разработчиком, зато снимает страх перед темой: четыре этапа, два апдейта, один ответ — остальное обвязка вокруг уже знакомого цикла range.

> **Пройти схему без денег.** Понадобятся токен бота из BotFather и локальный Go. Для фиата возьмите тестовый токен провайдера из раздела /payments и включите тестовую среду в Telegram Desktop — пройдёте инвойс и оплату тестовой картой. Для звёзд дешевле всего отправить инвойс самому себе: Currency XTR, пустой provider_token, цена в пару звёзд — и весь путь от pre-checkout до successful_payment пройдёт по-настоящему. Логи handler-а покажут оба апдейта целиком — сохраните их как фикстуры для тестов из урока про тестирование бота.

### Проверка: инвойс в звёздах

**Вопрос:** Что нужно поменять в инвойсе, чтобы принимать Telegram Stars?

- ✅ Поставить Currency XTR и оставить provider_token пустой строкой
- Добавить поле Stars: true в InvoiceConfig
- Заменить bot.Send на специальный метод SendStars
- Указать сумму в центах, звёзды конвертируются автоматически

Инвойс в звёздах отличается от фиатного двумя полями: валюта XTR и пустой provider_token — списание делает сам Telegram. Суммы в Prices указываются целыми звёздами, а остальная схема — pre-checkout, successful_payment, возврат refundStarPayment — идентична провайдерской.

### Проверка: окно pre-checkout

**Вопрос:** Сколько времени у бота на ответ на pre_checkout_query?

- Не ограничено: Telegram ждёт столько, сколько нужно
- Одна минута
- ✅ Десять секунд, молчание трактуется как отказ
- Ровно до закрытия пользователем диалога оплаты

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

### Медленная проверка перед списанием

```go
case update.PreCheckoutQuery != nil:
        // хендлер проверяет наличие товара в базе
        // и на фоне деплоя база отвечает 15 секунд
        ok := tgbotapi.PreCheckoutQueryConfig{
                PreCheckoutQueryID: update.PreCheckoutQuery.ID,
                OK:                 true,
        }
        _, _ = bot.Request(ok)
```

**Что выведет программа?**

- Платёж пройдёт, ответ пришёл с опозданием — это нормально
- ✅ Telegram отменит оплату: десяти секундный лимит истёк
- Бот упадёт с таймаутом запроса
- Деньги спишутся, но successful_payment не придёт

Окно в десять секунд не продлевается, и опоздавший OK равен отказу: пользователь видит ошибку оплаты, деньги не списаны. Лечение структурное: лёгкая проверка из кэша перед списанием и вся тяжёлая логика — после successful_payment, где ограничений на время нет.

### Соберите платёжные ветки

Впишите поля апдейтов и подтверждение pre-checkout.

```go
case update.___ != nil: // запрос перед списанием
        _, _ = bot.Request(tgbotapi.PreCheckoutQueryConfig{
                OK: ___,
        })
case update.Message.___ != nil: // деньги пришли
        payload := update.Message.SuccessfulPayment.___
        grantPurchase(payload)
```

Подсказка: Запрос перед списанием лежит в update.PreCheckoutQuery, подтверждение — OK true, факт оплаты — SuccessfulPayment, а ваш id заказа вернётся в InvoicePayload.

Правильные ответы в пропусках: `PreCheckoutQuery`, `true`, `SuccessfulPayment`, `InvoicePayload`

### Соберите путь платежа

Расставьте строки: от инвойса до выдачи товара.

Правильный порядок строк:

1. `prices := []tgbotapi.LabeledPrice{tgbotapi.NewLabeledPrice("Подписка", 29900)}`
2. `invoice := tgbotapi.NewInvoice(chatID, title, desc, payload, token, "sub", "EUR", prices)`
3. `_, _ = bot.Send(invoice)`
4. `ok := tgbotapi.PreCheckoutQueryConfig{OK: true}`
5. `_, _ = bot.Request(ok)`
6. `payload := update.Message.SuccessfulPayment.InvoicePayload`

### Карточки: платежи

- **NewInvoice** — Конструктор InvoiceConfig: chatID, title до 32 символов, description, payload, provider_token, валюта и Prices. Отправляется обычным bot.Send.
- **LabeledPrice** — NewLabeledPrice(label, amount): сумма в минимальных единицах — копейки и центы для валют, целые звёзды для XTR.
- **payload** — Ваш идентификатор заказа до 128 байт: Telegram хранит его как есть и возвращает в successful_payment — по нему выдаётся товар.
- **pre_checkout_query** — Апдейт между нажатием «Оплатить» и списанием: десять секунд на ответ OK true/false, молчание — отказ.
- **successful_payment** — Факт оплаты в Message: InvoicePayload, TotalAmount, Currency и TelegramPaymentChargeId — ключ идемпотентности и возвратов.
- **XTR** — Код Telegram Stars в инвойсе: Currency XTR при пустом provider_token. Цифровые товары в ботах продаются через звёзды.

Карта собрана — дальше маршрут курса: [следующий урок про Mini Apps](/courses/telegram-bot/lessons/21-mini-apps-obzor/) — веб-интерфейс внутри Telegram, куда переезжает витрина для сложных товаров; [урок про ошибки и лимиты](/courses/telegram-bot/lessons/17-oshibki-i-limity/) — почему массовые инвойсы подчиняются тем же 429, что и рассылки; [урок про тесты бота](/courses/telegram-bot/lessons/23-testy-bota/) — как покрыть платёжные ветки без Telegram, сохранёнными апдейтами. Откуда платёжная механика растёт: цикл апдейтов и webhook — [восемнадцатый урок](/courses/telegram-bot/lessons/18-webhook-bot/), [деплой на VPS](/courses/telegram-bot/lessons/24-deploy-bota/) — обязательный шаг, прежде чем принимать деньги: платежи не прощают перезапусков без логов. Хранение статусов заказов в Redis разобрано в [уроке про хэши курса go-redis](/courses/go-redis/lessons/08-hashi/). Полный маршрут — [на странице курса](/courses/telegram-bot/).

## Коротко: что запомнить

- Платёж — четыре этапа и два апдейта: NewInvoice и Send, pre_checkout_query с ответом за десять секунд, successful_payment, выдача по payload.
- LabeledPrice считает в минимальных единицах: копейки и центы для валют, целые звёзды для XTR — перепутанная шкала дороже всего остального.
- Фиат идёт через провайдера: токен из BotFather /payments, тестовый режим для разработки, обвязка провайдера — отдельный проект.
- Telegram Stars: Currency XTR и пустой provider_token; цифровые товары в ботах продаются через звёзды, возврат — refundStarPayment.
- TelegramPaymentChargeId — ключ идемпотентности выдачи и возврата; без него повтор доставки апдейта выдаёт товар дважды.
- Урок обзорный: вебхуки провайдера, чеки, налоги и подписки через звёзды остались картой направлений, а не инструкцией.

## Домашнее задание

Соберите платёжный стенд: бот с командой /buy, которая отправляет инвойс в звёздах — Currency XTR, пустой provider_token, цена в одну-две звезды, payload вида test-order-1. Обработайте pre_checkout_query с лёгкой проверкой payload и successful_payment с записью charge_id в Redis-хэш. Оплатите инвойс сами себе и убедитесь, что повторная доставка successful_payment не выдаёт товар второй раз — добавьте идемпотентность по TelegramPaymentChargeId. Затем отправьте себе отказ: OK false с сообщением о снятом с продажи товаре.

## Вопросы и ответы по уроку

### Можно ли принимать рубли и доллары без провайдера?

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

### Чем Stars лучше провайдера для пет-проекта?

Меньше обвязки: нет договора, кабинета и вебхуков провайдера, инвойс отличается только валютой XTR и пустым токеном, а вся схема — pre-checkout и successful_payment — та же. Минус тоже честный: звёзды — внутренняя валюта, и вывод заработанного идёт через конвертацию, а пользователи должны сначала звёзды купить.

### Что делать, если pre-checkout ответил OK, а бот упал до выдачи товара?

Платёж состоится, successful_payment придёт — возможно, с повторами доставки при webhook. Отсюда идемпотентность: выдача товара привязана к payload, а факт выдачи записывается с charge_id; обработчик, увидев знакомый charge_id, просто подтверждает обработку без повторной выдачи. Деньги не теряются, если выдача вынесена из опасной зоны и основана на апдейте, а не на памяти процесса.
