# Приёмка liderra.ru — ПУНКТ №18: Онбординг-самозапись (почта + код, старт 300 ₽)

> **Для исполнителя:** формат PR1 (одобрен 20.06). Каждый шаг = `🔧 Код-факт (file:line)` · `🎬 Действие` · `📋 ОТЧЁТ (было→ожидали→стало + 📸/📝)` · `❌ Если не так`. Техника «под капотом»; владельцу — человеческие карточки.

**Цель:** показать, что новый клиент **сам** регистрируется без участия менеджера: вводит почту и пароль, проходит капчу, получает на почту **код**, подтверждает его — и попадает в кабинет с приветственным балансом **300 ₽**. Защита от злоупотреблений: ограничено число попыток ввода кода, код живёт 15 минут, один email — один аккаунт.

**Что снимаем глазами:** экран **Регистрации** (форма → ввод кода), **Дашборд** с балансом 300 ₽, письмо с кодом. Живые 📸.

**Источник сценария:** свод проверок №18 (ONB-REG/CODE/ATTEMPTS/RESEND/CAPTCHA); R3b «онбординг/UI».

---

## 🔧 Код-факты (подтверждено чтением 20.06)

- **Капча — первой** — без прохождения капчи регистрация отклоняется (`captcha_failed`) — [RegistrationService.php:47-49](../../../app/app/Services/Auth/RegistrationService.php#L47-L49).
- ⚠️ **Капча = Null БЕЗУСЛОВНО (M-2/GAP-3), не только local:** `CaptchaVerifier` биндится на `NullCaptchaVerifier` всегда ([AppServiceProvider.php:54-57](../../../app/app/Providers/AppServiceProvider.php#L54)) — реального Yandex SmartCaptcha в коде НЕТ. `NullCaptchaVerifier` пропускает любой непустой токен при `services.captcha.fake_passes` (default true). **На проде любой непустой `captcha_token` проходит** → проверять капчу как «реальную защиту» нельзя. Тест 5-попыток/TTL — это про **код из письма**, не про капчу. На прогоне сверить фактический прод-`.env` капчи.
- **Регистрация** → тенант `pending_email_confirm`, юзер неактивен; письмо с кодом **после commit** (сбой почты не валит) — [register :45-77](../../../app/app/Services/Auth/RegistrationService.php#L45-L77).
- **Один email — один аккаунт** — advisory-lock сериализует одновременные регистрации; активный email → `email_taken` — [:57-61](../../../app/app/Services/Auth/RegistrationService.php#L57-L61).
- **Код — 6 цифр, TTL 15 минут, 5 попыток** — [CODE_TTL_MINUTES=15 :34](../../../app/app/Services/Auth/RegistrationService.php#L34), [MAX_FAILED_ATTEMPTS=5 :36](../../../app/app/Services/Auth/RegistrationService.php#L36). На проде код — **только письмом** (dev-код лишь local/testing).
- **Подтверждение → активация + 300 ₽** — `confirm` атомарно: юзер активен, тенант `active`, баланс **300.00 ₽** — [START_BALANCE_RUB='300.00' :38](../../../app/app/Services/Auth/RegistrationService.php#L38), `confirm` [:79+](../../../app/app/Services/Auth/RegistrationService.php#L79).

---

## Шаг 18-1 — Регистрация: почта + пароль + капча → экран кода

**🔧 Код:** [register :45-77](../../../app/app/Services/Auth/RegistrationService.php#L45-L77).
**🎬 Действие (лок):** на `/register` ввести почту + пароль, пройти капчу, отправить.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Экран | форма пустая | после отправки — «Введите код из письма» | _(слот прогона)_ | 📸 до (форма) и после (экран кода) |
| Аккаунт | нет | создан, ждёт подтверждения (неактивен, баланс 0) | _(слот)_ | 📝 тенант `pending_email_confirm`, баланс 0 |
| Письмо с кодом | — | отправлено на почту | _(слот)_ | 📸 письмо с 6-значным кодом (опц.) |

**❌ Если не так:** аккаунт сразу активен / баланс ≠ 0 → дефект онбординга.

---

## Шаг 18-2 — Подтверждение кодом → кабинет + 300 ₽

**🔧 Код:** [confirm :79+](../../../app/app/Services/Auth/RegistrationService.php#L79); баланс — [:38](../../../app/app/Services/Auth/RegistrationService.php#L38).
**🎬 Действие (лок):** ввести 6-значный код из письма (лок — из dev-кода/лога) → подтвердить.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Вход в кабинет | недоступен | открыт, на дашборде **300 ₽** | _(слот)_ | 📸 Дашборд с балансом 300 ₽ |
| Статус аккаунта | ждёт подтверждения | активен | _(слот)_ | 📝 тенант `active`, баланс 300.00 |

**💡 Что внутри:** клиент регистрируется сам — вводит почту, получает код, подтверждает — и сразу в кабинете. Чтобы было что попробовать, портал дарит приветственные 300 ₽. Менеджер для старта не нужен.

**❌ Если не так:** активация без кода / баланс ≠ 300 → дефект.

---

## Шаг 18-3 — Защита: лимит попыток, срок кода, один email

**🔧 Код:** [MAX_FAILED_ATTEMPTS=5 :36](../../../app/app/Services/Auth/RegistrationService.php#L36), [TTL=15 :34](../../../app/app/Services/Auth/RegistrationService.php#L34), email-лок [:57-61](../../../app/app/Services/Auth/RegistrationService.php#L57-L61).
**🎬 Действие (лок):** (а) ввести неверный код несколько раз; (б) попробовать зарегистрировать уже занятый email; (в) (опц.) переждать 15 минут / запросить новый код.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Неверный код × много | — | после **5 попыток** — блок/новый код | _(слот)_ | 📸 сообщение о лимите попыток |
| Занятый email | — | **«email уже занят»** (один email — один аккаунт) | _(слот)_ | 📸 ошибка email_taken |
| Просроченный код | — | через 15 мин код не принимается, нужен новый | _(слот)_ | 📸/📝 «код истёк», повторная отправка |

**💡 Что внутри:** чтобы код нельзя было подобрать перебором — даётся ограниченное число попыток и срок жизни 15 минут. И на одну почту можно завести только один аккаунт — два тенанта на один email не создать.

**❌ Если не так:** код подбирается без лимита / два аккаунта на email / просроченный код принят → дефект безопасности.

---

## Verification №18 (выход из пункта)
- [ ] 📸 Регистрация (форма → экран кода); аккаунт создан неактивным с балансом 0.
- [ ] 📸 Подтверждение кодом → кабинет + 300 ₽ на дашборде.
- [ ] 📸 Лимит 5 попыток; занятый email отклонён; просроченный код не принят.
- [ ] 📝 Тенант `pending_email_confirm` → `active`, баланс 0 → 300.

## 📸 vs 📝 в этом пункте
- **📸 Скриншот:** Регистрация (форма/код), Дашборд 300 ₽, сообщения о лимите/занятом email, письмо с кодом.
- **📝 Текст:** статус тенанта, баланс, TTL/попытки — частично текстом.

## Грабли
- На проде код — **только письмом** (нужен живой воркер очереди); dev-код есть лишь локально.
- Капча на проде реальная → масса аккаунтов руками не завести (для нагрузки — служебный путь).
- Связка с №19 (после входа — гейт реквизитов) и PR1 (та же механика старта 300 ₽).
