VPN·HUB·CRACK
VPN для отпуска: «заморозка» подписки и доступ к РФ-сервисам из-за границы · VPN HUB CRACK
VPN для отпуска: «заморозка» подписки и доступ к РФ-сервисам из-за границы
VPN для отпуска: заморозка + доступ к РФ-сервисам
Отпуск это два встречных запроса от одного клиента, и оба бьют по выручке, если их не закрыть:
- «Зачем платить, пока я не пользуюсь?» Человек улетел на 3 недели, VPN ему пока не нужен, а оплаченные дни тают. В голове клиента это «деньги на ветер»: пишет в поддержку, просит вернуть, ставит на паузу через отмену автоплатежа, и часто не возвращается.
- «Выключите VPN!», а он и так выключен. Тот же клиент за границей хочет зайти в российский банк / Госуслуги / Ozon / Mir Pay, а приложение ругается: «Вы используете VPN, отключите его». Он его выключает. Не помогает. Потому что дело не в VPN, а в том, что его IP принадлежит другой стране.
Закрой оба, и вместо оттока получишь лояльность и «сервис, который думает за меня». Ниже два приёма.
📖 Незнакомые слова? Все термины простыми словами есть в Словаре терминов.
Приём 1. «Заморозка» подписки: банк дней вместо оттока
Идея. Клиент нажимает «❄️ Заморозить», подписка встаёт на паузу, оплаченные дни не сгорают, а складываются в «банк». Вернулся из отпуска → «▶️ Разморозить» → дни продолжают отсчёт ровно с того места, где встали. Вместо «верните деньги, я не пользуюсь» вы получаете «ок, заморожу и вернусь».
Это дешевле возврата, убирает повод для спора и заметно снижает отток на сезонных просадках (лето, новогодние каникулы). Живой пример в проде: сервис T-VPN, там это сделано именно так.
Как это работает (механика T-VPN)
В самой панели Remnawave «заморозки» нет, приём собирается в вашем боте. Хранение и две операции:
# поля в строке подписки/ключа:
frozen # 0/1 — заморожена ли подписка
frozen_at # когда заморозили
banked_days # сколько дней "положили в банк" (остаток на момент заморозки)
last_freeze_at # метка для лимита "раз в 30 дней"
ЗАМОРОЗИТЬ (по кнопке клиента):
если frozen → "уже заморожена"
если (now - last_freeze_at) < 30 дн → "заморозка раз в месяц, подождите N дн."
days_left = expireAt - now
если days_left < 2 → "осталось меньше 2 дней — нельзя"
Remnawave PATCH /api/users {status: DISABLED} # доступ выкл; сквады/hwid/telegramId/трафик сохраняются
frozen = 1, frozen_at = now, banked_days = days_left, last_freeze_at = now
РАЗМОРОЗИТЬ (по кнопке клиента ИЛИ авто через 30 дней):
Remnawave PATCH /api/users {status: ACTIVE, expireAt: now + banked_days}
frozen = 0, frozen_at = NULL, expireAt = now + banked_days
Ключевой момент: пока подписка заморожена, expireAt не уменьшается: часы стоят. При разморозке срок пересчитывается как now + banked_days, поэтому ни один оплаченный день не теряется. Статус DISABLED просто отключает доступ, сохраняя сквады, лимит трафика, HWID (привязку подписки к «железу» устройства) и привязку Telegram, после разморозки клиент подключается тем же ключом, ничего не переоформляя.
Обязательные ограничения (иначе «заморозка» = бесплатная парковка)
- Раз в 30 дней. Без лимита клиент будет морозить/размораживать по дню, и жить вечно. Проверяем
last_freeze_at. - Остаток ≥ 2 дней. Морозить «хвостик» бессмысленно и плодит баги на границе срока.
- Авто-разморозка через 30 дней. Замороженный аккаунт не должен висеть вечно: шедулер сам размораживает по истечении 30 дней (
expireAt = now + banked_days) и шлёт уведомление: «❄️➡️☀️ Подписка автоматически разморожена: прошло 30 дней, срок продолжил отсчёт». Так вы не держите слот/ресурсы под «мёртвыми» паузами. - Не дёргать замороженных. Напоминания «подписка кончается» и автопродление в шедулере должны пропускать
frozen-ключи, иначе клиент в отпуске получает спам «продлите» по замороженной подписке.
Частичная заморозка: морозим не подписку, а конкретные устройства
Полная заморозка ломается на самом частом случае: одной подпиской пользуется семья. Владелец уехал за границу и VPN ему не нужен, а жена, родители и дети остались в России и продолжают им пользоваться. Нажал «заморозить», выключил интернет всей семье. Клиент такое не жмёт и просто уходит в отток, ради борьбы с которым заморозку и делали.
Лечится тем, что человек перед заморозкой выбирает устройства-исключения: их доступ остаётся живым, всё остальное морозится. Подписка при этом остаётся одна, никаких «купите второй ключ».
Тариф на исключения: по половине дня за устройство:
| Что выбрал | Списывается из банка |
|---|---|
| Полная заморозка, исключений нет | 0 дней за сутки |
| 1 устройство продолжает работать | 0,5 дня за сутки |
| 2 устройства продолжают работать | 1 день за сутки |
💡 Почему это работает само. На двух устройствах частичная заморозка стоит ровно столько же, сколько обычная подписка, и смысл в ней пропадает. То есть тариф сам разводит людей по честным сценариям: уехал один из двоих → платишь половину; пользуется вся семья → платишь как обычно; не нужно никому → морозишь целиком и не платишь вовсе. Никаких дополнительных запретов писать не нужно, экономика ограничивает сама.
Ставку выше 1 дня за сутки не берите. При трёх исключениях по формуле выходит 1,5 дня, то есть человек платил бы за паузу дороже, чем за работающую подписку. Ограничивайте сверху единицей: rate = min(1.0, 0.5 × devices).
Как это делается на Remnawave. Отдельной ручки «отключить одно устройство» в панели нет, но нужное поведение собирается из HWID-лимита:
- Запомнить HWID устройств-исключений (они уже есть в списке привязок пользователя).
- Удалить HWID-привязки остальных устройств.
- Выставить
hwidDeviceLimitравным числу исключений.
Дальше замороженные устройства при попытке подключиться не смогут зарегистрировать свой HWID (лимит занят исключениями), а выбранные продолжают работать без перерыва. На разморозке возвращаете прежний лимит, и все устройства спокойно привязываются заново.
⚠️ Грабли. Если оставить лимит с запасом, любое устройство просто займёт свободный слот, и «заморозка» окажется фикцией. Лимит должен быть ровно по числу исключений.
Что добавить в модель данных к обычной заморозке:
frozen_devices_json TEXT -- HWID устройств-исключений (JSON-массив)
frozen_daily_rate REAL -- сколько дней списывать за сутки: 0 / 0.5 / 1.0
hwid_limit_before INTEGER -- лимит до заморозки, чтобы вернуть на разморозке
Шедулер, который раз в сутки уменьшает банк, вместо «пропустить замороженного» делает banked_days -= frozen_daily_rate. Банк становится дробным: храните его как REAL, иначе половинки будут молча округляться в пользу клиента.
Идея частичной заморозки: Борис Яновский, 28.07.2026. Спасибо: случай с семейной подпиской в исходную механику не попадал.
🤖 Готовый промпт: вшей «Заморозку» в своего бота через нейросеть
Не хочешь кодить руками, отдай задачу Claude / ChatGPT. Ниже промпт со спецификацией, проверенный на живом проде T-VPN (remnawave-shopbot). Как пользоваться: скопируй блок «Задача» как инструкцию нейросети, а спецификацию приложи как контекст вместе с кодом своего бота. Стек: панель Remnawave + aiogram-бот + FastAPI мини-апп + SQLite.
1) Задача (вставь как инструкцию нейросети)
Ты — backend/full-stack инженер VPN-сервиса на базе панели Remnawave + Telegram-бота
(aiogram) с мини-аппом (FastAPI) и SQLite. Реализуй фичу «Заморозка подписки»:
клиент может поставить подписку на паузу (например, на время отпуска), чтобы
НЕ сгорали оплаченные дни. Пока подписка заморожена — трафик не идёт, а срок
действия стоит на месте. По разморозке (вручную или автоматически) оставшиеся
дни продолжают отсчёт с текущего момента.
В самой панели Remnawave «заморозки» НЕТ — это надстройка над ботом:
4 поля в БД + 2 функции работы с панелью + 3 функции БД + 2 HTTP-эндпоинта +
1 задача шедулера + кнопки в интерфейсе. Соблюди ВСЕ нюансы из спецификации —
именно они отличают рабочую фичу от той, что теряет клиентам оплаченные дни.
Делай минимально инвазивно, с проверками владельца/идемпотентности, и обязательно
исключи замороженные ключи из всех фоновых рассылок и автосписаний.
2) Нюансы (тут все грабли, соблюдать обязательно)
expireAtНЕ уменьшается при заморозке. Дни сохраняются именно потому, что приDISABLEDдоступа нет. Ошибка: пересчитыватьexpireAtв момент заморозки.- При разморозке пересчитывай
expireAt = now + banked_days. Нельзя вернутьACTIVEсо старымexpireAt, иначе клиент может потерять дни. Всегдаnow + банк. banked_daysэто снимок оставшихся дней на момент заморозки (целое число).- Лимит «раз в 30 дней» через
last_freeze_at. Ставится при заморозке и НЕ обнуляется при разморозке (иначе разморозился → тут же заморозился снова). Кулдаун считается отlast_freeze_at. - Минимум 2 дня остатка, чтобы разрешить заморозку.
- PATCH в Remnawave сохраняет служебные поля:
uuid,email,activeInternalSquads(массив uuid),hwidDeviceLimit,telegramId,trafficLimitBytes,trafficLimitStrategy, иначе панель сбросит сквады / лимит устройств / привязку Telegram / лимит трафика. - Замороженные ключи исключай из ВСЕХ фоновых процессов: напоминания «подписка кончается», автопродление с баланса, winback, grace-логика, везде ранний
if key.frozen: continue. - Авто-разморозка через 30 дней: шедулер находит
frozen=1 AND frozen_at <= now-30д, размораживает + уведомляет. Верхняя граница паузы. - Идемпотентность и владелец: ключ принадлежит юзеру; нельзя заморозить дважды; нельзя разморозить незамороженный.
- Часовые пояса: для Remnawave
expireAtэто UTC ISO (…Z); в локальной БД свой формат. Не перепутать. - Фронт перечитывает состояние (
loadUser) после freeze/unfreeze; бэкенд отдаёт в ключеfrozen,banked_days,days_left,last_freeze_at(+_msдля JS-кулдауна).
3) Модель данных (идемпотентные миграции)
| колонка | тип | назначение |
|---|---|---|
frozen |
INTEGER DEFAULT 0 | заморожена ли |
frozen_at |
TIMESTAMP | когда заморозили (для авто-разморозки) |
banked_days |
INTEGER DEFAULT 0 | «банк» дней |
last_freeze_at |
TIMESTAMP | последняя заморозка (лимит раз/30д) |
_ensure_table_column(cursor, "vpn_keys", "frozen", "INTEGER DEFAULT 0")
_ensure_table_column(cursor, "vpn_keys", "frozen_at", "TIMESTAMP")
_ensure_table_column(cursor, "vpn_keys", "banked_days", "INTEGER DEFAULT 0")
_ensure_table_column(cursor, "vpn_keys", "last_freeze_at", "TIMESTAMP")
4) Слой панели Remnawave: одна функция set_user_frozen
async def set_user_frozen(user_uuid, frozen, *, host_name=None, banked_days=None) -> bool:
"""DISABLED (заморозка) / ACTIVE + expireAt=now+banked (разморозка).
Сохраняет squads/email/hwid/telegramId/traffic."""
if not user_uuid:
return False
cur = await get_user_by_uuid(user_uuid, host_name=host_name) # GET текущего
if not cur:
return False
payload = {"uuid": cur.get("uuid"), "email": cur.get("email")}
sq = []
for s in (cur.get("activeInternalSquads") or []):
if isinstance(s, dict) and s.get("uuid"): sq.append(s["uuid"])
elif s: sq.append(s)
if sq: payload["activeInternalSquads"] = sq
if cur.get("hwidDeviceLimit") is not None: payload["hwidDeviceLimit"] = cur["hwidDeviceLimit"]
if cur.get("telegramId") is not None: payload["telegramId"] = cur["telegramId"]
if cur.get("trafficLimitBytes") is not None: payload["trafficLimitBytes"] = cur["trafficLimitBytes"]
if cur.get("trafficLimitStrategy"): payload["trafficLimitStrategy"] = cur["trafficLimitStrategy"]
if frozen:
payload["status"] = "DISABLED"
if cur.get("expireAt"):
payload["expireAt"] = cur["expireAt"] # НЕ меняем — часы стоят
else:
payload["status"] = "ACTIVE"
days = max(1, int(banked_days or 1))
exp = datetime.now(timezone.utc) + timedelta(days=days)
payload["expireAt"] = exp.strftime("%Y-%m-%dT%H:%M:%S.000Z") # UTC ISO
resp = await _request("PATCH", "/api/users", json_payload=payload, expected_status=(200, 201))
return resp.status_code in (200, 201)
5) Слой БД (3 функции)
def freeze_key(key_id, banked_days) -> bool:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return _exec("UPDATE vpn_keys SET frozen=1, frozen_at=?, banked_days=?, last_freeze_at=? WHERE key_id=?",
(now, int(banked_days), now, key_id))
def unfreeze_key(key_id, new_expire_at) -> bool:
# ВНИМАНИЕ: last_freeze_at НЕ трогаем — держит кулдаун 30 дней
return _exec("UPDATE vpn_keys SET frozen=0, frozen_at=NULL, expire_at=? WHERE key_id=?",
(new_expire_at, key_id))
def get_frozen_keys_to_unfreeze(older_than_days=30) -> list[dict]:
cutoff = (datetime.now() - timedelta(days=older_than_days)).strftime("%Y-%m-%d %H:%M:%S")
# SELECT * FROM vpn_keys WHERE frozen=1 AND frozen_at IS NOT NULL AND frozen_at <= cutoff
...
6) HTTP-эндпоинты мини-аппа
POST /api/freeze {user_id, key_id}
1. авторизовать + взять реальный telegram_id
2. key = get_key_by_id; проверить владельца (key.user_id == user_id)
3. если key.frozen → "уже заморожена"
4. uuid = key.remnawave_user_uuid; нет → "нет привязки"
5. лимит: (now - last_freeze_at) < 30д → "раз в месяц, ещё N дн"
6. days_left; если < 2 → "осталось меньше 2 дней"
7. set_user_frozen(uuid, True); ошибка → "не удалось на сервере"
8. freeze_key(key_id, days_left) → {ok:True, banked_days}
POST /api/unfreeze {user_id, key_id}
1. авторизовать + владелец
2. если не frozen → "не заморожена"
3. banked = key.banked_days
4. set_user_frozen(uuid, False, banked_days=banked)
5. new_exp = now + max(1, banked); unfreeze_key(key_id, new_exp) → {ok:True}
7) Шедулер (авто-разморозка + пропуски)
async def auto_unfreeze_expired(bot):
for key in get_frozen_keys_to_unfreeze(30):
banked = int(key.get("banked_days") or 0)
if await set_user_frozen(key["remnawave_user_uuid"], False,
host_name=key.get("host_name"), banked_days=banked):
unfreeze_key(key["key_id"], now_plus_days(max(1, banked)))
await bot.send_message(key["user_id"],
"❄️➡️☀️ Подписка автоматически разморожена — прошло 30 дней. Срок продолжил отсчёт.")
И в КАЖДОМ фоновом проходе по ключам (напоминания, автопродление, winback, grace) нужен ранний пропуск:
if key.get("frozen"):
continue
8) Фронт (мини-апп)
- Пункт меню
❄️ Заморозить подписку. Лист с тремя состояниями: заморожено (банк дней + кнопка «Разморозить»), кулдаун («заморозка раз в месяц · ещё N дн»), можно («оставшиеся N дней сохранятся» + «Заморозить»). - После действия: тост +
loadUser()(перечитать состояние). Главный экран приfrozenпоказывает «ледяную» плашку❄️ заморожена · авторазморозка через 30 дн.
function doFreeze(){
var k = window.__KEY; if(!k||!k.key_id){ toast('Нет активного ключа'); return; }
authFetch('/api/freeze',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({user_id:Number(window.__UID), key_id:k.key_id})})
.then(r=>r.json())
.then(j=>{ if(j&&j.ok){ toast('Подписка заморожена ❄️'); loadUser(); }
else toast((j&&j.error)||'Не удалось заморозить'); });
}
Бэкенд отдаёт в объекте ключа: frozen (0/1), banked_days, days_left, last_freeze_at (+ _ms).
9) Чек-лист тестов
- [ ] Заморозка при
days_left < 2→ отказ. - [ ] Повторная заморозка в течение 30 дней → отказ с числом дней кулдауна.
- [ ] Заморозка замороженного / разморозка не-замороженного / чужой
key_id→ отказ. - [ ] После заморозки:
DISABLED, сквады/hwid/telegramId/трафик на месте,expireAtне изменился. - [ ] После разморозки:
ACTIVE,expireAt ≈ now + banked_days. - [ ] Замороженный не получает «подписка кончается», с него не списывается автопродление.
- [ ] Через 30 дней шедулер сам размораживает + уведомляет.
- [ ] Мини-апп после действия показывает актуальное состояние (
loadUser).
TL;DR: 4 колонки в
vpn_keys→set_user_frozen(GET→PATCH, сохрани поля; заморозка =expireAtкак есть, разморозка =now + банк) → 3 функции БД → 2 эндпоинта с валидацией (владелец, ≥ 2 дня, раз/30д) → шедулер (авто-разморозка 30д +if frozen: continueво всех рассылках) → кнопки +loadUser(). Соблюди раздел «Нюансы»: там всё, из-за чего фича «теряет дни».🛠 На практике это 4 поля в БД, две ручки (
/freeze,/unfreeze), один PATCH в Remnawave и пара кнопок. Промпт выше проверен на проде, если нужна помощь с внедрением под ваш код, пишите @qwe8nxtroud.Связанные приёмы удержания: «Спасательный круг» (grace) и Воронка и удержание.
Приём 2. РФ-нода: чтобы российские сервисы работали из-за границы
Почему «Выключите VPN» не лечится выключением VPN
Российские банки (Сбер, Т‑Банк, Альфа), Госуслуги, Ozon, Wildberries, Mir Pay, отечественные стриминги, гео‑ и антифрод‑фильтры смотрят на страну IP. Если IP не российский, варианты ответа: «доступно только в России», требование лишней верификации или баннер «вы используете VPN, отключите его».
Клиент за границей упирается в это с выключенным VPN тоже: его реальный IP (отель, местная симка) иностранный, аккаунт российский → сервис недоволен. Включит ваш VPN с заграничным выходом, IP снова не РФ → то же самое. Проблема не в «детекте VPN», а в географии IP. Выключать нечего: нужно наоборот дать клиенту российский IP.
Решение: добавить ноду в РФ
Поднимаете обычную ноду в России и настраиваете на ней стелс‑вход: vless-tcp-reality или vless-tcp-selfsteal. Клиент ходит в РФ‑сервисы через эту ноду → видит российский IP → банк и Госуслуги довольны. Reality/selfsteal здесь нужны не ради обхода (внутри РФ эти сервисы не блокируют), а чтобы сам хостер и провайдер не опознали ноду как прокси и не прирезали её.
Два способа отдать это клиенту:
- Отдельный выход «🇷🇺 Россия». РФ‑нода добавляется как обычный сервер подписки. Клиент вручную переключается на неё, когда надо открыть банк, и обратно на заграничный выход для всего остального. Просто и понятно.
- Авто‑роутинг (премиальный UX). Заграничный выход остаётся по умолчанию, а трафик к РФ‑сервисам (
geosite:category-ru+ домены банков) автоматически уходит через РФ‑ноду. Клиент вообще ничего не переключает: банк идёт по РФ‑IP, всё прочее через загранку. Как сделать правила, смотрите в Маршрутизация.
⚠️ Не гоните весь трафик через РФ. Российский выход только под РФ‑сервисы. Если завернуть туда всё, клиент получит и российскую цензуру, и слежку, то есть ровно то, от чего он покупал VPN.
Грабли: «грязный» РФ‑IP
Банки и антифрод не любят айпи дата‑центров. Если РФ‑выход даёт сбои на банке, IP, скорее всего, «грязный»: возьмите чистый адрес (см. Чистые IP и репутация, а массово подбирает MultiRoller) и по возможности хостера, чей пул не зашорен. Под этот сценарий abuse‑риск низкий: нода обслуживает только банковский/госовский трафик, а не «запрещёнку», поэтому жалоб хостеру по ней практически не бывает.
Какой РФ‑хостер взять под ноду
Нужен дешёвый VPS, локация Россия, Ubuntu/Debian, 1 vCPU / 1 ГБ: нагрузка минимальная. Три проверенных варианта:
| Хостер | Статус | Под что |
|---|---|---|
| Beget 🇷🇺 | рекомендую | Удобная панель, оплата картой РФ, стабильные РФ‑локации. Хорош как нода РФ‑выхода под банки/Госуслуги. 👉 Регистрация |
| Timeweb 🇷🇺 | пользуюсь сам | Рубли/карта РФ, почасовая оплата, от ~150 ₽/мес, быстрый разворот. Мой основной РФ‑хост под инфраструктуру и релеи. 👉 Регистрация по нашей реф‑ссылке |
| UFO 🇷🇺 | сам не пользовался | По отзывам, недорогая альтернатива под РФ‑ноду; беру на заметку как запасной вариант. |
📌 Эти РФ‑хосты берём именно под РФ‑выход (доступ к российским сервисам). Под основной заграничный выход РФ‑хостинг не используем: там abuse‑риск и блокировки; почему, смотрите в «Сервер за 15 минут».
Чек‑лист «отпускного» VPN
- [ ] В боте/мини‑аппе есть кнопки ❄️ Заморозить / ▶️ Разморозить (банк дней
banked_days, статус RemnawaveDISABLED/ACTIVE). - [ ] Лимиты заморозки: раз в 30 дней, остаток ≥ 2 дней, авто‑разморозка через 30 дней.
- [ ] Шедулер напоминаний/автопродления пропускает
frozen‑ключи. - [ ] Поднята РФ‑нода (vless‑tcp‑reality или selfsteal) на дешёвом РФ‑VPS, локация Россия.
- [ ] РФ‑выход отдан клиенту: отдельный сервер «🇷🇺 Россия» или авто‑роутинг РФ‑сервисов через Маршрутизацию.
- [ ] IP РФ‑ноды чистый (проверен под банки), весь остальной трафик НЕ через РФ.
Связано: «Спасательный круг» (grace) и Воронка и удержание (другие приёмы против оттока); VLESS‑TCP‑Reality и Self‑steal с nginx (как поднять РФ‑ноду); Маршрутизация (завернуть РФ‑сервисы на РФ‑выход); Сервер за 15 минут (хостеры и фундамент).