Перейти к содержимому

Тарифы

Бот поддерживает два способа описания продаж:

  • JSON-каталог тарифов из TARIFFS_CONFIG_PATH (по умолчанию data/tariffs.json);
  • конфигурация через переменные .env, если JSON-файл отсутствует.

JSON-каталог может содержать несколько тарифов разных моделей: подписки на срок, пакеты трафика без срока действия, разные наборы Internal Squads, лимиты устройств и пакеты докупки. Пример формата: data/tariffs.example.json.

Коротко по моделям:

  • period - подписка на срок с месячным лимитом трафика и опциональной докупкой GB поверх месячного лимита;
  • traffic - покупка пакетов GB без пользовательского срока действия, где повторная покупка добавляет трафик к текущему остатку;
  • premium-сквады - дополнительный набор Internal Squads внутри любого тарифа, с отдельным названием, счетчиком, месячным лимитом и отдельными пакетами докупки.

Каталог тарифов можно настраивать из Web App админки: раздел Система → Тарифы. Админка читает и сохраняет файл из TARIFFS_CONFIG_PATH, валидирует данные той же моделью TariffsConfig, что и бот, и атомарно перезаписывает JSON только после успешной проверки.

В интерфейсе доступны:

  • добавление, редактирование и удаление тарифов;
  • включение и выключение тарифа на витрине;
  • выбор тарифа по умолчанию;
  • настройка тарифов на срок (period): месячный лимит, периоды, RUB/Stars цены, пакеты докупки трафика;
  • настройка тарифов по трафику (traffic): пакеты GB, RUB/Stars цены, курс конвертации;
  • настройка базовых Internal Squads из списка Remnawave;
  • настройка premium-раздела: названия RU/EN, premium Internal Squads, месячный premium-лимит и RUB/Stars пакеты докупки premium-трафика;
  • настройка базового HWID-лимита и пакетов докупки устройств.

После сохранения изменения применяются к новым запросам Web App сразу, потому что конфиг тарифов загружается из JSON при обращении. Уже созданные подписки сохраняют свой tariff_key; при удалении или отключении тарифа проверьте, что активные подписки с этим ключом не требуют дальнейшего продления или смены.

Подробности по админ-панели, правам доступа, сохранению настроек и списку разделов есть в админ-панели.

Если файл из TARIFFS_CONFIG_PATH существует и проходит валидацию, используется каталог тарифов. В этом режиме TRAFFIC_PACKAGES и цены подписок из .env не формируют витрину продаж, потому что цены и пакеты берутся из JSON.

Если JSON-файл отсутствует, бот использует значения .env:

  • RUB_PRICE_*, STARS_PRICE_* и *_MONTHS_ENABLED для подписок на срок;
  • TRAFFIC_PACKAGES и STARS_TRAFFIC_PACKAGES для продажи пакетов трафика;
  • USER_TRAFFIC_LIMIT_GB, USER_TRAFFIC_STRATEGY, USER_SQUAD_UUIDS, USER_HWID_DEVICE_LIMIT для пользователей Remnawave.

В режиме без JSON-каталога наличие TRAFFIC_PACKAGES или STARS_TRAFFIC_PACKAGES переключает витрину на продажу трафика вместо подписок на срок.

Минимальная структура:

{
  "default_tariff": "standard",
  "tariffs": [
    {
      "key": "standard",
      "names": { "ru": "Стандарт", "en": "Standard" },
      "descriptions": { "ru": "Базовый набор серверов" },
      "premium_names": { "ru": "Premium-серверы", "en": "Premium servers" },
      "squad_uuids": ["uuid-1"],
      "billing_model": "period",
      "monthly_gb": 500,
      "prices_rub": { "1": 150, "3": 400 },
      "enabled_periods": [1, 3],
      "topup_packages": {
        "rub": [{ "gb": 10, "price": 99 }],
        "stars": [{ "gb": 10, "price": 2500 }]
      },
      "hwid_device_packages": {
        "rub": [
          {
            "count": 1,
            "price": 99,
            "prices": { "1": 99, "3": 249 },
            "min_price": 20
          }
        ],
        "stars": [{ "count": 1, "price": 50, "prices": { "1": 50, "3": 130 } }]
      },
      "enabled": true
    }
  ]
}

Основные поля:

