Три часа полировки: как рабочий мастер превратился в готовую к релизу функцию
Мастер онлайн-расторжения страхового полиса уже умел всё необходимое: подтверждение личности через MyID, выбор полиса, расчёт суммы возврата и отправку заявки. Но именно несколько часов финальной доработки превратили его из просто работающей функции в продукт, готовый к выпуску. За это время я ограничил список доступных полисов только теми, которые действительно можно расторгнуть, заменил обязательную повторную биометрическую проверку 30-минутным окном доверия, сделал мастер устойчивым к перезагрузке страницы и смене языка, а также научил варианты причин автоматически подстраиваться под тип страхового продукта.

К этому моменту мастер расторжения уже умел всё самое важное: подтверждение личности через MyID, выбор полиса, расчёт суммы возврата, банковские реквизиты, отправку заявки. Чего ему ещё не хватало — того слоя, который пользователи чувствуют сильнее всего: части, где функция перестаёт бороться с человеком. Этот пост — о том слое и о том, как много из него помещается в несколько часов работы, когда фундамент сделан хорошо.
Показывать только то, что действительно можно расторгнуть
Внутренняя страховая система подтвердила готовность онлайн-расторжения ровно для двух семейств продуктов: обязательного автострахования (ОСАГО) и туристического страхования (международное, внутреннее и страхование пассажиров под одним кодом продукта). Все остальные продукты — КАСКО, страхование имущества, здоровья и от несчастных случаев — предлагать нельзя, даже если полис активен.
Поэтому список полисов получил фильтр по семейству продукта. И ещё один фильтр сверху: полисы, по которым уже существует заявка на расторжение, тоже скрываются. Выбор такого полиса может закончиться только экраном «Заявка уже существует» — зачем тогда позволять его выбирать? Я придерживаюсь простого принципа: не стоит предлагать пользователю выбор, который заранее обречён на неудачу. Дополнительная защита при этом остаётся: если заявление было подано в офисе компании и сайт ещё об этом не знает, проверка статуса сразу после выбора полиса поймает это и корректно сообщит пользователю о существующей заявке.
Одно подтверждение личности вместо проверки перед каждой заявкой
Первоначальное требование было строгим: биометрическая проверка перед каждым расторжением. Затем проявился реальный пользовательский сценарий — человек входит в систему через MyID (а это уже проверка лица!), нажимает «расторгнуть полис» и буквально через несколько секунд получает просьбу пройти ту же самую проверку ещё раз. Технически всё правильно. Практически — совершенно бессмысленно.
Новое продуктовое решение: одна проверка действует 30 минут, и засчитывается любая успешная проверка через MyID — как при входе в систему, так и внутри самого мастера. Я реализовал это через «штамп» — небольшую запись в рамках вкладки, которая создаётся после каждой успешной проверки и сверяется, когда мастер решает, показывать ли шаг подтверждения личности. Штамп привязан к пользователю (вход под другой учётной записью в той же вкладке делает его недействительным), удаляется при выходе из системы и истекает по фиксированным часам, которые ничто не может продлить. Вход по паролю штамп не создаёт: нет проверки — нет штампа.
Самой тонкой частью оказалось то, что при входе через MyID штамп записывается ещё до загрузки пользовательского профиля. Поэтому сначала он создаётся анонимным, а затем «усыновляется» первым профилем, который его подтверждает, — с сохранением исходного времени проверки, так что привязка не может растянуть окно. Несколько расторжений в одном окне теперь стоят одной биометрической проверки, а границей служит время, а не количество попыток.
Пережить перезагрузку страницы и смену языка
Две ошибки объединяла одна общая причина, и найти её было по-настоящему приятно. Первая: смена языка интерфейса посреди мастера сбрасывала всё к первому шагу. Переключатель языка переходит на другой URL-префикс, из-за чего компонент страницы пересоздаётся и теряет всё внутреннее состояние. Исправление — стабильный ключ страницы, чтобы все языковые варианты использовали один и тот же экземпляр компонента: тексты переводятся реактивно вокруг нетронутого состояния.
Это исправление немедленно вызвало вторую ошибку: язык на этой странице вообще перестал переключаться. Оказалось, приложение завершало смену локали внутри хуков перехода между страницами — а они срабатывают только при пересоздании компонента. Мой компонент больше не пересоздавался, поэтому начатая смена языка никогда не завершалась. Теперь страница завершает её сама, когда меняется её URL. Урок: если отказываешься от жизненного цикла, выясни, что ещё на нём держалось.
Ту же заботу получила и перезагрузка страницы. Прогресс мастера — текущий шаг, выбранный полис, текст причины — теперь сохраняется в рамках вкладки, и после обновления пользователь продолжает ровно с того места, где остановился, за одним спокойным экраном загрузки вместо видимого перепрыгивания по шагам. Единственное, что перезагрузка теряет, — прикреплённый PDF: браузеры не позволяют сохранить выбранный файл, поэтому его нужно прикрепить повторно. Всё остальное возвращается.
Причины теперь соответствуют выбранному продукту
Быстрые варианты причин на шаге «Причина отмены» раньше были одним статичным набором — включая «Продал автомобиль» для туристического полиса. Теперь варианты зависят от продукта: автострахование предлагает автомобильные причины, туристическое — связанные с поездкой, а «Некорректная информация» и «Другое» доступны всегда. Выбранный вариант по-прежнему вставляет свой текст в поле комментария на виду у пользователя — то, что человек видит, ровно то и получает сотрудник компании. А если пользователь выбрал автомобильную причину, вернулся назад и сменил полис на туристический, неподходящий вариант и вставленная им фраза удаляются автоматически — при этом каждое слово, написанное пользователем самостоятельно, остаётся без изменений.
Самое маленькое исправление этой серии — моё любимое: в узбекском языке во фразе «действует до даты» слово gacha ставится после даты — «Amal qiladi 25.10.2026 gacha». Раньше фраза строилась одинаково для всех языков: правильно для русского и английского, но грамматически неверно для узбекского. Теперь это один шаблон перевода с подстановкой даты в нужное место для каждого языка. Такие детали редко попадают в планы спринтов, но именно они создают ощущение, что интерфейс написан человеком, который действительно говорит на языке пользователя, — потому что так и есть.
Что я вынес из этих трёх часов
Полностью реализованная функция — это только половина пути. Вторая половина — вычитание и починка: убрать выбор, который не может закончиться успехом; убрать повторную проверку, которая не доказывает ничего нового; убрать потерю состояния, которую пользователи не прощают; убрать грамматику, звучащую чужеродно. Весь список выше занял у меня около трёх часов — и эта скорость не была случайностью. Это то, что возвращает чистая структура мастера: когда состояние живёт в одном месте, а у каждого правила — единственная точка ответственности, полировка превращается в чек-лист, а не в раскопки. Вместе эти три часа и есть разница между демо и готовым продуктом.