# Дорожная карта разработки

Рабочий план платформы лояльности: один движок, который встраивается по API в собственные продукты
компании и продаётся как самостоятельный SaaS. Основание плана — исследование
[docs/research/report.md](research/report.md), ключевые решения зафиксированы в [docs/adr](adr/).

## Как устроен план

- Этапы идут по порядку. Каждый заканчивается работающим инкрементом: код, тесты, документация и зелёный CI.
- Задача считается сделанной (Definition of Done), когда:
  - написаны unit- и feature-тесты, включая негативные сценарии и межтенантную изоляцию;
  - Pint, Larastan и Deptrac проходят без ошибок;
  - публичный контракт (API, события, схема БД) описан, а если решение необратимое — оформлен ADR;
  - обновлены документы этапа и `CHANGELOG`.
- Порядок этапов 8–10 гибкий. Этап 11 обязателен до пилота.
- Версия для пилота — этапы 0–7, основа этапа 8, встраивание в один собственный продукт (9.1) и этап 11.

Статусы: ⬜ не начато · 🟡 в работе · ✅ готово.

---

## Этап 0. Фундамент ✅

**Цель:** инженерная платформа, на которой строятся все модули, и доказанная тестами изоляция тенантов.

- ✅ 0.1 Окружение: PHP 8.5, PostgreSQL 17 (ICU ru-RU), Laravel 13, скрипты `scripts/dev`, git.
- ✅ 0.2 Качество: Pint со `strict_types`, Larastan (уровень 8), Pest 5 с архитектурными тестами, Deptrac, composer-скрипты `lint`, `analyse`, `test`, `check`.
- ✅ 0.3 Модульный монолит: `internachi/modular`, соглашения о структуре модуля, публичный API модуля (`Contracts`, `Events`), карта зависимостей в `deptrac.php`.
- ✅ 0.4 Платформа БД: роли `owner`, `app`, `system` и групповые роли RLS; команда `db:bootstrap`; соединения `pgsql`, `pgsql_owner`, `pgsql_system`; хелперы миграций (`Rls::enable`, `Rls::appendOnly`, `TenantSchema`); тесты идут ролью приложения.
- ✅ 0.5 Ядро: UUIDv7, `Money`, `IntMath`, `Allocator`, `Clock`, ошибки `application/problem+json`, Request ID, маскирование ПДн в логах. Курсорная пагинация перенесена на этап 1 — вместе с первым списочным API.
- ✅ 0.6 Мультиарендность: контекст тенанта в приложении и в сессии PostgreSQL, трейт `BelongsToTenant`, сброс контекста в каждом запросе (Octane), передача контекста в задачи очереди, межтенантные тесты.
- ✅ 0.7 Оргструктура тенанта: партнёры, тенанты, бренды, юрлица (ИНН, КПП, ОГРН), точки продаж с часовым поясом, кассы.
- ✅ 0.8 CI в GitLab: `composer validate` и `audit`, Pint, Larastan, Deptrac, Pest на PostgreSQL 17 (конфигурация готова, проверится первым пайплайном).
- ✅ 0.9 Документация: дорожная карта, ADR 0001–0008, архитектура, инструкция по окружению, `CLAUDE.md`, README, CHANGELOG.

**Готово, когда:** CI зелёный; роль приложения видит только данные своего тенанта; без контекста тенанта запросы возвращают ноль строк; системная роль видит всё; Deptrac без нарушений.

## Этап 1. Леджер баллов ✅

**Цель:** корректный и проверяемый учёт баллов — единственный источник балансов.

- ✅ 1.1 Программа лояльности (модуль `programs`): тенант, название, неизменяемые часовой пояс и валюта (триггер БД), статус.
- ✅ 1.2 Типы баллов: код, точность, стоимость в копейках, политика долга, приоритет списания; условия неизменяемы.
- ✅ 1.3 Счета участника (`available`, `pending`, `held`) и системные счета (эмиссия, погашение, сгорание, корректировки).
- ✅ 1.4 Проводки: единый писатель, сбалансированность (отложенный триггер), идемпотентность, атомарное обновление балансов, CHECK на отрицательный баланс, append-only для проводок и журнала партий, составные FK на тип баллов.
- ✅ 1.5 Партии: создание при начислении, списание от ближайшего сгорания, журнал движений, возврат в те же партии, погашение долга.
- ✅ 1.6 Операции: начисление (сразу или с активацией), резерв, проведение целиком или частично, отмена, истечение резерва, сгорание, восстановление (в исходные партии или в новую), сторно начисления с тремя политиками, корректировки с кодом причины.
- ✅ 1.7 Запросы: балансы (доступно, ожидает, в резерве, можно потратить, ближайшее сгорание), история с курсорной пагинацией.
- ✅ 1.8 Фоновые команды `ledger:activate-due`, `ledger:expire-due`, `ledger:release-expired-holds` по расписанию; поиск работы — системной ролью, обработка — от имени тенанта. Обработка одним SQL на все партии участника; массовая пакетная обработка — при росте объёмов.
- ✅ 1.9 Сверка `ledger:reconcile` (ежедневно): балансы, сбалансированность, остатки партий, суммы по участнику.
- ✅ 1.10 Тесты: операции и крайние случаи, гарантии БД, рандомизированные прогоны с проверкой инвариантов после каждого шага, параллельные списания через две сессии PostgreSQL, команды обслуживания. Базовый замер `ledger:benchmark`: p50 15–24 мс, ~46 операций/с на процесс (Windows, локальная БД).

