diff --git a/docs/00-index.md b/docs/00-index.md index 14cef25..bb8b894 100644 --- a/docs/00-index.md +++ b/docs/00-index.md @@ -51,12 +51,16 @@ | № | Вопрос | Решение | Где отражено | |---|---|---|---| | Q-01 | Вторая валюта | USD | [D-010](decisions/D-010-currencies.md) | +| Q-02 | Цены mass в USD, суммы пополнения и реферального бонуса | Mass: «Месяц» $6.99, «Год» $69.90. Пополнение: dedicated — 2 500, 5 000, 10 000 ₽ и $25, $50, $100; mass — 700, 2 100, 6 990 ₽ и $7, $21, $70. Реферальный бонус — не деньги, а 30 дней к ключу обоим | [01-product.md](01-product.md), разделы 6 и 10; [04-billing.md](04-billing.md), разделы 4 и 11; [D-025](decisions/D-025-referral-days.md) | | Q-03 | Валюта по умолчанию для нового посетителя | По языку: ru → RUB, остальные → USD; настраивается у домена | [06-i18n.md](06-i18n.md), раздел 6 | | Q-04 | Валюта аккаунта | Задаётся при регистрации из выбранной; меняется, пока баланс нулевой и нет ключей; дальше — через поддержку | [04-billing.md](04-billing.md), раздел 2 | | Q-05 | WG-конфиги mass на этапе 1 | Конфиги, добавленные администратором, по странам; другая страна — через поддержку; автоматически — этап 5 | [D-020](decisions/D-020-key-models.md) | | Q-06 | Где Umami | На этом сервере, без Docker, под пользователем `umami`; панель — `umi.veilix.cc` | [D-018](decisions/D-018-analytics.md) | | Q-07 | Как запускать сервисы | Каждый под своим пользователем ОС: vx_dev (dev), vx_prod (prod), umami; systemd user-юниты | [D-004](decisions/D-004-environments.md) | +| Q-08 | Email поддержки брендов | `help@veilix.app` и `help@veilixvpn.com` | [01-product.md](01-product.md), раздел 11 | +| Q-09 | Часовой пояс часов поддержки | Московское время | [01-product.md](01-product.md), раздел 11 | | Q-15 | Защита prod от сессий Claude | Отдельно не нужна: сессии Claude работают под vx_dev без доступа к prod, а под vx_prod нет ни редактора, ни ИИ-агентов | [D-004](decisions/D-004-environments.md) | +| Q-16 | Где принимать почту поддержки | Ящики на внешнем почтовом хостинге, их настраивает пользователь (R-18). На этапе 2 письма из них забирает Chatwoot; до этого на сайте только адрес | [09-infrastructure.md](09-infrastructure.md), раздел 6; [08-integrations.md](08-integrations.md), раздел 4 | ### 4.2 Открытые @@ -64,14 +68,11 @@ | № | Вопрос | Предложение | Нужно к этапу | |---|---|---|---| -| Q-02 | Цены mass в USD, суммы пополнения и реферального бонуса в USD для обоих брендов | Нужны цифры. Пока в seed временные $6.99 и $69.90 (около $1 за 100 ₽, как у dedicated в макете) | 1b | -| Q-08 | Email поддержки брендов | `help@veilix.app` и `help@veilixvpn.com` (в макете были домены .cc) | 1b | -| Q-09 | Часовой пояс часов поддержки (10–20 в рабочие дни) | МСК | 1b | | Q-10 | Имена Telegram-ботов: 2 основных, 2 поддержки, тестовые | — | 3 | | Q-11 | Оплата в Telegram: Stars, внешняя страница или только с баланса | — | 3 | | Q-12 | RUB-процессинг (KZ, TR) и криптопроцессинг: какие провайдеры | — | 4 | | Q-13 | Юрлицо для юридических текстов | Пока заглушки | 1h | | Q-14 | Куда складывать бэкапы prod-базы | — | 1h | -| Q-16 | Где принимать почту поддержки (`help@…`): свой почтовый хостинг или пересылка в Chatwoot | — | 1b | | Q-17 | Нужны ли разные аккаунты одного провайдера для разных брендов | Пока нет. Если понадобятся — платёжной системе добавим поле «только для сайтов» | 4 | | Q-18 | Кто запускает выкладку в prod | Пользователь одной командой; по желанию — sudo-правило, разрешающее vx_dev запускать только команду выкладки от имени vx_prod | 1h | +| Q-19 | Реферальный бонус днями: крайние случаи | Приглашённому тоже 30 дней; ключ готовится — дни добавятся при выдаче; «последний ключ» — купленный последним из неистёкших и неотменённых; такого нет — дни ждут следующего ключа; возврат заказа приглашённого бонус не отменяет ([D-025](decisions/D-025-referral-days.md)) | 1g | diff --git a/docs/01-product.md b/docs/01-product.md index 6852ae9..51fd8f3 100644 --- a/docs/01-product.md +++ b/docs/01-product.md @@ -69,7 +69,7 @@ 4. До 5 устройств на ключ (поле тарифа). 5. Статусы локаций: «Включено» и «Скоро». Локации «Скоро» показываются, но не работают. 6. В кабинете — «Список ключей», как у dedicated; внутри ключа ссылка Xray и конфиги WG. Формулировки «текущая/запасная локация» из макета не используем. -7. Цены в USD для mass в макете нет — вопрос Q-02. +7. Цены в USD (в макете их нет, решение Q-02): «Месяц» $6.99, «Год» $69.90. ## 7. Локации из макета @@ -133,18 +133,20 @@ ## 10. Реферальная программа 1. «Приведите друга — вы оба получите по месяцу в подарок». -2. «Месяц» начисляется деньгами на баланс обоим после первой оплаты заказа другом. Сумма задаётся в админке для каждого сайта и валюты; в макете — 2 499 ₽ (dedicated) и 699 ₽ (mass). -3. Бонус попадает на баланс и поэтому уходит в ближайшее списание. -4. Ссылки: `https://my.veilix.app/r/<код>` (для mass — `my.veilixvpn.com`) и `t.me/<основной бот>?start=<код>`. -5. Приглашения между брендами не засчитываются, самоприглашения отсекаются. -6. В кабинете показываем: приглашено, начислено, ожидает оплаты; список приглашённых с замаскированной почтой. +2. «Месяц» — это дни срока ключа, а не деньги: по 30 дней обоим после первой оплаты заказа другом ([D-025](decisions/D-025-referral-days.md)). Число дней задаётся в настройках сайта. + a. Другу — на ключ, который он купил. + b. Пригласившему — на его последний купленный ключ. Работающего ключа нет — дни добавятся к следующему. +3. Ссылки: `https://my.veilix.app/r/<код>` (для mass — `my.veilixvpn.com`) и `t.me/<основной бот>?start=<код>`. +4. Приглашения между брендами не засчитываются, самоприглашения отсекаются. +5. В кабинете показываем: приглашено, начислено месяцев, ожидает оплаты; список приглашённых с замаскированной почтой. ## 11. Поддержка и заявки 1. Страница поддержки: Telegram (бот поддержки бренда) и email, FAQ по разделам, форма связи. -2. Заявка «Нужны индивидуальные условия» (dedicated) и «Нужны особые условия» (mass): имя, email, Telegram, WhatsApp, коротко о задаче. -3. Заявки и обращения сохраняются в Payload и приходят на почту поддержки (этап 1), затем уходят в Twenty CRM и Chatwoot (этап 2). -4. Часы поддержки из макета: с 10 до 20 в рабочие дни (часовой пояс — Q-09). +2. Почта поддержки: `help@veilix.app` (dedicated) и `help@veilixvpn.com` (mass); в макете были домены .cc. Ящики — на внешнем почтовом хостинге; на этапе 2 письма из них забирает Chatwoot. До этого на сайте только адрес. +3. Заявка «Нужны индивидуальные условия» (dedicated) и «Нужны особые условия» (mass): имя, email, Telegram, WhatsApp, коротко о задаче. +4. Заявки и обращения сохраняются в Payload и приходят на почту поддержки (этап 1), затем уходят в Twenty CRM и Chatwoot (этап 2). +5. Часы поддержки из макета: с 10 до 20 по московскому времени в рабочие дни. ## 12. Языки diff --git a/docs/03-data-model.md b/docs/03-data-model.md index 468c870..0449d95 100644 --- a/docs/03-data-model.md +++ b/docs/03-data-model.md @@ -38,6 +38,8 @@ erDiagram ORDERS ||--o{ LEDGER_ENTRIES : "списание, возврат" CUSTOMERS ||--o{ PAYMENT_METHODS : "карты" CUSTOMERS ||--o{ REFERRALS : "пригласил" + KEYS ||--o{ KEY_EXTENSIONS : "продления без оплаты" + REFERRALS ||--o{ KEY_EXTENSIONS : "бонус" CUSTOMERS ||--o{ CUSTOMER_SESSIONS : "сессии" ``` @@ -51,12 +53,12 @@ erDiagram | name | text (L) | «Вейликс» / «Veilix» | | orderPrefix | text | префикс номеров: `VX`, `VV` | | locales | multi-select из языков конфига | языки контента сайта; en обязателен; домены показывают часть этих языков ([D-023](decisions/D-023-site-locales.md)) | -| supportEmail | email | почта поддержки (Q-08) | +| supportEmail | email | почта поддержки: `help@veilix.app`, `help@veilixvpn.com` | | emailFrom | group: name, address | отправитель писем, напр. «Вейликс» | | supportBot, mainBot | text | имена ботов (этап 3) | | referralEnabled | checkbox | | -| referralBonus | array: currency, amount | бонус «месяц в подарок» в каждой валюте | -| topupPresets | array: currency, amounts[] | суммы пополнения в кабинете | +| referralBonusDays | number, по умолчанию 30 | бонус «месяц в подарок»: дни срока ключа обоим ([D-025](decisions/D-025-referral-days.md)) | +| topupPresets | array: currency, amounts[] | суммы пополнения в кабинете ([04-billing.md](04-billing.md), раздел 4) | | graceDays | number, по умолчанию 3 | льготный период | | reminderDays | number, по умолчанию 3 | за сколько дней напоминать | | renewalLeadHours | number, по умолчанию 24 | за сколько часов до окончания пробовать продлить | @@ -249,6 +251,23 @@ QR-коды строятся на лету из `payload`, не хранятся Переходы — в [04-billing.md](04-billing.md). +### 5.4 `key-extensions` — Продления без оплаты (сайт) + +Реферальный бонус и «Продлить вручную» ([04-billing.md](04-billing.md), раздел 6.4). Только создание, как у движений по счёту. + +| Поле | Тип | Описание | +|---|---|---| +| key | relationship | | +| days | number | сколько дней добавлено | +| reason | select `referral` \| `compensation` | реферальный бонус или компенсация из админки | +| referral | relationship → referrals | для `referral` | +| appliedAt | date, null | когда сдвинут срок; пусто — ключ готовится, дни добавятся при выдаче | +| expiresAtBefore, expiresAtAfter | date | срок до и после | +| comment | text | обязателен для `compensation` | +| createdBy | relationship → admins, null | null — система | + +Уникальность: (`referral`, `key`) — бонус не начисляется дважды. + ## 6. Деньги ### 6.1 `orders` — Заказы (сайт) @@ -300,13 +319,13 @@ QR-коды строятся на лету из `payload`, не хранятся | customer | relationship | | | currency | | валюта аккаунта | | amount | number со знаком | `+` зачисление, `−` списание | -| type | select `topup` \| `order_charge` \| `refund` \| `referral_bonus` \| `adjustment` \| `payout` | `payout` — возврат денег с баланса на карту (этап 4) | -| payment, order, key, referral | relationship | источник записи | +| type | select `topup` \| `order_charge` \| `refund` \| `adjustment` \| `payout` | `payout` — возврат денег с баланса на карту (этап 4). Реферальный бонус — не деньги, а дни (5.4) | +| payment, order, key | relationship | источник записи | | balanceAfter | number | баланс после записи | | comment | text | обязателен для `adjustment` | | createdBy | relationship → admins, null | null — система | -Уникальность: (`payment`, `topup`), (`order`, `order_charge`), (`referral`, `customer`, `referral_bonus`). Это защищает от двойного проведения. +Уникальность: (`payment`, `topup`), (`order`, `order_charge`). Это защищает от двойного проведения. ### 6.4 `payment-methods` — Сохранённые способы оплаты (сайт) @@ -314,7 +333,7 @@ QR-коды строятся на лету из `payload`, не хранятся ### 6.5 `referrals` — Приглашения (сайт) -`referrer`, `referee` (уникальное), `status` (`pending` \| `review` \| `rewarded` \| `rejected`), `qualifyingOrder`, `rewardedAt`, `rejectReason`. +`referrer`, `referee` (уникальное), `status` (`pending` \| `review` \| `rewarded` \| `rejected`), `qualifyingOrder`, `days` (копия `sites.referralBonusDays` при начислении), `refereeKey`, `referrerKey` (пусто, пока у пригласившего нет подходящего ключа: дни ждут его следующего ключа), `rewardedAt`, `rejectReason`. ## 7. Контент и коммуникации @@ -376,7 +395,7 @@ QR-коды строятся на лету из `payload`, не хранятся | customers | всё | чтение, правка заметок, блокировка | — | только свой профиль | | keys, connections | всё | всё | — | только свои, чтение | | orders, payments | всё | чтение, ручной платёж | — | только свои, чтение | -| ledger-entries | создание корректировок, чтение | чтение | — | только свои, чтение | +| ledger-entries, key-extensions | создание корректировок и компенсаций, чтение | чтение | — | только свои, чтение | | leads | всё | всё | — | — | | email-log, audit-log | чтение | чтение без `debugCode` | — | — | diff --git a/docs/04-billing.md b/docs/04-billing.md index b236964..62218d1 100644 --- a/docs/04-billing.md +++ b/docs/04-billing.md @@ -28,7 +28,7 @@ ## 4. Пополнение баланса -1. Платёж с назначением `topup`. Суммы — пресеты сайта для валюты (`sites.topupPresets`) или своя сумма в пределах лимитов платёжной системы. +1. Платёж с назначением `topup`. Суммы — пресеты сайта для валюты (`sites.topupPresets`) или своя сумма в пределах лимитов платёжной системы. Пресеты: dedicated — 2 500, 5 000, 10 000 ₽ и $25, $50, $100; mass — 700, 2 100, 6 990 ₽ и $7, $21, $70 (Q-02). 2. После зачисления — письмо `topup_succeeded`. 3. После любого пополнения система сама оплачивает с баланса неоплаченные заказы на продление этого пользователя, начиная со старого. Неоплаченные заказы на новые ключи сама не оплачивает. 4. Криптовалюта (этап 4): счёт в монете, курс фиксируется на 15 минут, зачисление в валюте аккаунта после подтверждения сети. @@ -70,6 +70,14 @@ 1. Галочка автопродления — у каждого ключа. 2. «Отвязать карту» — в аккаунте, раздел «Счёт и платежи». После отвязки автопродление списывает только баланс. +### 6.4 Продление без оплаты + +Реферальный бонус (раздел 11) и «Продлить вручную» в админке (раздел 9) добавляют к сроку ключа N дней без денег. Каждое такое продление — запись `key-extensions` с причиной. + +1. Ключ `active` или `grace`: `expiresAt` сдвигается на N дней. Ключ `grace`, у которого новый срок ещё не наступил, снова `active`. Неоплаченный заказ на продление на старую дату отменяется: задача `renewals` создаст новый к новой дате. +2. Ключ `provisioning`: дни ждут выдачи и добавляются к сроку при ней (раздел 8). +3. Истёкший, отменённый и заблокированный ключи так не продлеваются. + ## 7. Льготный период и окончание (задача `expiry`, каждые 15 минут) 1. `active`, срок истёк, продление не оплачено → `grace`, `graceUntil = expiresAt + graceDays`, письмо `grace_started`. Ключ работает. @@ -81,7 +89,7 @@ ## 8. Выдача ключа 1. Админка, ключ в статусе `provisioning`: администратор добавляет подключения (WG/AWG по устройствам, ссылку Happ, RU-адрес), IP-адреса и нажимает «Выдать ключ». -2. Нужно хотя бы одно активное подключение. Затем: `active`, `activatedAt = сейчас`, `expiresAt = сейчас + период`, письмо `key_ready`. +2. Нужно хотя бы одно активное подключение. Затем: `active`, `activatedAt = сейчас`, `expiresAt = сейчас + период` плюс дни продлений без оплаты, которые ждали выдачи (раздел 6.4), письмо `key_ready`. 3. Список «Очередь выдачи» в админке: ключи `provisioning` по времени создания; локации `on_demand` помечены сроком «24 часа». ## 9. Ручные операции в админке @@ -92,7 +100,7 @@ | Корректировка баланса | Движение `adjustment` ±сумма, комментарий обязателен. Цепочка действий не запускается | | Возврат на баланс | На заказе: сумма (не больше оплаченной минус возвращённое), галочка «Отменить ключ». Движение `refund`, заказ `refunded` или `partially_refunded`, ключ `cancelled`, если отмечено. Письмо `refund_issued` | | Выдать ключ | `provisioning` → `active` (раздел 8) | -| Продлить вручную | Продлить ключ на N дней без оплаты (компенсация) | +| Продлить вручную | Продлить ключ на N дней без оплаты (компенсация, раздел 6.4), комментарий обязателен | | Заблокировать | Пользователь `blocked`: войти нельзя. Ключ `suspended` | Все операции пишутся в журнал действий. @@ -108,7 +116,11 @@ 1. Привязка: при регистрации, если есть cookie `vx_ref` (ставит `my.<домен>/r/<код>`, живёт 60 дней) или параметр `/start <код>` в боте. Код того же сайта, не свой. Создаётся приглашение `pending`. 2. Условие: оплачен первый заказ `new_key` приглашённого. Пополнение баланса условием не считается. 3. Проверка: совпадает хэш IP регистрации приглашённого и пригласившего → `review`, решает администратор. Иначе — начисление. -4. Начисление: движение `referral_bonus` обоим, в валюте каждого, сумма из `sites.referralBonus`. Нет суммы для валюты — бонус не начисляется, в журнале предупреждение. Приглашение `rewarded`, письма `referral_reward`. +4. Начисление — дни, а не деньги ([D-025](decisions/D-025-referral-days.md)): обоим по `sites.referralBonusDays` (по умолчанию 30) продлением без оплаты (раздел 6.4). Приглашение `rewarded`, письма `referral_reward` обоим. + a. Приглашённому — на ключ из заказа, который выполнил условие. + b. Пригласившему — на последний купленный ключ в статусе `provisioning`, `active` или `grace`. Такого нет — дни ждут и добавляются к следующему ключу пригласившего, когда он будет создан. +5. Возврат по заказу приглашённого бонус пригласившего не отменяет; злоупотребления разбирает администратор. +6. Крайние случаи (сколько дней приглашённому, какой ключ пригласившего, возврат) — предложение по Q-19, см. D-025. ## 12. Уведомления diff --git a/docs/07-frontend.md b/docs/07-frontend.md index 6e77b1e..2871594 100644 --- a/docs/07-frontend.md +++ b/docs/07-frontend.md @@ -93,6 +93,7 @@ 14. Кабинет mass: подписи навигации те же, что у dedicated («Список ключей», «Купить ключ», «Ключи»). Подписи макета MassApp («Подключения», «Выбрать страну», «Подписка») не используем (пункт 4). 15. Ширина надписи логотипа — по содержимому: в макете она подогнана под русское название. 16. 404. При `notFound()` Next 16 отдаёт пустую оболочку (``), а страница 404 в оформлении бренда отрисовывается в браузере; статус ответа — 404. Серверную отрисовку 404 и 500 решаем на этапе 1b вместе с их оформлением: вариант — `global-not-found.js`. +17. Реферальная программа (1g): бонус — дни срока ключа, а не деньги ([D-025](decisions/D-025-referral-days.md)). Текст под ссылкой «Месяц начисляется вам и другу… и применяется к ближайшему списанию» заменяем на условия в днях. В истории платежей строка «Реферальный бонус» показывает «+30 дней» вместо «+2 499 ₽». Счётчик «Месяцев начислено» и статусы приглашённых остаются. ## 8. Состояния, которых нет в макетах diff --git a/docs/08-integrations.md b/docs/08-integrations.md index bbf44de..a828db1 100644 --- a/docs/08-integrations.md +++ b/docs/08-integrations.md @@ -33,7 +33,8 @@ 1. Один экземпляр на отдельном VPS обслуживает оба бренда. У каждого бренда свои входящие каналы: a. виджет на сайте и в кабинете; - b. бот поддержки в Telegram, подключённый к Chatwoot напрямую как Telegram-канал. + b. бот поддержки в Telegram, подключённый к Chatwoot напрямую как Telegram-канал; + c. почта `help@…`: email-канал Chatwoot забирает письма из ящика на почтовом хостинге по IMAP и отвечает через его SMTP ([09-infrastructure.md](09-infrastructure.md), раздел 6). 2. Для вошедших пользователей виджет получает идентификацию с HMAC-подписью: оператор видит, кто пишет, а подделать её нельзя. Передаём атрибуты: сайт, id пользователя, email, число ключей. 3. Почему бот поддержки отдельный: Telegram отдаёт обновления одного бота только одному получателю. Основной бот принадлежит нашему приложению, бот поддержки — Chatwoot. diff --git a/docs/09-infrastructure.md b/docs/09-infrastructure.md index 8cc5089..fe94881 100644 --- a/docs/09-infrastructure.md +++ b/docs/09-infrastructure.md @@ -73,11 +73,11 @@ vx — личный пользователь администратора, к п 1. `smtp_bind_address = 169.58.76.183`, `myhostname = fr-ub2gs3.veilix.cc`: письма уходят с IP, у которого обратная запись совпадает с прямой. 2. OpenDKIM: ключи для veilix.app и veilixvpn.com (селектор, напр. `mail`). 3. DNS для каждого из двух доменов: - a. SPF: `v=spf1 ip4:169.58.76.183 -all` (плюс SMTP-провайдер, если используем); + a. SPF: `v=spf1 ip4:169.58.76.183 include:<почтовый хостинг> -all`. Хостинг нужен, потому что ответы поддержки уходят через него (пункт 5); плюс SMTP-провайдер, если используем; b. DKIM: TXT `mail._domainkey.<домен>` с открытым ключом; c. DMARC: `_dmarc.<домен>` — сначала `v=DMARC1; p=none; rua=mailto:…`, после проверки — `p=quarantine`. 4. Проверить, что хостер не закрывает исходящий порт 25 и что 169.58.76.183 нет в чёрных списках. -5. Приём почты поддержки (`help@…`) — где ящик, вопрос Q-16. +5. Почта поддержки (Q-16): ящики `help@veilix.app` и `help@veilixvpn.com` — на внешнем почтовом хостинге, MX-записи доменов указывают на него (пользователь, R-18). На этапе 2 Chatwoot забирает письма по IMAP и отвечает через SMTP хостинга. Доменов брендов в `mydestination` Postfix нет и быть не должно: тогда письма администраторам на `help@…` уходят на хостинг, а не остаются на этом сервере. ## 7. Бэкапы @@ -90,7 +90,7 @@ vx — личный пользователь администратора, к п | № | Этап | Задача | Статус на 21.10.2026 | |---|---|---|---| | R-00 | 0 | Переезд разработки: перенести `/home/vx/veilix_dev` в `/home/vx_dev/veilix_dev` (владелец vx_dev); SSH-ключ vx_dev для VSCode; Claude Code под vx_dev | сделано | -| R-01 | 0 | Репозиторий в Gitea; SSH-ключ vx_dev с правом push; для vx_prod — ключ только на чтение (deploy key) | в работе: репозиторий `ssh://git@gte.apr.sh:26789/andreypr207/veilix.app.git` подключён как `origin`; ключ vx_dev — `~/.ssh/veilix_gitea_ed25519`, ждёт добавления в Gitea как deploy key с правом записи; ключ vx_prod — на этапе 1h | +| R-01 | 0 | Репозиторий в Gitea; SSH-ключ vx_dev с правом push; для vx_prod — ключ только на чтение (deploy key) | vx_dev — сделано: `origin` — `ssh://git@gte.apr.sh:26789/andreypr207/veilix.app.git`, ключ `~/.ssh/veilix_gitea_ed25519` добавлен в Gitea как deploy key с правом записи, `main` отправлен. Ключ vx_prod — на этапе 1h | | R-02 | 1a | PostgreSQL: роли `vx_dev`, `vx_prod`, `umami` и их базы (раздел 5) | сделано и проверено: vx_dev входит в `veilix_dev`, в `veilix_prod` — отказ | | R-03 | 1a | pnpm: `corepack enable pnpm` | сделано | | R-04 | 1a | Пользователи vx_dev, vx_prod, umami; `loginctl enable-linger` для них | сделано | @@ -106,4 +106,5 @@ vx — личный пользователь администратора, к п | R-14 | 4 | Аккаунты платёжных систем (сначала тестовые), адреса вебхуков | | | R-15 | 1h | По желанию (Q-18): sudo-правило, разрешающее vx_dev запускать от имени vx_prod только команду выкладки | | | R-16 | 1h | По желанию: правило nftables — к 127.0.0.1:3000 подключаются только `www-data` (nginx) и vx_prod ([10-security.md](10-security.md), раздел 7) | | -| R-17 | 1a | nginx dev: отдельный `location /_next/webpack-hmr` с `proxy_read_timeout 1h` в server-блоках dev-хостов и `dev.plc.veilix.cc`. Иначе WebSocket горячей перезагрузки закрывается каждые 30 секунд (`proxy_read_timeout 30` в `nginx.conf`) | в работе: есть в `dev.plc` и `dev.tg.*`; нужен ещё в блоке `dev.veilix.app`, `dev.veilixvpn.com`, `dev.my.*` | +| R-17 | 1a | nginx dev: отдельный `location /_next/hmr` с `proxy_read_timeout 1h` во всех трёх server-блоках dev на 443: `dev.plc.veilix.cc`, сайты и кабинеты, `dev.tg.*`. В Next 16.4 WebSocket горячей перезагрузки — `/_next/hmr`, и сервер может молчать дольше 30 секунд, а `nginx.conf` закрывает соединение после 30 секунд тишины (`proxy_read_timeout 30`) | в работе: сначала в задаче был старый путь `/_next/webpack-hmr`. Блоки с ним в `dev.plc` и `dev.tg.*` не срабатывают — их переименовать; в блоке сайтов и кабинетов блока нет | +| R-18 | 1h | Почта поддержки (Q-16): ящики `help@veilix.app` и `help@veilixvpn.com` на почтовом хостинге, MX-записи доменов; хостинг — в SPF (раздел 6). До этого письма администраторам и заявки видны только в админке и журнале писем | | diff --git a/docs/decisions/D-025-referral-days.md b/docs/decisions/D-025-referral-days.md new file mode 100644 index 0000000..c2a29d0 --- /dev/null +++ b/docs/decisions/D-025-referral-days.md @@ -0,0 +1,34 @@ +# D-025. Реферальный бонус — дни срока ключа + +Статус: принято, 21.10.2026. Заменяет денежный бонус из макета (Q-02). Крайние случаи — предложение по Q-19. + +## Контекст + +В макете «месяц в подарок» начисляется обоим деньгами на баланс: 2 499 ₽ у dedicated и 699 ₽ у mass. Для этого нужна сумма в каждой валюте каждого сайта, а бонус уходит в ближайшее списание, в том числе за другой тариф. + +## Решение + +1. Бонус — дни срока ключа, без денег. Число дней — настройка сайта `referralBonusDays`, по умолчанию 30. +2. Приглашённый получает дни на ключ, который купил заказом, выполнившим условие (первый оплаченный `new_key`). +3. Пригласивший получает дни на свой последний купленный ключ. +4. Условие, проверка самоприглашений и границы сайта прежние ([04-billing.md](../04-billing.md), раздел 11). +5. Начисление — продление без оплаты. Тот же механизм у «Продлить вручную» в админке: запись в коллекции `key-extensions` ([04-billing.md](../04-billing.md), раздел 6.4). + +## Крайние случаи (предложение, Q-19) + +1. «Месяц» приглашённому — тоже 30 дней: одна настройка на обе стороны. +2. Ключ ещё готовится (`provisioning`) — дни добавятся при выдаче: срок идёт с выдачи. +3. «Последний ключ» — купленный последним среди неистёкших и неотменённых (`provisioning`, `active`, `grace`). Ключ в льготном периоде после начисления снова `active`. +4. У пригласившего такого ключа нет — дни ждут и добавятся к его следующему ключу. +5. Возврат по заказу приглашённого бонус пригласившего не отменяет; злоупотребления разбирает администратор. + +## Рассмотренные варианты + +1. Деньги на баланс, как в макете: суммы в каждой валюте, бонус уходит на любое списание. +2. Дни на ключ с ближайшей датой окончания: пользователь выбрал последний ключ. + +## Последствия + +1. В `sites` вместо сумм по валютам `referralBonus` — число дней `referralBonusDays`. Тип движения `referral_bonus` в `ledger-entries` не нужен. +2. Новая коллекция `key-extensions` — продления без оплаты: бонус и компенсация ([03-data-model.md](../03-data-model.md), раздел 5.4). У приглашения — ключи обеих сторон. +3. Кабинет: условия на странице «Реферальная программа» — в днях; в истории платежей строка «Реферальный бонус» показывает «+30 дней» вместо суммы ([07-frontend.md](../07-frontend.md), раздел 7). diff --git a/docs/decisions/README.md b/docs/decisions/README.md index e162111..7d8054c 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -28,3 +28,4 @@ | [D-022](D-022-proxy-routing.md) | Proxy: внутренние маршруты по хосту, домены из внутреннего эндпоинта | принято | | [D-023](D-023-site-locales.md) | Языки сайта — поле `sites.locales`, домены показывают их часть | принято | | [D-024](D-024-locale-redirect.md) | Перенаправление на выбранный язык | принято | +| [D-025](D-025-referral-days.md) | Реферальный бонус — дни срока ключа, а не деньги | принято | diff --git a/docs/stages/stage-1a-foundation.md b/docs/stages/stage-1a-foundation.md index 768ad17..b1ca598 100644 --- a/docs/stages/stage-1a-foundation.md +++ b/docs/stages/stage-1a-foundation.md @@ -50,10 +50,10 @@ Root: R-00…R-06 ([09-infrastructure.md](../09-infrastructure.md), раздел 4. В `sites` сделаны только `code`, `name`, `orderPrefix` и `locales`; остальные поля добавят этапы, которые ими пользуются ([03-data-model.md](../03-data-model.md), раздел 3.1). 5. Таблицы переводов Payload — с суффиксом `_l10n`; для вопросов push в dev — `pnpm db:reset`. 6. Миграции: механизм проверен на пустой схеме; начальную миграцию создаём перед первой выкладкой (1h). -7. Mass в USD — временные цены $6.99 и $69.90 до решения Q-02. +7. Mass в USD — $6.99 и $69.90: сначала временные, утверждены в Q-02. 8. Адаптация макетов: переключатели для 5 языков и валюты, «Политика cookies» в подвале, подписи кабинета mass как у dedicated ([07-frontend.md](../07-frontend.md), раздел 7). Осталось: -1. R-17 (root): `location /_next/webpack-hmr` с большим `proxy_read_timeout` в nginx dev — иначе горячая перезагрузка переподключается каждые 30 секунд. +1. R-17 (root): `location /_next/hmr` с большим `proxy_read_timeout` в nginx dev — иначе горячая перезагрузка переподключается после 30 секунд тишины. 2. На этап 1b: серверная отрисовка 404 и 500 ([07-frontend.md](../07-frontend.md), раздел 7.16), цвета карты, пересборка страниц по событию из Payload. diff --git a/docs/stages/stage-1b-marketing.md b/docs/stages/stage-1b-marketing.md index 7b9b6fd..1c5d21b 100644 --- a/docs/stages/stage-1b-marketing.md +++ b/docs/stages/stage-1b-marketing.md @@ -23,7 +23,7 @@ ## 4. Перед стартом -Q-02 (цены mass в USD), Q-08 (email поддержки), Q-09 (часовой пояс), Q-16 (ящик поддержки). +Открытых вопросов нет: Q-02, Q-08, Q-09 и Q-16 решены 21.10.2026 ([00-index.md](../00-index.md), раздел 4.1). ## 5. Готово, когда diff --git a/docs/stages/stage-1d-billing.md b/docs/stages/stage-1d-billing.md index d7708d4..470fb16 100644 --- a/docs/stages/stage-1d-billing.md +++ b/docs/stages/stage-1d-billing.md @@ -8,11 +8,11 @@ ## 2. Входит -1. Коллекции `orders`, `payments`, `ledger-entries`, `payment-methods` (структура), `keys` (поля и статусы), `audit-log`. +1. Коллекции `orders`, `payments`, `ledger-entries`, `payment-methods` (структура), `keys` (поля и статусы), `key-extensions`, `audit-log`. 2. `core/billing`: `processPayment`, оплата с баланса, задачи `renewals`, `expiry`, `reminders`, `reconcile`; блокировки и уникальные ограничения ([04-billing.md](../04-billing.md)). 3. `/order` по макетам обоих брендов: шаг «Почта» из 1c, шаг «Оплата» (с баланса; «Заказ создан, ожидает оплаты», когда платёжных систем нет), согласие с условиями, сводка из тарифа, локации и валюты. 4. `/buy` в кабинете: dedicated — сетка локаций с ценами, mass — «Месяц» и «Год». -5. Админка: ручной платёж, корректировка баланса, возврат на баланс, продление вручную, блокировка, «Очередь выдачи»; всё — в журнал действий. +5. Админка: ручной платёж, корректировка баланса, возврат на баланс, продление вручную (запись в `key-extensions`, [04-billing.md](../04-billing.md), раздел 6.4), блокировка, «Очередь выдачи»; всё — в журнал действий. 6. Шаблоны писем на RU и EN: `order_paid`, `key_provisioning`, `renewal_reminder`, `expiry_reminder`, `renewal_succeeded`, `renewal_failed`, `grace_started`, `key_expired`, `topup_succeeded`, `refund_issued`, `admin_new_order`, `admin_provisioning_needed`. ## 3. Не входит diff --git a/docs/stages/stage-1e-keys.md b/docs/stages/stage-1e-keys.md index 4ab904f..53f65a5 100644 --- a/docs/stages/stage-1e-keys.md +++ b/docs/stages/stage-1e-keys.md @@ -9,7 +9,7 @@ ## 2. Входит 1. Коллекция `connections` с шифрованием содержимого ([03-data-model.md](../03-data-model.md), 5.2). -2. Админка: на странице ключа — добавление подключений (WG/AWG — файлом или текстом, ссылка Happ, ссылка Xray, RU-адрес, номер устройства, страна), действие «Выдать ключ», список «К отключению». +2. Админка: на странице ключа — добавление подключений (WG/AWG — файлом или текстом, ссылка Happ, ссылка Xray, RU-адрес, номер устройства, страна), действие «Выдать ключ» (к сроку добавляются дни продлений без оплаты, ждавшие выдачи), список «К отключению». 3. Страница ключа в кабинете: срок, галочка автопродления, «Продлить сейчас», WG-конфиги (скачать, QR), Happ/Xray (копировать, QR); для mass — WG по странам: конфиги, которые добавил администратор; нужна другая страна — ссылка на поддержку. 4. Состояния: готовится, льготный период, истёк, нет подключений. 5. Содержимое конфигов отдаётся отдельным запросом только владельцу, в HTML страницы не попадает. QR строится в браузере из полученного содержимого. diff --git a/docs/stages/stage-1g-referral.md b/docs/stages/stage-1g-referral.md index 98adeab..440ba75 100644 --- a/docs/stages/stage-1g-referral.md +++ b/docs/stages/stage-1g-referral.md @@ -4,20 +4,25 @@ ## 1. Цель -«Приведите друга — вы оба получите по месяцу в подарок» по правилам [04-billing.md](../04-billing.md), раздел 11. +«Приведите друга — вы оба получите по месяцу в подарок» по правилам [04-billing.md](../04-billing.md), раздел 11. Бонус — дни срока ключа, а не деньги ([D-025](../decisions/D-025-referral-days.md)). ## 2. Входит 1. Реферальные коды пользователей. 2. Маршрут `my.<домен>/r/<код>`: cookie `vx_ref` на 60 дней и переход на сайт бренда. 3. Привязка при регистрации; коллекция `referrals`. -4. Бонус обоим после первой оплаты заказа `new_key` приглашённого; статус `review` при совпадении хэша IP. -5. Суммы бонуса по валютам в `sites`. -6. Страница `/referral` по макету: приглашено, начислено, ожидает; ссылка с копированием; приглашённые с замаскированной почтой. +4. Бонус обоим после первой оплаты заказа `new_key` приглашённого: дни к ключам через `key-extensions`; статус `review` при совпадении хэша IP. +5. Число дней бонуса в `sites` (`referralBonusDays`). +6. Страница `/referral` по макету: приглашено, начислено месяцев, ожидает; ссылка с копированием; приглашённые с замаскированной почтой. Условия под ссылкой — про дни, а не про списание ([07-frontend.md](../07-frontend.md), раздел 7). 7. Шаблон `referral_reward` на RU и EN; список приглашений в админке. -## 3. Готово, когда +## 3. Перед стартом -1. A делится ссылкой → B регистрируется → B оплачивает первый заказ → обоим начислен бонус. -2. Самоприглашение с того же IP попадает в `review`. -3. Ссылка другого бренда не засчитывается. +Q-19 — крайние случаи бонуса днями. + +## 4. Готово, когда + +1. A делится ссылкой → B регистрируется → B оплачивает первый заказ → к ключу B и к последнему ключу A добавлено по 30 дней. +2. У A нет подходящего ключа — дни добавляются к следующему ключу A. +3. Самоприглашение с того же IP попадает в `review`. +4. Ссылка другого бренда не засчитывается. diff --git a/docs/stages/stage-2-support-crm.md b/docs/stages/stage-2-support-crm.md index 1ecdd38..ec75948 100644 --- a/docs/stages/stage-2-support-crm.md +++ b/docs/stages/stage-2-support-crm.md @@ -6,12 +6,12 @@ 1. Chatwoot ([08-integrations.md](../08-integrations.md), раздел 4): виджет на сайтах и в кабинетах, отдельные входящие каналы для каждого бренда; идентификация вошедших пользователей с HMAC; категория в баннере cookies. 2. Боты поддержки обоих брендов подключены к Chatwoot как Telegram-каналы. -3. Почта поддержки (Q-16) — по решению, например email-канал Chatwoot. +3. Почта поддержки: email-каналы Chatwoot для ящиков `help@…` на почтовом хостинге — письма по IMAP, ответы через SMTP хостинга (Q-16). 4. Twenty ([08-integrations.md](../08-integrations.md), раздел 5): заявки уходят через API фоновой задачей с повторами, `leads.crmId`. ## 2. Перед стартом -Root: R-12 — VPS, Chatwoot, Twenty, ключи API, боты поддержки в BotFather. +Root: R-12 — VPS, Chatwoot, Twenty, ключи API, боты поддержки в BotFather. Ящики поддержки (R-18) и их данные IMAP и SMTP. ## 3. Готово, когда diff --git a/src/seed/data.ts b/src/seed/data.ts index 5f1af6a..c2c296f 100644 --- a/src/seed/data.ts +++ b/src/seed/data.ts @@ -120,7 +120,7 @@ export const DEDICATED_TARIFFS: (TariffSeed & { tier: Tier })[] = [ /** * Mass: все страны тарифа, первый период стоит как продление. - * Цены в USD временные, пока не решён Q-02 (docs/00-index.md, раздел 4.2). + * Цен в USD в макете нет, их задал Q-02 (docs/00-index.md, раздел 4.1). */ export const MASS_TARIFFS: TariffSeed[] = [ {