> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bedolagam.ru/llms.txt
> Use this file to discover all available pages before exploring further.

# Купоны на подписку

> Партии одноразовых купонов для оптовых продаж, раздач и конкурсов

## Обзор

Купон — одноразовая ссылка, которая выдаёт подписку по конкретному тарифу на конкретный срок. Купоны создаются **партиями**: администратор генерирует N ссылок, передаёт их партнёру или разыгрывает, а расчёт с партнёром идёт вне бота.

Подсистема появилась в `v3.63.0`, лимит активаций на пользователя и удаление партий — в `v3.67.0`.

> [!NOTE]
>
> Купон — не промокод. Промокод даёт скидку, баланс, дни или трафик к существующей покупке и может быть многоразовым. Купон выдаёт подписку целиком, одноразовый и живёт внутри партии. См. [Промокоды и промопредложения](/bot/promo-system).

## Что получает пользователь

Активация купона повторяет семантику подарочной подписки:

* нет подписки — создаётся новая по тарифу партии на `period_days`;
* есть активная подписка — она **продлевается** на `period_days`;
* есть истёкшая подписка — она заменяется новой.

## Ссылка активации

Каждый купон — это deep link:

```
https://t.me/{bot_username}?start=coupon_{token}
```

Токен — 32 hex-символа (128 бит). Полная нагрузка (`coupon_` + токен = 39 символов) укладывается в лимит Telegram в 64 символа, поэтому ссылка всегда ищется точным совпадением и не обрезается.

## Параметры партии

| Параметр | Описание |
| - | - |
| `name` | Название партии — для себя, видно в списке |
| `tariff_id` | Тариф, который выдаёт купон |
| `period_days` | На сколько дней выдаётся или продлевается подписка |
| `coupons_total` | Сколько купонов сгенерировать |
| `wholesale_price_kopeks` | Оптовая цена за купон. Только бухгалтерия — деньги через бота не проходят |
| `max_per_user` | Сколько купонов **этой партии** может активировать один пользователь. `0` — без ограничения |
| `valid_until` | Дата, после которой купоны партии не активируются |

> [!TIP]
>
> Для раздач и конкурсов ставьте `max_per_user=1`. Купоны одноразовые, но их в партии много — без лимита один человек может забрать всю партию.

## Управление через кабинет

| Метод | Эндпоинт | Действие |
| - | - | - |
| `GET` | `/admin/coupons` | Список партий |
| `POST` | `/admin/coupons` | Создать партию и сгенерировать купоны |
| `GET` | `/admin/coupons/{batch_id}` | Карточка партии |
| `GET` | `/admin/coupons/{batch_id}/links` | Экспорт ссылок партии |
| `POST` | `/admin/coupons/{batch_id}/revoke` | Отозвать неиспользованные купоны партии |
| `DELETE` | `/admin/coupons/{batch_id}` | Удалить партию |

Партии также создаются и просматриваются из админ-панели бота.

## Статусы купона

| Статус | Значение |
| - | - |
| `active` | Не активирован, готов к использованию |
| `redeemed` | Активирован; в записи сохраняются пользователь и время |
| `revoked` | Отозван администратором |

> [!NOTE]
>
> Флаг `is_revoked` у партии — это кэш для списка. Право на активацию всегда определяется статусом конкретного купона, а не флагом партии.

## Отказы при активации

| Код | Что видит пользователь |
| - | - |
| `invalid` | Купон не найден, отозван или уже активирован кем-то другим |
| `expired` | Срок действия партии истёк |
| `already_redeemed_by_you` | Этот купон пользователь уже активировал сам |
| `per_user_limit` | Лимит `max_per_user` по этой партии исчерпан |
| `internal` | Внутренняя ошибка |

> [!WARNING]
>
> «Не найден», «отозван» и «активирован другим» намеренно отвечают одним и тем же `invalid`. Иначе перебор ссылок превращался бы в оракул, по которому можно проверять существование токенов.

## Гарантии

> **Один купон — одна активация**
>
> Списание идёт под `SELECT ... FOR UPDATE` с повторной проверкой под блокировкой. Два одновременных перехода по одной ссылке не выдадут две подписки.

> **Отклонённая ссылка не держит блокировку**
>
> Вся валидация выполняется на чтении без блокировки — неудачный переход не тормозит остальные активации.

> **Деньги через бота не проходят**
>
> `wholesale_price_kopeks` — только учёт. Расчёт с партнёром идёт вне бота, поэтому купоны не участвуют в статистике продаж как платежи.

## Связанные разделы

> **[Промокоды](/bot/promo-system)**
>
> Скидки, баланс, дни и трафик

> **[Подарочные подписки](/cabinet/gift-subscriptions)**
>
> Похожий сценарий выдачи подписки по ссылке


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.