ПолеНазначение
default_tariffТариф по умолчанию для первичного выбора и привязки активных подписок без tariff_key.
tariffs[].keyСтабильный ключ тарифа. Используется в платежах, подписках и смене тарифа.
tariffs[].namesНазвания тарифа по языкам.
tariffs[].descriptionsОписания тарифа по языкам.
tariffs[].enabledДоступность тарифа на витрине.
tariffs[].squad_uuidsInternal Squads Remnawave для пользователей тарифа.
tariffs[].premium_namesНазвание premium-раздела по языкам. Используется в карточке лимита, модалке докупки premium-трафика и предупреждениях. Если поле не задано, используется Premium-серверы / Premium servers.
tariffs[].premium_squad_uuidsInternal Squads с отдельным premium-лимитом. Ноды для учета берутся автоматически из accessible nodes этих сквадов через API панели.
tariffs[].premium_monthly_gbОтдельный месячный лимит трафика по premium-сквадам. 0 или отсутствие поля отключает отдельное ограничение.
tariffs[].premium_topup_packagesПакеты докупки premium-трафика в формате { "rub": [{ "gb": 10, "price": 99 }], "stars": [...] }. Требуют premium_squad_uuids.
tariffs[].billing_modelМодель тарифа: period или traffic.
tariffs[].hwid_device_limitБазовый лимит HWID-устройств. 0 означает безлимит, отсутствие поля использует USER_HWID_DEVICE_LIMIT.
tariffs[].hwid_device_packagesПакеты докупки устройств. price — legacy/monthly fallback, prices задаёт полную цену пакета для периодов тарифа ("1", "3", "6", "12"), min_price задаёт минимальную цену prorate-докупки.

Для period-тарифа также используются:

ПолеНазначение
monthly_gbБазовый месячный лимит трафика тарифа. 0 означает безлимит.
prices_rubЦены периодов в рублях, ключ - количество месяцев.
prices_starsЦены периодов в Telegram Stars.
enabled_periodsПериоды, доступные для покупки.
topup_packagesПакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте.

Для traffic-тарифа используются:

ПолеНазначение
traffic_packagesПакеты трафика в GB для рублей и Telegram Stars.
conversion_rate_rub_per_gbКурс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф.

Если у traffic-тарифа нет RUB-пакетов, conversion_rate_rub_per_gb обязателен.

period продает доступ на срок с месячным лимитом трафика.

При покупке или продлении:

  • дата начала берется от текущей активной подписки, если она еще действует, иначе от текущего времени;
  • срок считается календарными месяцами через add_months;
  • промокод может добавить бонусные дни к рассчитанному сроку;
  • tier_baseline_bytes получает значение monthly_gb;
  • topup_balance_bytes сохраняется из текущей активной подписки;
  • traffic_limit_bytes становится tier_baseline_bytes + topup_balance_bytes;
  • в Remnawave отправляется trafficLimitStrategy = MONTH;
  • в Remnawave отправляются Internal Squads из тарифа;
  • в Remnawave отправляется эффективный HWID-лимит тарифа.

MONTH означает, что сброс использованного трафика выполняет Remnawave. Бот не рассчитывает дату сброса самостоятельно и не хранит отдельный период сброса для period-тарифов.

Докупка трафика для period-тарифа увеличивает topup_balance_bytes и общий traffic_limit_bytes. Этот баланс сохраняется в подписке и учитывается при продлении period-тарифа. В панель отправляется актуальный лимит, а доступ переводится в ACTIVE.

Тариф может включать дополнительный набор Internal Squads с отдельным лимитом трафика. Это удобно для сценария “обычные серверы без изменений, premium-серверы ограничены отдельно”.

{
  "squad_uuids": ["standard-squad-uuid"],
  "premium_names": { "ru": "Premium-серверы", "en": "Premium servers" },
  "premium_squad_uuids": ["premium-squad-uuid"],
  "premium_monthly_gb": 50,
  "premium_topup_packages": {
    "rub": [{ "gb": 10, "price": 99 }],
    "stars": [{ "gb": 10, "price": 2500 }]
  }
}

Правила:

  • обычный лимит тарифа продолжает работать через trafficLimitBytes Remnawave;
  • premium-трафик считается отдельно по нодам, доступным из premium_squad_uuids;
  • список UUID нод не хранится в тарифе: бот запрашивает accessible nodes каждого premium-сквада у Remnawave и кеширует результат;
  • пока premium-лимит не исчерпан, пользователь получает squad_uuids + premium_squad_uuids;
  • при исчерпании premium-лимита бот убирает только premium-сквады, обычный доступ остается;
  • после докупки premium-трафика бот возвращает premium-сквады, если новый лимит снова больше использованного premium-трафика.
  • докупленный premium-трафик не сгорает в конце месяца: каждый месяц сначала расходуется premium_monthly_gb, а докупленный остаток уменьшается только на трафик сверх месячного лимита;
  • при новом календарном месяце счетчик premium-трафика и premium_topup_used_bytes сбрасываются, но premium_topup_balance_bytes переносится дальше.

Если premium_squad_uuids заданы, но premium_monthly_gb пустой или 0 и нет premium_topup_packages, premium-сквады работают как дополнительный доступ без отдельного ограничения. Если заданы premium_topup_packages или положительный premium_monthly_gb, premium_squad_uuids обязательны.

