Выключен по умолчанию: пополнение под просмотром открывает учебный экран. Включён — платёж уходит в банк, и над кнопкой «Оплатить» появляется предупреждение, что деньги спишутся по-настоящему и зачислятся водителю. Режим гаснет вместе с выходом из учётки сотрудника: следующий раз он включается осознанно, а не достаётся по наследству. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Премиум Водитель — PWA водителя
React + Vite + TypeScript. Прод: https://app.pptaxi.ru.
Бэкенд — модуль payments в Premium CRM (https://crm.pptaxi.ru/api,
репозиторий crm2, backend/app/modules/payments/).
Как поднять локально
npm install
npm run dev # http://localhost:5174
Отдельно поднимается бэкенд crm2 на 127.0.0.1:8000 (тот же порт, что в
backend/Dockerfile). Больше настраивать нечего: dev-сервер сам подставляет
VITE_API_BASE=/api и проксирует /api на бэкенд — см. vite.config.ts.
Почему через прокси, а не «разрешить localhost в CORS бэкенда». Прокси
делает запрос ОДНОГО происхождения со страницей (http://localhost:5174/api/…),
поэтому CORS в разработке не участвует вообще. Боевой список origin'ов на
бэкенде остаётся закрытым, а CORS_ALLOW_LOCALHOST=true в backend/.env для
этого фронта включать не надо. (Раньше рецепт с CORS_ALLOW_LOCALHOST тут и не
работал: прокси не было, и фронт по умолчанию уходил на боевой домен, а не на
локальный бэкенд.)
Сервис-воркер в режиме разработки не регистрируется (devOptions у
vite-plugin-pwa по умолчанию выключены) — старый кэш прод-версии в дев не
лезет.
Бэкенд не на 127.0.0.1:8000
VITE_API_TARGET=http://127.0.0.1:8090 npm run dev
В PowerShell инлайн-префикса VAR=... команда нет — переменную ставят отдельно:
$env:VITE_API_TARGET="http://127.0.0.1:8090"; npm run dev
Имя переменной то же, что в CRM (crm2/frontend/vite.config.ts), — рецепт один
на оба фронта.
Ходить в API напрямую, мимо прокси
.env.local (в git не попадает):
VITE_API_BASE=http://127.0.0.1:8000/api
Своя настройка приоритетнее дев-дефолта — специально, чтобы npm run dev её не
перебивал. Для похода в чужой origin бэкенду понадобится
CORS_ALLOW_LOCALHOST=true в backend/.env и рестарт.
⚠️
crm.pptaxi.ru— боевой терминал Т-Банка и боевая касса АТОЛ. Пополнение, сделанное «просто посмотреть», — это реальные деньги водителя, реальный чек 54-ФЗ и реальная проводка в леджере. Поэтому прокси по умолчанию смотрит в локальный бэкенд, а не в прод.
Проверки
npx tsc -b # типы
npm test # vitest run
npx tsc --noEmit без -b здесь всегда зелёный и ничего не значит:
корневой tsconfig.json — solution-файл (files: [] + references), под него
не попадает ни один файл (tsc --noEmit --listFiles печатает пустоту). Типы
живут в tsconfig.app.json (src) и tsconfig.node.json (конфиги +
dev-proxy.test.ts), и до обоих добирается только tsc -b.
Сборка
npm run build # tsc -b && vite build → dist/
Дев-дефолт VITE_API_BASE=/api подставляется только при запущенном dev-сервере
и в бандл не попадает: в собранном assets/index-*.js лежит
https://crm.pptaxi.ru/api (проверено сборкой). Поэтому VITE_API_BASE боевой
сборке задавать не обязательно — без неё src/api/client.ts подставляет ровно
этот адрес.
⚠️
npm run previewподнимает уже собранный бандл, а база в нём абсолютная: запросы уходят на боевойcrm.pptaxi.ruмимо прокси dev-сервера. Смотреть локальные правки — толькоnpm run dev.
Куда выкладывается dist/ — в плане проекта:
../../docs/superpowers/plans/2026-06-18-premium-driver-pwa.md, раздел
«Deployment notes»: статика на app.pptaxi.ru, Caddy с фолбэком на index.html.
Обкатка: кто может войти и как смотреть чужой экран
Приложение выложено на боевой домен, а обкатывается интерфейс. Поэтому на время обкатки включены две вещи.
1. Вход только по списку номеров
Переменная бэкенда (crm2/backend/.env, читается в app/config.py):
DRIVER_APP_ALLOWED_PHONES=+7 (914) 123-45-67, 79990001122
Разделители — запятая, точка с запятой, перенос строки (пробел разделителем не считается: номер вписывают в привычном виде). Сверяются последние 10 цифр. Пусто — гейт выключен, вход открыт всем водителям.
Проверка стоит в единственной воронке выдачи токена
(modules/payments/driver_auth.create_driver_token_for), поэтому закрыты сразу
все пять путей входа: код в бот, кнопка «Войти через Telegram», колбэки своего
бота и два аварийных отката на n8n. Бот отвечает человеку текстом, остальные
пути — 403. Логика гейта: modules/payments/login_gate.py.
2. Просмотр от лица водителя (сотрудник)
На экране входа внизу — «Вход для сотрудника» (/dev): логин и пароль учётки
CRM, при включённой двухфакторке спросит код. Дальше поиск по ФИО, телефону
или id — и приложение открывается от лица выбранного водителя.
- Токен помечен claim'ом
imp, живёт 8 часов (обычный водительский — 30 дней). - Гейт —
is_adminв CRM. Каждая выдача пишется в аудит (driver_app.impersonate). - Кнопка «Отозвать доступ» в CRM гасит и просмотр сотрудника — поколение общее.
- Гейт обкатки на этот путь не распространяется: смотреть надо как раз на водителей, которых в allowlist нет.
Пока просмотр действует, сверху на всех экранах висит плашка «Смотрю как …» со
ссылкой на вкладку «Отладка» (/debug, водителю недоступна):
- от чьего лица открыт экран и кнопка сменить водителя;
driver_id, режим, сотрудник, когда истекает токен, адрес API, коммит и время сборки, состояние сети;- журнал последних 40 ответивших запросов (метод, путь, статус, миллисекунды);
- сброс кэша сервис-воркера с перезагрузкой — лечит «правка не приехала»;
- выходы: из просмотра, из учётки сотрудника.
Деньги под просмотром: учебный платёж
Пополнение под imp-токеном не идёт в банк. Проверки суммы, категории и
расчёт комиссии настоящие, а вместо Init на терминале заводится учебный заказ в
Redis (modules/payments/mock_pay.py, префикс mock-, час жизни). Экран
/mock-pay/:orderId даёт выбрать, что «ответит» банк — оплачено, отказ,
истёкший счёт, возврат, — и настоящий экран оплаты разбирает эти статусы своим
боевым кодом. В driver_payments не попадает ни строки, чек 54-ФЗ не пробивается.