**Готово, когда:** инварианты обеспечены на уровне БД; сверка показывает ноль расхождений после рандомизированных прогонов; параллельные списания никогда не уводят баланс ниже допустимого. — выполнено.

## Этап 2. Участники, идентификация и согласия ✅

- ✅ 2.1 Участники программы (модуль `members`): статусы (активен, заморожен, заблокирован, обезличен), профиль, дополнительные атрибуты. Объединение дублей перенесено на этап 4: ему нужен перенос баллов между участниками в леджере.
- ✅ 2.2 Защита ПДн: телефон и email зашифрованы (XChaCha20-Poly1305, контекст «тенант | таблица | участник | поле»), поиск по слепому индексу HMAC, отдельному для каждого тенанта; маскированные значения для касс и интерфейсов; обезличивание сохраняет идентификатор для леджера. Ключи — `php artisan pii:generate-keys`, поддерживается ротация.
- ✅ 2.3 Идентификаторы: телефон (+7, E.164), пластиковые карты (выпуск номеров, привязка, блокировка), внешний ID продукта-носителя, одноразовый 8-значный код для кассы (для QR).
- ✅ 2.4 Согласия (модуль `consents`): версии документов (правила, согласие на ПДн отдельным документом, реклама по каналам) и неизменяемый журнал согласий и отзывов с доказательствами.
- ✅ 2.5 Коды подтверждения (модуль `verification`): хранится только HMAC кода, срок 5 минут, 5 попыток, паузы 60/120/300 с, лимиты на номер (5 в час, 10 в сутки), IP и дневной лимит тенанта, только номера +7, автоматическая остановка при низкой доле подтверждений (SMS pumping), код привязан к цели, номеру и контексту (сумма, касса) и расходуется один раз. Лимиты по устройству и ключу доступа — на этапе 4 вместе с ключами касс.
- ✅ 2.6 Регистрация одной транзакцией: проверка кода, участник, согласия, карта. HTTP-эндпоинты для кассы, сайта и продукта-носителя — на этапе 4.

**Готово, когда:** поиск по любому идентификатору быстрее 20 мс (p95); ПДн зашифрованы в БД; доказательства согласий хранятся; лимиты OTP подтверждены тестами. — выполнено (поиск — один запрос по уникальному индексу; замер p95 — в нагрузочных тестах этапа 4).

## Этап 3. Программа и движок правил ✅

Этап идёт тремя частями с отдельными коммитами: 3a — каталог, правила программы и базовый расчёт чека
(готово); 3b — акции и промокоды (готово); 3c — уровни и механики (готово). **3c выполняется
после этапа 4:** уровни, welcome, рефералы, штампы и сгорание «от последней активности» считаются от
подтверждённых покупок (антифрод: бонусы — только после первой подтверждённой покупки), поэтому им нужна
модель чека.