Состояние хранится в подписке:

  • premium_baseline_bytes - базовый premium-лимит тарифа;
  • premium_topup_balance_bytes - оставшийся докупленный premium-трафик;
  • premium_topup_used_bytes - часть докупленного premium-трафика, уже потраченная в текущем месяце;
  • premium_used_bytes - использованный premium-трафик за текущий календарный месяц;
  • premium_period_start_at - месяц, к которому относится premium_used_bytes;
  • premium_is_limited - признак, что premium-сквад временно снят.

В пользовательском Web App premium-лимит показывается отдельной карточкой: использовано, лимит, остаток, докупленный переносимый остаток и список серверов/сквадов, на которые действует отдельное ограничение. В Telegram-разделе “Моя подписка” выводится тот же блок.

Обычная докупка и premium-докупка показываются отдельно. Обычная докупка использует topup_packages у period-тарифа или traffic_packages у traffic-тарифа. Premium-докупка использует только premium_topup_packages, получает sale_mode=premium_topup и в заголовке показывает premium_names, а не название обычной докупки.

Предупреждения по premium-лимиту отправляются отдельно от обычного трафика на тех же процентах TARIFF_TRAFFIC_WARNING_LEVELS. Сообщение использует название из premium_names, перечисляет серверы/сквады и ведет пользователя в докупку premium-трафика.

В Web App админке premium-сквады можно выбрать из выпадающего списка на вкладке Premium в редакторе тарифа. Список берется из API Remnawave (/api/admin/panel/internal-squads), поэтому UUID обычно не нужно копировать вручную.

traffic продает объем трафика без пользовательского срока действия.

При покупке:

  • end_date ставится в дальнюю дату 2099-01-01 UTC, если у активной подписки нет более поздней даты;
  • duration_months = 0;
  • period_start_at = NULL;
  • tier_baseline_bytes = 0;
  • topup_balance_bytes хранит доступный остаток трафика;
  • в Remnawave отправляется trafficLimitStrategy = NO_RESET;
  • автопродление и уведомления о скором окончании срока отключаются для такой подписки.

Очередная покупка добавляет GB к фактическому остатку:

remaining = max(0, current_limit - current_used)
balance_after = remaining + purchased
limit_after = current_used + balance_after

Так пользователь не теряет уже оплаченный остаток, а Remnawave продолжает считать общий лимит от текущего использованного трафика.

Если докупка трафика вызывается для traffic-тарифа, она обрабатывается как покупка очередного пакета этого же traffic-тарифа.

Тариф может задавать базовый лимит устройств и пакеты докупки:

{
  "hwid_device_limit": 5,
  "hwid_device_packages": {
    "rub": [
      {
        "count": 1,
        "price": 99,
        "prices": { "1": 99, "3": 249, "6": 449, "12": 799 },
        "min_price": 20
      }
    ],
    "stars": [{ "count": 1, "price": 50, "prices": { "1": 50, "3": 130 } }]
  }
}

Правила:

  • hwid_device_limit хранит базовый лимит тарифа;
  • extra_hwid_devices хранит только текущую активную сумму докупленных устройств;
  • срок действия каждой докупки хранится в hwid_device_purchases.valid_from / valid_until;
  • эффективный лимит равен hwid_device_limit + active extra_hwid_devices;
  • базовый лимит 0 означает безлимит, в Remnawave отправляется hwidDeviceLimit = 0;
  • при безлимитном базовом лимите докупка устройств не применяется;
  • полная цена HWID-пакета берется из prices[duration_months]; если периода нет, используется fallback price * duration_months;
  • фактическая цена докупки считается пропорционально оплачиваемому окну valid_from -> valid_until относительно периода подписки и фиксируется в платежe;
  • для Telegram Stars цена округляется вверх до целого Stars, для RUB — вверх до копеек; min_price защищает от микроплатежей в конце периода;
  • при продлении подписки докупленные устройства не продлеваются автоматически: старая докупка действует до прежнего end_date, а для нового срока создается отдельная hwid_devices_renewal-покупка;
  • traffic-тарифы не показывают и не принимают докупку HWID-устройств, потому что у них нет срока подписки;
  • при смене тарифа базовый лимит берется из целевого тарифа, а неиспользованная RUB-стоимость HWID-докупок конвертируется в дни нового period-тарифа или GB traffic-тарифа; XTR/Stars-докупки не конвертируются без явного курса и продолжают жить по своему valid_until;
  • история докупок пишется в hwid_device_purchases;
  • платеж хранит количество устройств в payments.purchased_hwid_devices.

Докупка устройств доступна в Web App через /api/devices/topup-options и /api/payments, а также в Telegram-боте из раздела устройств.

Смена тарифа доступна для активных подписок с tariff_key и записывается в таблицу tariff_changes.

Варианты расчета:

