> ## 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.

# Рекуррентные платежи

> Автопродление подписок: сохранённые карты, автоматическое списание и управление

## Обзор

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

1. **Автопродление (autopay)** — автоматическое списание с баланса при приближении окончания подписки
2. **Сохранённые карты** — автоматическое пополнение баланса через YooKassa, если средств на балансе недостаточно
3. **Провайдерские подписки** — [СБП-подписки Platega](#сбп-подписки-platega) (`v3.66.0`) и [подписки Lava](#подписки-lava) (`v3.67.0`): деньги списывает сам провайдер, баланс бота не участвует

> [!WARNING]
>
> Провайдерские подписки и автооплата с баланса **взаимоисключающи** для одной подписки: включение одной модели выключает другую. Platega и Lava также исключают друг друга.

```
Подписка истекает через N дней
        ↓
Баланс достаточен?
   ├─ Да → Списание с баланса → Продление подписки
   └─ Нет → Есть сохранённая карта?
              ├─ Да → Списание с карты → Пополнение баланса → Продление
              └─ Нет → Уведомление «Недостаточно средств»
```

> [!WARNING]
>
> Для полноценной работы необходимо включить **оба** компонента:
>
> * `ENABLE_AUTOPAY=true` — автопродление с баланса
> * `YOOKASSA_RECURRENT_ENABLED=true` — сохранение и использование карт
>
> Без `ENABLE_AUTOPAY` сохранённые карты не будут использоваться автоматически. Без `YOOKASSA_RECURRENT_ENABLED` продление возможно только при достаточном балансе.

***

## Автопродление (с баланса)

### Как работает

Мониторинг-сервис ежедневно проверяет подписки с включённым автопродлением:

1. Находит подписки, где `autopay_enabled=true` и до окончания осталось ≤ `autopay_days_before` дней
2. Рассчитывает стоимость продления (с учётом промо-скидок)
3. Если баланс достаточен — списывает и продлевает подписку
4. Если баланс недостаточен — пытается списать с сохранённой карты (при наличии)
5. Отправляет уведомление о результате

> [!NOTE]
>
> Пробные (trial) и суточные подписки исключены из автопродления. Также обрабатываются подписки, истекшие не более 48 часов назад.

### Настройка

```env theme={null}
ENABLE_AUTOPAY=true
DEFAULT_AUTOPAY_ENABLED=true
DEFAULT_AUTOPAY_DAYS_BEFORE=3
MIN_BALANCE_FOR_AUTOPAY_KOPEKS=10000
```

| Переменная | Тип | По умолчанию | Описание |
| - | - | - | - |
| `ENABLE_AUTOPAY` | `bool` | `false` | Глобальное включение автопродления |
| `DEFAULT_AUTOPAY_ENABLED` | `bool` | `false` | Автопродление включено у новых пользователей по умолчанию |
| `DEFAULT_AUTOPAY_DAYS_BEFORE` | `int` | `3` | За сколько дней до окончания подписки запускать продление |
| `MIN_BALANCE_FOR_AUTOPAY_KOPEKS` | `int` | `10000` | Минимальный баланс для срабатывания (100 руб) |
| `SUBSCRIPTION_RENEWAL_BALANCE_THRESHOLD_KOPEKS` | `int` | `20000` | Порог баланса для автопродления (200 руб) |

### Интерфейс пользователя

#### В боте

Пользователь управляет автопродлением через меню подписки:

| Кнопка | Действие |
| - | - |
| Включить/Выключить | Переключение `autopay_enabled` |
| Настроить дни | Выбор за сколько дней до окончания списывать |

Отображается текущий статус: включено/выключено и количество дней.

#### В Cabinet

```
PATCH /cabinet/subscription/autopay
```

**Запрос:**

```json theme={null}
{
  "enabled": true,
  "days_before": 3
}
```

> [!NOTE]
>
> Автопродление недоступно для суточных тарифов. При попытке включить на суточном тарифе API вернёт ошибку.

***

## Сохранённые карты (YooKassa)

### Как сохраняются карты

При оплате через YooKassa карта сохраняется автоматически:

1. Пользователь создаёт платёж через YooKassa
2. Если `YOOKASSA_RECURRENT_REQUIRED=true` — карта сохраняется принудительно
3. Если `YOOKASSA_RECURRENT_REQUIRED=false` — YooKassa показывает чекбокс «Сохранить карту»
4. После успешной оплаты webhook возвращает `payment_method.saved=true`
5. Бот сохраняет данные карты в таблицу `saved_payment_methods`

```
Пользователь              YooKassa                    Бот
    │                        │                          │
    │  1. Оплата             │                          │
    │───────────────────────>│                          │
    │                        │                          │
    │  2. Чекбокс            │                          │
    │  «Сохранить карту»     │                          │
    │  (или принудительно)   │                          │
    │───────────────────────>│                          │
    │                        │                          │
    │                        │  3. Webhook              │
    │                        │  payment_method.saved     │
    │                        │──────────────────────────>│
    │                        │                          │  4. Сохранение
    │                        │                          │  в БД
    │                        │                          │
    │  5. Карта сохранена    │                          │
    │<──────────────────────────────────────────────────│
```

### Что сохраняется

| Поле | Описание |
| - | - |
| `yookassa_payment_method_id` | Токен YooKassa для повторных списаний |
| `method_type` | Тип: `bank_card`, `yoo_money`, `sberbank` и др. |
| `card_first6` | Первые 6 цифр (BIN) |
| `card_last4` | Последние 4 цифры |
| `card_type` | Платёжная система: Visa, MasterCard, Mir |
| `card_expiry_month/year` | Срок действия |
| `title` | Отображаемое имя, напр. «Visa \*4444» |

> [!NOTE]
>
> Дедупликация: если карта с таким `payment_method_id` уже существует, новая запись не создаётся. Если карта была ранее деактивирована — она реактивируется.

### Настройка

```env theme={null}
YOOKASSA_RECURRENT_ENABLED=true
YOOKASSA_RECURRENT_REQUIRED=false
```

| Переменная | Тип | По умолчанию | Описание |
| - | - | - | - |
| `YOOKASSA_RECURRENT_ENABLED` | `bool` | `false` | Включить сохранение карт и рекуррентные платежи |
| `YOOKASSA_RECURRENT_REQUIRED` | `bool` | `false` | Принудительно сохранять карту при каждой оплате |

### Управление картами (Cabinet API)

#### Список карт

```
GET /cabinet/balance/saved-cards
```

**Ответ:**

```json theme={null}
{
  "cards": [
    {
      "id": 42,
      "method_type": "bank_card",
      "card_last4": "4444",
      "card_type": "Visa",
      "title": "Visa *4444",
      "created_at": "2026-01-15T12:00:00Z"
    }
  ],
  "recurrent_enabled": true
}
```

#### Удаление карты

```
DELETE /cabinet/balance/saved-cards/{card_id}
```

Мягкое удаление — карта помечается как `is_active=false`. При повторной оплате той же картой она реактивируется автоматически.

***

## Автоматическое списание с карты

### Процесс

Если при автопродлении на балансе недостаточно средств и у пользователя есть сохранённые карты:

1. Рассчитывается сумма для продления подписки
2. Бот перебирает все активные карты пользователя
3. Для каждой карты создаётся платёж через YooKassa API (без подтверждения пользователя)
4. При успехе — баланс пополняется, подписка продлевается
5. При неудаче — пробуется следующая карта
6. Если все карты не сработали — отправляется уведомление

```
Мониторинг                 Recurrent Service          YooKassa
    │                           │                        │
    │  1. Баланс недостаточен   │                        │
    │──────────────────────────>│                        │
    │                           │                        │
    │                           │  2. Карта #1           │
    │                           │  create_autopayment()  │
    │                           │───────────────────────>│
    │                           │                        │
    │                           │  3. Результат          │
    │                           │<───────────────────────│
    │                           │                        │
    │                           │  [Если ошибка]         │
    │                           │  4. Карта #2...        │
    │                           │───────────────────────>│
    │                           │                        │
    │  5. Результат             │                        │
    │<──────────────────────────│                        │
```

> [!NOTE]
>
> Каждый платёж использует ключ идемпотентности, что предотвращает повторные списания при сетевых ошибках.

### Защита от дублирования

Сервис рекуррентных платежей выполняется не чаще одного раза в сутки (daily guard). Подписки, уже обработанные за текущий день, пропускаются.

***

## СБП-подписки Platega

Появились в `v3.66.0`. Пользователь один раз подтверждает подписку в приложении банка, дальше Platega сама списывает деньги по расписанию и присылает боту коллбек. Баланс бота в цепочке не участвует.

### Включение

```env theme={null}
PLATEGA_ENABLED=true
PLATEGA_RECURRENT_ENABLED=false   # общий гейт функции
```

> [!NOTE]
>
> `PLATEGA_RECURRENT_ENABLED` — глобальный выключатель. Пока он `false`, ни бот, ни кабинет не показывают оформление СБП-автооплаты, даже если сам Platega подключён.

### Где оформляется

* в боте — при покупке и продлении, как альтернатива оплате с баланса;
* на экране «недостаточно средств» при продлении (`v3.67.0`);
* в кабинете — `POST /subscription/platega-recurrent/enable` и `/purchase`.

### Каденс списаний

Platega умеет только интервалы day / week / month / year с шагом 1, поэтому период тарифа приводится к ближайшему:

| Период тарифа | Интервал | Шаг продления |
| - | - | - |
| Суточный тариф | `day` | 1 день |
| 7 дней | `week` | 7 дней |
| 28–31 день | `month` | Столько же дней |
| 350–380 дней | `year` | Столько же дней |
| Любой другой | `month` | 30 дней по 30-дневной цене |

### Статусы подписки

| Статус | Значение |
| - | - |
| `PENDING` | Создана, ждём подтверждения в банке |
| `ACTIVE` | Работает, списания идут |
| `PAST_DUE` | Списание не прошло, провайдер повторяет попытки |
| `CANCELLED` | Отменена пользователем или администратором |
| `FAILED` | Не состоялась (в том числе зависший `PENDING` старше 30 минут) |

### Управление и сверка

* **Пользователь** — `GET /subscription/platega-recurrent` (статус) и `POST /subscription/platega-recurrent/cancel`.
* **Администратор** — статус и отмена СБП-автооплаты в карточке пользователя в кабинете.
* **События** — кабинет получает `sbp_recurring.*` по вебсокету.
* **Реконсилятор** — в цикле мониторинга сверяет локальные записи со статусом у Platega. Это страховка от потерянных коллбеков: пропавшая у провайдера подписка хоронится, а недоступность Platega откладывает решение до следующего цикла, а не помечает подписку неудачной.

> [!NOTE]
>
> Автооплата отменяется автоматически при удалении или отзыве подписки — «осиротевших» списаний не остаётся. Каждый коллбек идемпотентен по идентификатору списания, поэтому повтор от провайдера не продлевает подписку дважды.

## Подписки Lava

Появились в `v3.67.0` и устроены по образцу Platega, но с одним принципиальным отличием.

```env theme={null}
LAVA_ENABLED=true
LAVA_RECURRENT_ENABLED=false
```

> [!WARNING]
>
> Подписка Lava оформляется на **продукт из личного кабинета Lava**, а не на произвольную сумму: цена и период заданы на стороне Lava. Поэтому тарифу в боте нужно проставить `lava_product_id` — без него рекуррент по этому тарифу не предлагается.

Эндпоинты кабинета зеркалят платеговские:

| Метод | Эндпоинт | Действие |
| - | - | - |
| `POST` | `/subscription/lava-recurrent/enable` | Включить автопродление действующей подписки |
| `POST` | `/subscription/lava-recurrent/purchase` | Покупка сразу с привязкой |
| `GET` | `/subscription/lava-recurrent` | Текущий статус |
| `POST` | `/subscription/lava-recurrent/cancel` | Отменить |

> [!NOTE]
>
> При ручном продлении подписки дата следующего списания сдвигается — двойной оплаты за один и тот же период не будет.
>
> Цена автопродления учитывает докупленные устройства и трафик, а не только базовую стоимость тарифа (`v3.67.0`).

## Уведомления

### Предупреждения об окончании подписки

Настраиваются через `AUTOPAY_WARNING_DAYS` (по умолчанию `3,1` — за 3 дня и за 1 день):

| Условие | Сообщение |
| - | - |
| Автопродление вкл. + карта сохранена | «Автоматическое списание с карты» |
| Автопродление вкл. + нет карты | «Подписка продлится автоматически» (с баланса) |
| Автопродление выкл. | «Не забудьте продлить вручную!» |

### Результат автопродления

| Событие | Описание |
| - | - |
| Успешное продление с баланса | «Подписка продлена на N дней за X руб» |
| Успешное списание с карты | «Автоплатёж выполнен, баланс пополнен на X руб» |
| Недостаточно средств (нет карты) | «Не удалось списать X руб, баланс: Y руб» + кнопки |
| Все карты не сработали | «Не удалось списать ни с одной сохранённой карты» |

> [!NOTE]
>
> Уведомление о недостаточном балансе отправляется с cooldown 24 часа — повторное уведомление в течение суток не приходит.

***

## Администрирование

### Управление автопродлением пользователя

```
POST /cabinet/admin/users/{user_id}/subscription-action
```

```json theme={null}
{
  "action": "toggle_autopay",
  "autopay_enabled": true
}
```

### Просмотр статистики

Сервис рекуррентных платежей возвращает статистику после каждого запуска:

| Метрика | Описание |
| - | - |
| `checked` | Подписок проверено |
| `payments_created` | Успешных автоплатежей |
| `insufficient_no_card` | Недостаточно средств, нет карты |
| `all_cards_failed` | Все карты не сработали |
| `already_processed` | Уже обработано сегодня |
| `errors` | Ошибки при обработке |

***

## Полная настройка

### Минимальная конфигурация

```env theme={null}
# Автопродление с баланса
ENABLE_AUTOPAY=true

# Рекуррентные платежи через YooKassa
YOOKASSA_ENABLED=true
YOOKASSA_SHOP_ID=123456
YOOKASSA_SECRET_KEY=live_xxxxxxxxxxxxx
YOOKASSA_RECURRENT_ENABLED=true
```

### Расширенная конфигурация

```env theme={null}
# Автопродление
ENABLE_AUTOPAY=true
DEFAULT_AUTOPAY_ENABLED=true
DEFAULT_AUTOPAY_DAYS_BEFORE=3
MIN_BALANCE_FOR_AUTOPAY_KOPEKS=10000
SUBSCRIPTION_RENEWAL_BALANCE_THRESHOLD_KOPEKS=20000
AUTOPAY_WARNING_DAYS=3,1

# Рекуррентные платежи
YOOKASSA_RECURRENT_ENABLED=true
YOOKASSA_RECURRENT_REQUIRED=false
```

| Переменная | Тип | По умолчанию | Описание |
| - | - | - | - |
| `ENABLE_AUTOPAY` | `bool` | `false` | Глобальное включение автопродления |
| `DEFAULT_AUTOPAY_ENABLED` | `bool` | `false` | Автопродление для новых пользователей |
| `DEFAULT_AUTOPAY_DAYS_BEFORE` | `int` | `3` | За сколько дней до окончания списывать |
| `MIN_BALANCE_FOR_AUTOPAY_KOPEKS` | `int` | `10000` | Минимальный баланс для срабатывания (100 руб) |
| `SUBSCRIPTION_RENEWAL_BALANCE_THRESHOLD_KOPEKS` | `int` | `20000` | Порог баланса для автопродления (200 руб) |
| `AUTOPAY_WARNING_DAYS` | `str` | `3,1` | Дни для предупреждений (через запятую) |
| `YOOKASSA_RECURRENT_ENABLED` | `bool` | `false` | Включить сохранение карт |
| `YOOKASSA_RECURRENT_REQUIRED` | `bool` | `false` | Принудительное сохранение карт |

***

## Устранение проблем

### Автопродление не срабатывает

1. `ENABLE_AUTOPAY=true`?
2. У пользователя включено автопродление? Проверьте `autopay_enabled` на подписке
3. Подписка не trial и не суточная?
4. Баланс достаточен или есть сохранённая карта?
5. Проверьте логи:
   ```bash theme={null}
   docker logs <bot_container> 2>&1 | grep -i "autopay\|recurrent"
   ```

### Карты не сохраняются

1. `YOOKASSA_RECURRENT_ENABLED=true`?
2. Пользователь дал согласие на сохранение (или `YOOKASSA_RECURRENT_REQUIRED=true`)?
3. Webhook от YooKassa приходит? Проверьте:
   ```bash theme={null}
   docker logs <bot_container> 2>&1 | grep -i "payment_method.*saved"
   ```

### Списание с карты не проходит

1. Срок действия карты не истёк?
2. На карте достаточно средств?
3. Банк не блокирует рекуррентные платежи?
4. Проверьте ответ YooKassa в логах:
   ```bash theme={null}
   docker logs <bot_container> 2>&1 | grep -i "recurrent\|autopayment"
   ```

### Пользователь получает повторные уведомления

Cooldown на уведомления о недостаточном балансе — 24 часа. Если Redis недоступен, cooldown не работает и уведомления могут дублироваться. Проверьте подключение к Redis.


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