- ✅ 3.1 Настройки программы (модуль `rules`): ✅ черновик и публикация; ✅ процент начисления на оплаченное деньгами (базисные пункты, округление вниз один раз на чек); ✅ задержка активации; ✅ сгорание через N дней от активации в конце дня по часовому поясу программы или «никогда»; ✅ лимиты списания (доля строк, принимающих баллы, остаток на единицу товара, минимальная сумма чека); ✅ политики возвратов (потраченные баллы, возврат в новую партию); ✅ сгорание в календарную дату (день и месяц через 0–5 лет после активации) и после N дней без покупок (скользящие партии: каждая подтверждённая покупка продлевает срок всех таких баллов участника, уже просроченные не возвращаются); те же политики у бонусов. Списание нескольких типов баллов по приоритету перенесено в этап 12.
- ✅ 3.2 Каталог (модуль `catalog`): товары и категории по кодам мерчанта с идемпотентным импортом; признаки «ограниченный товар» (табак: ни скидок, ни баллов, ни акций), «не начислять», «не списывать», «не участвует в акциях» наследуются от всех категорий-предков; минимальная цена единицы (МРЦ алкоголя). Ставка НДС движку не нужна — её передаёт касса.
- ✅ 3.3 Уровни: настройка уровней — часть версионируемых правил программы (до 10 уровней, первый — начальный без порогов; пороги по сумме оплат деньгами и числу чеков, нужны оба; свой процент начисления, который умножают множители акций; уровень доступен условиям акций как `member.tier`); окно квалификации — скользящее (N дней) или календарный год в часовом поясе программы; повышение сразу после подтверждённой покупки, в том числе через ступень; мягкое понижение — пока покупки квалифицируют, срок удержания продлевается, после его окончания `tiers:review` (ежечасно) понижает на одну ступень и начинает новый срок; ручная фиксация уровня до даты с причиной (ни покупки, ни пересмотр её не меняют); возвраты и аннулирования вычитаются из дня покупки, но уровень сами не понижают. Модуль `tiers`: дневная статистика покупок участника и состояние уровня; покупки считаются и до включения уровней. Runtime API показывает уровень и сколько не хватает до следующего. ⬜ Окно «от годовщины участника» — по запросу клиента; события смены уровня — с outbox (этап 5), ручная фиксация через API — с Management API (этап 5).
- ✅ 3.4 Акции: условия — строгое подмножество JsonLogic с белым списком переменных чека, покупки и участника (сумма и состав чека, канал, точка, дата, время, день недели, уровень, сегменты, число покупок); выбор строк условием по SKU, категориям, количеству и сумме; эффекты — скидка процентом, суммой на чек или на единицу, спеццена, баллы процентом, фиксом на чек или на единицу, множитель базового начисления; группы совмещения («первая по приоритету», «лучшая для покупателя»), эксклюзивность, приоритеты; расписание (период, дни недели, часы, в том числе через полночь); каналы и точки; «только участникам»; бюджет акции и лимит на участника за день, неделю, месяц или всю акцию. Жизненный цикл: черновик → версия → активна ⇄ пауза → архив. ⬜ Наборы «N по цене M» и подарки — отдельной задачей позже.
- ✅ 3.5 Промокоды: общие (с лимитом использований и лимитом на участника), уникальные одноразовые (партии до 100 000, алфавит без похожих символов, выгрузка), персональные для участника; сроки действия, отключение; проверка перед расчётом с причинами отказа; резерв с блокировками, подтверждение и освобождение по ссылке на чек (идемпотентно). Подключение к кассовому протоколу — этап 4.
- ✅ 3.6 Снапшоты: неизменяемые версии правил программы и версии акций; снапшот программы хранит базовые правила и ссылки на версии активных акций, хэш — от канонического JSON (публикация без изменений не создаёт версию); чек считается по снапшоту момента покупки — офлайн-чеки получают правила своего времени. Ссылка на снапшот в чеке — этап 4.
- ✅ 3.7 Движок расчёта: чистая детерминированная функция без обращений к БД; конвейер «проверка и исключения каталога → выбор акций → скидки акций → списание баллов → базовое начисление → баллы акций»; нижняя граница строки (остаток на единицу и МРЦ) для скидок и списания; распределение по строкам методом наибольшего остатка (с переносом на другие строки для фиксированных сумм); списывается наименьшее число баллов, покрывающее скидку в целых копейках; объяснение расчёта кодами; 18 «золотых» векторов с ручным расчётом (`app-modules/rules/tests/golden`) и property-тесты. Замер `php artisan rules:benchmark`: 50 строк и 20 акций — p50 5,3 мс, p95 8,2 мс (Windows, без OPcache). Dry-run-эндпоинт — этап 4.
- ✅ 3.8 Механики (модуль `bonuses`, настройки — часть версионируемых правил программы): ✅ welcome-бонус сразу при регистрации или после первой подтверждённой покупки от минимальной суммы в течение N дней; ✅ «приведи друга» — у каждого участника свой реферальный код, при регистрации по коду бонусы пригласившему и другу начисляются после первой покупки друга от минимальной суммы, не больше N наград пригласившему в календарный месяц, заблокированному — ничего; ✅ день рождения — баллы за N дней до даты (29 февраля — 28-го в невисокосные годы) участникам со стажем от N дней, раз в год, `bonuses:birthdays` ежечасно по часовому поясу каждой программы; переменная условий акций `member.birthday_offset` (дней до или после ближайшего дня рождения) для скидок и множителей «в день рождения ±N дней»; у бонусов свои задержка активации и срок жизни; возврат или аннулирование чека, который принёс бонус, забирает его по политике программы. ✅ Штампы: до 5 карт в программе, у каждой свой тип баллов в леджере (целые, со своим сроком жизни), товары — условием по строке, цель N штампов; штамп за каждую целую единицу товара, полная карта делает бесплатной самую дешёвую единицу в чеке (после скидок акций, не ниже нижней границы строки); штампы бесплатных единиц резервируются вместе с чеком, начисленные штампы проводятся при подтверждении, отмена и возвраты их возвращают; офлайн-чеки только копят штампы; штампы участника видны в Runtime API.