ПереходПоведение
period -> periodОстаток оплаченных дней оценивается по effective_monthly_price_rub, затем пересчитывается в дни целевого тарифа через месячную цену целевого тарифа. Неиспользованная RUB-стоимость HWID-докупок добавляется к этому расчету как дополнительные дни. Количество дней округляется вниз.
period -> period с доплатойЕсли целевой тариф дороже, может быть создан платеж tariff_upgrade; неиспользованная RUB-стоимость HWID-докупок уменьшает сумму доплаты. После оплаты применяется целевой тариф, а конвертированные HWID-окна закрываются.
period -> trafficОстаток оплаченных дней и неиспользованная RUB-стоимость HWID-докупок конвертируются в GB по conversion_rate_rub_per_gb или минимальной RUB-цене GB из пакетов целевого тарифа.
traffic -> periodПользователь выбирает и оплачивает период целевого тарифа; остаток GB сохраняется как topup_balance_bytes поверх лимита period-тарифа.

При смене тарифа бот меняет:

  • tariff_key;
  • Internal Squads в Remnawave;
  • trafficLimitBytes;
  • trafficLimitStrategy;
  • базовый HWID-лимит;
  • effective_monthly_price_rub для period-тарифов;
  • auto_renew_enabled и уведомления для traffic-тарифов.

В платежах используются поля:

ПолеНазначение
sale_modeТип продажи: subscription, traffic_package, topup, premium_topup, tariff_upgrade, hwid_devices.
tariff_keyКлюч тарифа, к которому относится платеж.
purchased_gbКупленный объем GB для traffic-пакетов и докупки трафика.
purchased_hwid_devicesКоличество устройств при докупке HWID.
hwid_valid_from, hwid_valid_untilЗафиксированное окно действия HWID-докупки на момент создания платежа.
hwid_pricing_period_months, hwid_proration_ratio, hwid_full_priceМетаданные расчета цены HWID-докупки: период тарифа, коэффициент prorate и полная цена пакета для периода.
subscription_duration_monthsКоличество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки.

В callback и metadata платежных провайдеров sale_mode может передаваться с суффиксом тарифа, например subscription@standard или topup@standard. При активации платежа тариф сохраняется отдельно в tariff_key.

Remnawave ограничивает доступ при достижении trafficLimitBytes, переводя пользователя в статус LIMITED. Бот не удаляет пользователя из Internal Squads при 100% использования трафика.

TariffTrafficWorker запускается, когда активен JSON-каталог тарифов. Раз в 300 секунд он:

  • синхронизирует из панели status, trafficLimitBytes, usedTrafficBytes и trafficLimitStrategy;
  • для period-тарифов выставляет trafficLimitStrategy = MONTH, если панель еще показывает другую стратегию;
  • отправляет предупреждения на уровнях из TARIFF_TRAFFIC_WARNING_LEVELS (по умолчанию 85,90,95);
  • не отправляет status=ACTIVE при простой синхронизации стратегии, чтобы не снять статус LIMITED, выставленный Remnawave;
  • дедуплицирует предупреждения через traffic_warnings.

Для period-тарифов дедупликация предупреждений привязана к началу текущего месяца. Для traffic-тарифов она учитывает текущий trafficLimitBytes, чтобы после покупки очередного пакета пользователь мог получить следующий набор предупреждений.

Подписки, которые были ограничены логикой предыдущих запусков бота (is_throttled=True), восстанавливаются воркером только когда лимит снова больше использованного трафика.

Автопродление через YooKassa применяется к подпискам на срок. Для режима продажи трафика без JSON-каталога автопродление пропускается. Для traffic-тарифов JSON-каталога покупка является пакетом трафика, а не периодической подпиской.

Пробный период использует настройки TRIAL_DURATION_DAYS, TRIAL_TRAFFIC_LIMIT_GB, TRIAL_TRAFFIC_STRATEGY и TRIAL_SQUAD_UUIDS. Он не выбирает тариф из JSON-каталога, но его можно настроить на странице Система → Тарифы рядом с каталогом продаж. Если TRIAL_SQUAD_UUIDS пустой, для trial применяются squads из USER_SQUAD_UUIDS.

Промокоды с бонусными днями применяются к покупке period-подписки. Реферальные бонусы по периодам также относятся к подпискам на срок; в режиме продажи трафика без JSON-каталога Web App не показывает детализацию бонусов по месяцам.

При запуске с активным JSON-каталогом бот заполняет активные подписки без tariff_key:

  • tariff_key получает default_tariff;
  • tier_baseline_bytes берется из текущего лимита подписки или из monthly_gb тарифа по умолчанию;
  • topup_balance_bytes становится 0, если значение отсутствовало;
  • period_start_at очищается;
  • effective_monthly_price_rub берется из последнего успешного платежа или из цены тарифа по умолчанию.

Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов.