TKBK Studio
TKBK Studio
Next.js 15: лендинг с тарифами, вход по email и Telegram, подписки через ЮKassa с чеками 54-ФЗ
Готовая основа платного веб-сервиса для рынка РФ: лендинг с тарифами, вход без паролей, оплата подписки через ЮKassa, личный кабинет и админка. Вы пишете только свой продукт — регистрация, оплата и учёт подписок уже работают.
Вход — по одноразовому коду из письма (SMTP, а при разработке код просто печатается в консоль) или через Telegram Login Widget с проверкой подписи по документации Telegram. Оба способа можно привязать к одному аккаунту.
Оплата сделана так, как требует ЮKassa: платёж создаётся с Idempotence-Key, пользователь уходит на страницу оплаты (redirect), а статус после вебхука всегда перепроверяется запросом GET /v3/payments/{id}. Передаётся чек 54-ФЗ с email покупателя; по флагу включается автопродление с сохранением способа оплаты и списанием без участия пользователя.
Внутри — Next.js 15 (App Router, TypeScript), Tailwind 4 и SQLite с миграциями: никаких внешних баз и облачных сервисов. Запуск одной командой в Docker, бизнес-логика покрыта тестами Vitest.
Для кого: Стартапы, разработчики SaaS и онлайн-сервисов для РФ, инфобизнес
Ключи и токены не вводятся на сайте — их кладут в файл .env на своём сервере.
APP_URLАдрес сайта — Например https://saas.example.ru — без слеша в концеADMIN_EMAILSEmail администраторов — Через запятую — им откроется /adminYOOKASSA_SHOP_IDshopId ЮKassa — Личный кабинет ЮKassa → Интеграция → Ключи APIYOOKASSA_SECRET_KEYСекретный ключ ЮKassa — Там же; для тестового магазина ключ начинается с test_YOOKASSA_RECEIPTSПередавать чеки 54-ФЗ — true, если подключены «Чеки от ЮKassa» или онлайн-кассаYOOKASSA_VAT_CODEКод НДС в чеке — 1 — без НДС (УСН, НПД)YOOKASSA_RECURRINGАвтопродление — true — только после согласования автоплатежей с ЮKassaSMTP_HOSTSMTP-сервер — Например smtp.yandex.ru. Пусто — коды входа пишутся в логSMTP_USERSMTP-логин — Обычно адрес ящикаSMTP_PASSWORDSMTP-пароль — Пароль приложения почтового сервисаTELEGRAM_BOT_USERNAMEЮзернейм бота для входа — Без @. Домен сайта привяжите у @BotFather: /setdomainTELEGRAM_BOT_TOKENТокен бота — @BotFather → /newbot; нужен для проверки подписи входаSESSION_SECRETСекрет подписи кодов — Необязательно: openssl rand -hex 32. Пусто — создастся data/secret.key# {{PRODUCT_NAME}}
SaaS-стартер на Next.js 15: лендинг с тарифами, вход по коду из письма и через Telegram, подписки с оплатой ЮKassa (чеки 54-ФЗ, автопродление), личный кабинет и админка. База — SQLite, запуск одной командой.
## Что умеет
- **Лендинг** с тарифами из конфига (`lib/config.ts`), шагами «как это работает» и FAQ.
- **Вход без паролей**: одноразовый код на email (SMTP; без SMTP код печатается в консоль) и Telegram Login Widget с проверкой подписи. Оба способа можно привязать к одному аккаунту.
- **Оплата ЮKassa**: платёж с `Idempotence-Key`, переход на страницу оплаты (redirect), вебхук с обязательной перепроверкой статуса через `GET /v3/payments/{id}`, сверка суммы, продление подписки ровно один раз.
- **Чеки 54-ФЗ**: поле `receipt` с email покупателя, кодом НДС и признаками «услуга / полный расчёт».
- **Автопродление** (по флагу): сохранение способа оплаты, списание за сутки до конца периода, отключение в один клик.
- **Кабинет**: подписка, история платежей, способы входа.
- **Админка** для адресов из `ADMIN_EMAILS`: пользователи, платежи, выручка за 30 дней, поиск.
- **Заготовки** публичной оферты и политики конфиденциальности (152-ФЗ).
## Быстрый старт с Docker (10 минут)
1. Установите [Docker Desktop](https://www.docker.com/products/docker-desktop/) (или Docker Engine на сервере).
2. Создайте `.env` из примера и заполните минимум: `ADMIN_EMAILS` (ваш email), `YOOKASSA_SHOP_ID` и `YOOKASSA_SECRET_KEY` от **тестового** магазина (см. «Настройка ЮKassa»):
```bash
cp .env.example .env
```
3. Запустите:
```bash
docker compose up -d --build
```
4. Откройте http://localhost:3000 → «Войти» → введите email. Пока SMTP не настроен, код смотрите в логах:
```bash
docker compose logs -f app
```
5. Выберите тариф, оплатите тестовой картой `5555 5555 5555 4477` (срок — любая будущая дата, CVC — любые 3 цифры). После оплаты вы вернётесь в кабинет, и подписка станет активной.
База и секретный ключ лежат в Docker-томе `app-data` и переживают пересборку образа.
## Запуск без Docker
Нужен Node.js 22.12+ (рекомендуется 24 LTS).
```bash
npm install
cp .env.example .env
npm run dev # разработка: http://localhost:3000
npm run build && npm start # продакшен
```
SQLite ставится готовым бинарником (better-sqlite3) — компилятор и Python не нужны ни на Windows, ни на Linux/macOS. Порт меняется так: `PORT=3001 npm start`.
## Настройка ЮKassa
### Ключи и тестовый режим
1. Зарегистрируйтесь на [yookassa.ru](https://yookassa.ru) и в личном кабинете создайте **тестовый магазин** — платежи в нём ненастоящие.
2. «Интеграция → Ключи API»: `shopId` → `YOOKASSA_SHOP_ID`; «Выпустить секретный ключ» → `YOOKASSA_SECRET_KEY` (у тестового магазина начинается с `test_`).
3. Тестовая карта для успешной оплаты — `5555 5555 5555 4477`. Карты с ошибками оплаты и другие сценарии — в [документации ЮKassa по тестированию](https://yookassa.ru/developers/payment-acceptance/testing-and-going-live/testing).
4. Для боевого режима выпустите ключи боевого магазина и замените их в `.env`.
### URL для уведомлений (вебхук)
«Интеграция → HTTP-уведомления»: адрес `https://ваш-домен/api/yookassa/webhook`, события `payment.succeeded` и `payment.canceled`.
- Нужен публичный HTTPS-адрес. При локальной разработке уведомления не дойдут — это нормально: когда пользователь возвращается в кабинет (`/account?order=…`), сервер сам спрашивает статус у ЮKassa.
- Телу уведомления сервер **не доверяет**: статус всегда перепроверяется запросом в API, сумма сверяется с заказом, повторные уведомления не продлевают подписку второй раз. Если API ЮKassa недоступно, вебхук отвечает 500, и ЮKassa повторит уведомление позже.
### Чеки 54-ФЗ
Если подключены «Чеки от ЮKassa» или своя онлайн-касса, оставьте `YOOKASSA_RECEIPTS=true`. В каждый платёж уйдёт `receipt`: email покупателя (спрашиваем на странице оплаты), позиция «услуга», полный расчёт, код НДС из `YOOKASSA_VAT_CODE`: `1` — без НДС (УСН, НПД), `11` — НДС 22%, `7` и `8` — НДС 5% и 7% для УСН с НДС (полная таблица — в [документации ЮKassa](https://yookassa.ru/developers/payment-acceptance/receipts/54fz/other-services/parameters-values)). Если чеки не подключены, поставьте `YOOKASSA_RECEIPTS=false`.
### Автопродление
1. Согласуйте автоплатежи с менеджером ЮKassa — без этого сохранение способа оплаты не сработает.
2. Включите `YOOKASSA_RECURRING=true`. На странице оплаты появится галочка «Включить автопродление» — по умолчанию она снята: согласие должно быть явным.
3. После успешной оплаты способ оплаты сохраняется. За сутки до конца периода сервер сам списывает оплату (не чаще раза в сутки, ещё 3 дня после окончания, если не хватило денег).
4. Пользователь отключает автопродление в кабинете в один клик. Если он отзовёт разрешение в банке, автопродление выключится само.
Списание идёт по цене, по которой пользователь подписался: изменения тарифов действуют на новые покупки.
## Вход через Telegram
1. В [@BotFather](https://t.me/BotFather): `/newbot` → токен в `TELEGRAM_BOT_TOKEN`, юзернейм (без @) в `TELEGRAM_BOT_USERNAME`.
2. Там же `/setdomain` → выберите бота → укажите домен сайта (например `saas.example.ru`). Без этого виджет покажет «Bot domain invalid».
3. Виджет работает только на домене из `/setdomain`, поэтому на `localhost` его не проверить. Используйте сервер или туннель (например, `cloudflared tunnel --url http://localhost:3000`) и укажите его домен.
Сервер проверяет подпись (`hash`) по [документации Telegram](https://core.telegram.org/widgets/login#checking-authorization) и не принимает данные старше суток.
## Почта (SMTP)
Подойдёт любой SMTP: Яндекс 360 (`smtp.yandex.ru`, порт 465, пароль приложения), Mail.ru (`smtp.mail.ru`, 465), Unisender Go, SendPulse. Заполните `SMTP_HOST`, `SMTP_USER`, `SMTP_PASSWORD`, `MAIL_FROM`. Пока `SMTP_HOST` пуст, коды входа пишутся в лог сервера — это удобно для разработки, но не для клиентов.
## Тарифы
Тарифы лежат в `lib/config.ts` (константа `PLANS_JSON`):
```json
[
{ "id": "start", "title": "Старт", "price": 490, "periodDays": 30, "features": ["Все базовые функции"] },
{ "id": "pro", "title": "Pro", "price": 1490, "periodDays": 30, "highlight": true, "features": ["Расширенные лимиты"] }
]
```
- `id` — латиница, цифры, `-` и `_`; `price` — рубли (до 2 знаков после запятой); `periodDays` — длительность; `highlight` — выделить карточку.
- Ошибка в JSON остановит сборку с понятным сообщением.
- Повторная покупка того же тарифа продлевает подписку от конца текущего периода. При смене тарифа неиспользованный остаток пересчитывается в дни нового тарифа по дневной цене.
## Админка
`/admin` открывается пользователям, чей email указан в `ADMIN_EMAILS` (войдите по коду на этот адрес). Остальные видят 404.
## Структура проекта
```
app/ страницы и API (App Router)
api/auth/… вход по email-коду, Telegram, выход
api/billing/… создание платежа, отключение автопродления
api/yookassa/… вебхук ЮKassa
account/ admin/ checkout/ login/ legal/
components/ интерфейс (карточки тарифов, формы входа, таблицы)
lib/
config.ts название, описание, тарифы, реквизиты (из мастера TKBK Studio)
plans.ts тарифы и денежная математика — чистые функции
billing/ платёж, применение статуса, автопродление, планировщик
auth/ сессии, коды входа, проверка Telegram
repo/ SQL-запросы
yookassa.ts клиент API ЮKassa с повторами
db.ts env.ts база с миграциями, настройки из .env
tests/ тесты Vitest
instrumentation.ts запуск фоновых задач при старте сервера
```
## Тесты и проверки
```bash
npm test # Vitest: тарифы, деньги, Telegram hash, коды входа, вебхук с фейковым API ЮKassa
npm run typecheck # TypeScript
npm run build # сборка Next.js
```
## FAQ
**Оплатил, а подписка не активна.** Обновите кабинет через минуту. Проверьте логи (`docker compose logs app`) и URL уведомлений в ЮKassa. Статус каждого платежа и его ID в ЮKassa видны в админке.
**Виджет Telegram пишет «Bot domain invalid».** Привяжите домен к боту: `/setdomain` у @BotFather. Домен должен совпадать с `APP_URL`.
**Нужен ли HTTPS?** Для продакшена — да: этого требуют ЮKassa (уведомления) и Telegram, а cookie становятся Secure, когда `APP_URL` начинается с `https://`. Проще всего поставить перед контейнером [Caddy](https://caddyserver.com) — он сам выпустит сертификат: `saas.example.ru { reverse_proxy localhost:3000 }`.
**Где база и как сделать бэкап?** Файл `data/app.db` (в Docker — том `app-data`). Бэкап: `docker compose stop app && docker compose cp app:/app/data ./backup && docker compose start app`.
**Как поменять дизайн?** Цвет — переменная `--color-brand` в `app/globals.css`, тексты лендинга — `app/page.tsx`, компоненты — `components/`.
**Можно ли PostgreSQL?** Да: замените `lib/db.ts` и SQL в `lib/repo/` — остальной код работает с ними через функции.
## Что доработать
- Промокоды и пробный период.
- Возвраты через API ЮKassa из админки.
- Ручная выдача подписки в админке.
- Напоминания об окончании подписки на email и в Telegram.
- Экспорт платежей в CSV.
- Командные аккаунты.
Перед запуском проверьте с юристом тексты оферты и политики (`app/legal/`) и подайте уведомление об обработке персональных данных в Роскомнадзор.
Услуги, мастера, свободные окна на 14 дней, напоминания за сутки и за 2 часа, предоплата и отзывы
Оценка 0–10 после заказа: довольных — на Яндекс Карты, 2ГИС и WB, недовольных — сразу к менеджеру
Оплата звёздами или картой, одноразовые ссылки, напоминания о продлении и автоисключение по окончании