**Готово, когда:** «золотые» векторы проходят детерминированно; объяснение расчёта доступно; чек на 50 строк с 20 акциями считается быстрее 10 мс.

## Этап 4. Обработка чеков (Checkout API) 🟡

Части: 4a — фундамент Runtime API, 4b — модель чека и кассовый протокол (ADR-0009), 4c — участники и
балансы через API, подтверждение списания, офлайн-пакеты, антифрод, 4d — OpenAPI и контрактные тесты,
производительность. Готово всё, кроме прогона k6 на стенде Linux + Octane.

- 🟡 4.1 Модель чека (модуль `processing`): ✅ чек и строки с построчными эффектами и снапшотом правил; состояния `reserved`, `confirmed`, `cancelled`, `voided`, `partially_returned`, `returned`; естественный ключ (касса, бизнес-дата, номер); фискальные признаки (ФН, ФД, ФП); время покупки отдельно от времени записи; данные покупки неизменяемы (триггер БД). Режим скидок, кратных количеству, для касс, которые не умеют делить строку, перенесён в 9.2 (делается под конкретные кассовые интеграции).
- 🟡 4.2 Эндпоинты: ✅ `receipts:calculate`, регистрация с резервом (или сразу с подтверждением), `:confirm`, `:cancel` (отмена резерва или аннулирование), возвраты, просмотр чека; резерв атомарно берёт баллы, бюджеты и лимиты акций и промокоды, подтверждение проводит баллы и начисляет новые, отмена и возврат всё возвращают. ✅ Участники: `members:lookup` (без полного телефона), регистрация с кодом (`members:send-code`, `verifications/{id}:verify`), коды для приложения; баланс и история баллов; `receipts:batch` для офлайн-чеков.
- ✅ 4.3 Подтверждение списания: политика программы `redemption.confirmation` (`none`, `weak_identifiers` по умолчанию, `always`); код приложения — сильный идентификатор, телефон, карта и ручной ввод — слабые; код отправляется на телефон участника с привязкой к кассе и числу баллов, проверяется отдельным вызовом вне транзакции запроса (неверные попытки всегда считаются), чек только потребляет подтверждённый код; id кода сохраняется с чеком.
- ✅ 4.4 Ключи касс (модуль `access`): ключ на кассу и программу, токен `lk_<префикс>.<секрет>` показывается один раз, хранится SHA-256 секрета; поиск по префиксу до определения тенанта — узкой SECURITY DEFINER-функцией; скоупы `receipts`, `members`, `balances`; срок действия, отзыв (действует в пределах минуты кэша), `last_used_at`; команды `api-keys:issue` и `api-keys:revoke` до появления Management API. ⬜ Ротация с периодом перекрытия и HMAC-подпись запросов — с Management API (этап 5).
- ✅ 4.5 HTTP-идемпотентность: `Idempotency-Key` обязателен для изменяющих вызовов; ключ занимается в той же транзакции, что и работа запроса, — сбой не оставляет следов, параллельный дубль ждёт первый запрос и получает его ответ (`Idempotent-Replayed: true`); другой запрос с тем же ключом — 422; хранение 48 часов, очистка `idempotency:prune` ежечасно.
- ✅ 4.6 Лимиты запросов: на ключ и на тенант (конфиг `access.rate_limits`), 429 в формате problem+json с `RateLimit-*` и `Retry-After`; лимитер Runtime API отдельный от будущего Management API.
- 🟡 4.7 Антифрод-контроли: ✅ пауза списания после смены телефона (24 часа), лимит чеков со списанием на участника в сутки, ручной ввод требует кода, кассир и флаг ручного ввода сохраняются с чеком, возврат только по исходному чеку, переводов между участниками нет, лимиты запросов на ключ. ✅ welcome и реферальные бонусы — только после подтверждённой покупки от минимальной суммы и забираются при её возврате, лимит реферальных наград в месяц, бонус ко дню рождения — только участникам со стажем. ⬜ Уведомление о списании со ссылкой «это не я» — этап 7; лимиты кодов подтверждения по ключу доступа.
- ➡️ 4.11 Объединение дублей участников перенесено в этап 6 (инструменты поддержки): нужна операция переноса партий в леджере, перенос карт, и возвраты чеков объединённого участника должны сторнировать баллы у того, к кому они перенесены.
- ✅ 4.8 TTL резервов (30 минут) и автоосвобождение `receipts:expire-reservations` каждую минуту; офлайн-пакеты до 100 чеков обрабатываются задачей в очереди `offline` отдельно от онлайн-потока: чеки до 30 дней, только начисление, без скидочных акций, результат по каждому чеку.
- ✅ 4.9 OpenAPI 3.1 как источник истины (`docs/api/runtime-v1.yaml`): каждый ответ Runtime API в любом тесте и тело каждого успешного запроса сверяются со спецификацией (`Tests\Support\OpenApiContract`), архитектурный тест требует, чтобы маршруты `/api/v1` и операции спецификации совпадали; каталог ошибок — в описаниях ответов. ✅ SDK из спецификации — этап 5 (5.6).
- 🟡 4.10 Производительность: ✅ число запросов к БД — расчёт 10 → 6, регистрация 32 → 20, подтверждение 33 → 25 (вид чека без перечитывания, сроки начисления и точность баллов хранятся с чеком, баланс одним запросом, наступившие партии одним запросом, id системных счетов и собранные снапшоты правил в памяти воркера); ✅ сценарий k6 `tests/load/checkout.js` и данные для него `processing:load-fixtures` (`docs/dev/load-testing.md`). ⬜ Прогон k6 на стенде Linux + Octane с целью p95 ≤ 200 мс и запись результатов; дальнейшее сокращение запросов внутри операций леджера.

**Готово, когда:** сквозные сценарии (частичные возвраты, повторы, офлайн) проходят; базовые показатели нагрузки задокументированы.

## Этап 5. Management API, партнёры и события 🟡

- ✅ 5.1 Партнёры (собственные продукты — тоже партнёры): OAuth 2.0 client credentials (токены на час, ошибки по RFC 6749), выбор мерчанта заголовком `Loyal-Merchant` только среди своих, ключи мерчантов `lm_...`, скоупы, лимиты; команды `partners:create`, `oauth-clients:issue|revoke`, `merchant-keys:issue` до кабинетов. Пользователи партнёра (люди с входом в кабинет) — с кабинетами на этапе 6.
- ✅ 5.2 Провижининг по API (`docs/api/management-v1.yaml`): ✅ мерчанты партнёра (создание с `external_id` против дублей, список, просмотр), вебхуки мерчанта и каталог событий; ✅ программы, типы баллов (в том числе для карт штампов), документы согласий, черновик правил программы целиком (уровни, бонусы, карты штампов, политики сгорания), публикация и версии; бренды, юрлица, точки, кассы и их отключение, ключи Runtime API касс и их отзыв. Сквозной тест: партнёр создаёт мерчанта, настраивает программу и кассу и проводит чек только через API; ✅ акции (черновик, версии, запуск, пауза, архив), промокоды (общие, партии уникальных с выгрузкой, персональные, отключение); участники для бэк-офиса (поиск по телефону, карте, внешнему id, карточка, история баллов, корректировки с кодом причины и необязательным `Idempotency-Key`, фиксация уровня, заморозка, блокировка, обезличивание, бонусы). Шаблоны программ перенесены в кабинет мерчанта (6.3), отчёт об использовании по мерчантам — в биллинг (этап 10).
- ✅ 5.3 Outbox (модуль `outbox`, ADR-0005): доменные события модулей превращаются в интеграционные в той же транзакции; relay системной ролью (`FOR UPDATE SKIP LOCKED`) передаёт их потребителям в тенанте события, после 10 неудач отказывается; каталог событий с версиями данных — `member.enrolled`, `member.tier_changed`, `receipt.confirmed`, `receipt.cancelled`, `receipt.voided`, `receipt.returned`, `bonus.granted`, `bonus.reversed`; очистка опубликованных через 30 дней.
- 🟡 5.4 Вебхуки (модуль `webhooks`, `docs/api/webhooks.md`): ✅ эндпоинты с подпиской на типы событий, секрет показывается один раз и хранится зашифрованным; подпись Standard Webhooks (сверена с эталонным вектором), пример проверки для интеграторов на PHP (проверяется тестами) и Node.js; 11 попыток за ~2,6 суток, `410 Gone` отключает эндпоинт, журнал попыток, повторная отправка, ротация секрета с сутками подписи обоими секретами; защита от SSRF (https, только публичные адреса, соединение с проверенным адресом, без редиректов). ✅ Управление эндпоинтами через Management API. ⬜ Эндпоинты партнёра, получающие события всех его мерчантов, — по запросу.
- ✅ 5.5 Журнал аудита (модуль `audit`, ADR-0010): каждое изменение через Management API (успешное или нет), отказы 403, выдача токенов, чтение данных участников и выгрузка кодов промокодов; исполнитель, мерчант, операция, объекты, запрос без ПДн и секретов, код ответа и ошибки, `request_id`, IP; append-only для всех ролей; записи о мерчанте — под RLS его тенанта, действия партнёра вне мерчантов читает только системная роль. Выгрузка: `GET /audit-entries` (скоуп `audit`, курсор, период, операция) и `audit:export` в NDJSON для оператора. Кабинеты (этап 6) пишут в тот же журнал через `AuditLog::record()`.
- ✅ 5.6 SDK на PHP и TypeScript из OpenAPI; тестовый режим (sandbox) для интеграторов: ✅ каталог кодов ошибок `x-problem-codes` в спецификациях (проверяется по ответам в тестах и по исходникам), полнота ответов 401/403/429, `x-safe-to-retry`; ✅ генератор SDK в закрытом мире (`sdk/generator`, ADR-0011); ✅ песочница (ADR-0012): отдельное развёртывание с `LOYAL_SANDBOX=true`, маркер `test_` в учётных данных и в их хешах, двойники `RealWorldEffect`, только тестовые телефоны и e-mail, код `000000`, заголовок `Loyal-Environment`; ✅ PHP SDK (`loyal/sdk`, PHP 8.2+, без зависимостей) и TypeScript SDK (`@loyal/sdk`, ESM, Node 22+, без зависимостей): ключи идемпотентности и безопасные повторы, токены партнёра, типизированные ошибки с кодами каталога, перебор страниц, проверка подписи вебхуков, помощники для тестов и песочницы; одинаковое поведение закреплено контрактом `sdk/README.md` и эталонными векторами `sdk/conformance` (проверяются по спецификациям); PHP SDK проверен сквозь приложение (путь партнёра и кассы, песочница, подпись настоящей доставки вебхука), пример быстрого старта `sdk/php/examples/quickstart.php`; CI: PHP SDK на PHP 8.2 и TypeScript SDK. Публикация пакетов — после выбора имён, лицензии и реестров (вопрос владельцу продукта).

**Готово, когда:** партнёр создаёт мерчанта и проводит чек только через API (и через SDK); вебхуки проверяются подписью в примере потребителя и в SDK.

## Этап 6. Кабинеты и веб-касса 🟡

Части: 6a — ядро сотрудников (модуль `identity`), 6b — Filament, вход с 2FA, каркас кабинетов и словарь аудита (6b-1 — каркас, 6b-2 — защищённые действия, аудит кабинетов, сотрудники платформы, 6b-3 — почта и забытый пароль, 6b-4 — приглашения),
6c — кабинет мерчанта I (команда, организация, ключи, вебхуки, журнал), 6d — консоль платформы (партнёры, мерчанты и
статусы, доступ поддержки, состояние системы), 6e — кабинет мерчанта II (запуск программы, шаблоны, типы баллов,
правила, документы согласий), 6f — веб-касса, 6g — кабинет мерчанта III (участники, ручные корректировки, сводка),
6h — кабинет мерчанта IV (акции, промокоды, каталог), 6i — кабинет партнёра, 6j — плоскость участника и виджеты,
6k — перенос партий в леджере, 6l — объединение дублей.

- 🟡 6.1 Пользователи и доступ: членство в тенантах, роли и права, 2FA, политика паролей, сессии. ✅ 6a (ADR-0013): учётные записи сотрудников с зашифрованным e-mail, членство на трёх уровнях (платформа, команды партнёров, команды мерчантов под RLS), фиксированные роли и права, доступ сотрудников партнёра к его мерчантам по роли, явный запрет отключённым членством, правила команд в домене (последний владелец с работающей учётной записью, своё членство, владельцами управляют владельцы, сотрудники партнёра командой мерчанта не управляют), последний администратор платформы, разделение обязанностей операторов платформы; Argon2id и политика паролей, атомарные лимиты попыток входа (без блокировки записи) и второго фактора, реестр сеансов с простоем по кабинетам и абсолютным сроком, журнал безопасности учётных записей, обезличивание сотрудника, команды `staff:*`; жёсткие настройки cookie сеанса вне разработки (проверка на запросе) и доверенные прокси. ✅ 6b-1 (ADR-0014): вход в кабинеты с обязательным TOTP (код ±30 секунд, один раз) и кодами восстановления, лимиты платформы на все шаги входа и на подтверждения в профиле, профиль со сменой пароля, «Мои сеансы», «События безопасности». ✅ 6b-2: администрирование учётных записей из консоли (отключить, включить, сбросить 2FA) с правом `accounts.manage`, без действий над собой и без отключения последнего администратора. ✅ 6b-3: «Забыли пароль?» во всех кабинетах (один ответ для любого адреса, письмо из очереди, ссылка на час и один раз, не больше 3 в час на запись, ссылка строится от `APP_URL`), письма сотрудникам на русском, ссылка для смены пароля из консоли, письмо о сбросе 2FA. ✅ 6b-4: приглашения в консоль и в команды мерчантов — ссылка только письмом, 72 часа и один раз, страница приглашения в кабинетах (новый человек задаёт имя и пароль, существующий принимает как есть), правила команды и права пригласившего при принятии, «Приглашения в консоль»; экран команды мерчанта — в 6c, приглашения партнёрам — в 6i.
- 🟡 6.2 Админка платформы (Filament): партнёры, тенанты, тарифы, состояние системы, вход от имени с аудитом. ✅ 6b-1: консоль `/console` для операторов платформы, вне разработки — только на своём хосте (`CONSOLE_DOMAIN`). ✅ 6b-2: «Сотрудники платформы» — роли, отзыв доступа, отключение и включение учётной записи, сброс 2FA с причиной; изменение подтверждается кодом 2FA, если его не вводили последние 10 минут, и пишется в журнал аудита (действия над людьми мерчанта или партнёра копируются в их журналы — когда консоль начнёт работать с ними, в 6d). ✅ 6d-1 (ADR-0015): «Партнёры» и «Мерчанты» — поиск, добавление, изменение, приостановка, закрытие и повторное открытие; статусы действуют везде: приостановленный мерчант только читает (кассы работают), закрытый недоступен, ключи отключённых касс не принимаются.
- 🟡 6.3 Кабинет мерчанта (Filament): настройки программы, типы баллов, уровни, конструктор акций, промокоды, каталог, точки, кассы и ключи, участники (маскированные ПДн, история, корректировки), документы согласий, отчёты, пользователи, аудит. ✅ 6b-1: каркас `/merchant/{id}` — мерчант из URL, доступ по членству в команде или роли в партнёре (сотрудникам партнёра переключатель показывает до 50 мерчантов каждого партнёра, остальные — по ссылке до кабинета партнёра в 6i), контекст тенанта на каждом запросе страницы и Livewire, только чтение у приостановленного мерчанта. ✅ 6c-1: «Команда» (участники, сотрудники партнёра, приглашения, роли, закрытие доступа) и «Журнал аудита» (фильтры, названия действий по-русски). ✅ 6c-2: «Организация» — точки (изменение, закрытие с отключением касс), кассы (включая веб-кассу), бренды, юрлица; ключи отключённых касс отклоняются на входе в 6d вместе со статусами мерчанта. ✅ 6c-3: «Ключи касс» и «Ключи мерчанта» — выпуск с показом один раз и вторым фактором, отзыв (и у приостановленного мерчанта), кто выпустил, последнее использование ключа кассы; отзыв ключей при закрытии доступа сотруднику и сотруднику партнёра. ✅ 6c-4: «Вебхуки» — добавление на все или выбранные события, секрет подписи один раз, отключение с причиной, новый секрет, доставки и повторная отправка.
- 6.4 Веб-касса (PWA) для точек без интеграции.
- 6.5 Встраиваемые web components поверх публичного API с короткоживущими сессиями и темизацией (white-label).
- 6.6 Инструменты поддержки: объединение дублей участников (перенос партий в леджере с сохранением сроков, перенос карт, сторно возвратов у участника, к которому перенесены баллы; запрет при активных резервах), ручные корректировки с причиной и аудитом.

**Готово, когда:** мерчант настраивает и ведёт программу без API; партнёр встраивает виджет участника в свой продукт.

## Этап 7. Клиентские каналы и коммуникации ⬜

- 7.1 Кабинет покупателя (PWA): вход по OTP, баланс, история, QR, прогресс уровня, управление согласиями.
- 7.2 Wallet-карты: Apple Wallet (PassKit, веб-сервис обновлений, push через APNs), веб-карта как запасной вариант.
- 7.3 Ядро коммуникаций: шаблоны, журнал сообщений, провайдеры (SMS, email, push RuStore/FCM/APNs, бот MAX), маршрутизация с цепочкой запасных каналов, разделение сервисных и рекламных сообщений, проверка согласий, частотные лимиты, «тихие часы» по часовому поясу программы.
- 7.4 Триггеры: welcome, первая покупка, реактивация (30/60/90 дней), день рождения, скорое сгорание, смена уровня, уведомление о списании.
- 7.5 Бот и мини-приложение MAX; Telegram как опциональный адаптер.

**Готово, когда:** триггеры срабатывают с проверкой согласий и лимитов; Wallet-карта обновляется при изменении баланса.

## Этап 8. Отчёты и аналитика ⬜

- 8.1 Операционные отчёты: охват программы по чекам и выручке, начисления, списания, сгорания, обязательства по юрлицам в баллах и рублях, отчёты по кассирам и точкам, результаты акций против контрольной группы.
- 8.2 Глобальная контрольная группа (holdout).
- 8.3 Аналитический контур: outbox → ClickHouse, когорты, удержание, LTV, RFM-сегменты с записью обратно в PostgreSQL.
- 8.4 Выгрузки CSV и XLSX, отчёты по расписанию, доступ для BI.
- 8.5 Данные для МСФО 15: входящий и исходящий остаток обязательств, движение, фактическая доля погашения.

**Готово, когда:** итоги отчётов сходятся с леджером; RFM-сегменты используются в акциях.

## Этап 9. Интеграции ⬜

- 9.1 Встраивание в первый собственный продукт (API и виджет).
- 9.2 МойСклад Loyalty API (касса вызывает наш сервер). Для касс, которые не умеют делить строку, — режим скидок и списания, кратных количеству единиц в строке.
- 9.3 JS-виджет для сайтов и Tilda; модуль для 1С-Битрикс.
- 9.4 Приложение для Эвотора (отдельный репозиторий).
- 9.5 Плагин iikoFront (.NET, отдельный репозиторий, лицензирование через iiko).
- 9.6 Расширение 1С (отдельный репозиторий).
- 9.7 Frontol (через партнёрство с АТОЛ), Saby и другие — по спросу.

**Готово, когда:** пилотные мерчанты проводят реальные чеки минимум через два канала.

## Этап 10. Биллинг SaaS ⬜

- 10.1 Тарифы: оплата за активные точки с включённым объёмом базы, SMS по себестоимости с прозрачной наценкой, оптовые цены для партнёров.
- 10.2 Учёт использования: активные точки, участники, чеки, сообщения.
- 10.3 Счета, акты и УПД через Диадок; рекуррентные платежи ЮKassa, CloudPayments, Т-Касса; работа с должниками и приостановка.
- 10.4 Отчёты и расчёты с партнёрами.

**Готово, когда:** ежемесячное выставление счетов пилотным тенантам работает автоматически.

## Этап 11. Прод, безопасность и комплаенс ⬜

- 11.1 Контейнеры: образы FrankenPHP + Octane, воркеры Horizon, планировщик, health checks.
- 11.2 Инфраструктура: managed Kubernetes, PostgreSQL и Redis в российском облаке с аттестацией УЗ-1, Terraform, секреты, бэкапы и PITR, резервная площадка.
- 11.3 Наблюдаемость: OpenTelemetry → Grafana (метрики, логи, трассировки), self-hosted Sentry, SLO (p95 чека ≤ 200 мс, доступность 99,9%), алерты, runbook'и.
- 11.4 Безопасность: пентест, сканирование зависимостей и секретов, SAST, SSO и MFA для админов; отдельная роль БД для трафика публичных API без прав на таблицы сотрудников (остаточный риск ADR-0013).
- 11.5 Документы по 152-ФЗ: типовое поручение на обработку, модель угроз, акт уровня защищённости, политики, регламент реагирования на инциденты (24/72 часа).
- 11.6 Staging, нагрузочный тест на staging, регламент подключения пилотного клиента.

**Готово, когда:** продуктивная среда работает, первый пилотный мерчант подключён.

## Этап 12 и далее. Развитие ⬜

Подарочные карты (учёт как аванс), несколько типов баллов в программе со списанием по приоритету, платные подписки, геймификация, коалиции и расчёты между юрлицами, SSO (OIDC, SAML), выделенные кластеры, ISO 27001, bug bounty, ML-скоринг антифрода, next best offer, MCP-сервер и `llms.txt`, коннекторы r_keeper и Set Retail, вынос горячего пути в Go — только если SLO не выдерживается после оптимизации.
