UC-REF-002. Аноним открыл реферальную ссылку (cookie 30 дней, validateReferralLink)
Назначение
Гость (аноним) переходит по реферальному URL с query-параметром referralUrl={slug}: slug фиксируется в
cookie referralUrl на 30 дней, ссылка проверяется операцией validateReferralLink, гостю показывается
имя пригласившего, а переход учитывается счётчиком VisitsCount. Привязка на этом шаге НЕ выполняется —
она произойдёт позже по правилам attach-rules.md при регистрации/авторизации
(UC-REF-003…006). Связан с контекстом referral.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-REF-002 |
| Контекст | referral (см. docs/06-services/referral/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Гость (аноним); альтернатива A2 — авторизованный Инвестор (триггер T3) |
| Permissions / scope | permissions не требуются: validateReferralLink — публичная анонимная операция (attach-rules.md, раздел «Безопасность») |
| Tenant | не применяется (аноним); slug из URL — недоверенный вход (валидация формата) |
| Record-level | не применяется (публичное чтение обезличенных данных ссылки) |
Предусловие
- Реферер сформировал и передал URL вида
{origin}{path}?referralUrl={slug}(UC-REF-001). - Ссылка существует и не удалена (
DeletedAt == null) — иначе альтернатива A1.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI (открытие URL) |
| Триггерящий элемент | Любая страница фронта, открытая с query-параметром referralUrl (лендинг, карточка объекта, подборка и т.д.) |
Основной Поток
- Гость открывает URL с
?referralUrl={slug}(из мессенджера, соцсети, QR-кода — канал зафиксирован вChannelссылки, referral-link.md). - Клиент сохраняет slug в cookie:
referralUrl={slug}; path=/; max-age=2592000(30 дней);SameSite=Lax. Повторное открытие с другим slug перезаписывает cookie (действует последняя ссылка). - Клиент вызывает публичную операцию
validateReferralLinkсо slug (validate-referral-link.md). Идемпотентность не требуется (чтение + атомарный счётчик). - Проверка ссылки:
- slug — недоверенный вход из URL/cookie: нормализация lowercase + формат
^[a-z0-9][a-z0-9-]{2,63}$(шаг 2 нормативного алгоритма attach-rules.md); мусор →isValid = falseбез обращения к хранилищу. - Ссылка ищется по slug среди неудалённых (guard G1). Не найдена/удалена →
isValid = false. - Найдена →
isValid = true;referrerDisplayName= имя владельца (Name.FirstNameиз репликиReferralUserReplica; PII сверх имени не раскрывается). Полный URL не строится (зона клиента). - При
isValid = true— атомарный инкремент счётчика переходовVisitsCount(без гонок и без optimistic-конфликта, referral-link.md, полеVisitsCount). - Переход построчно не хранится — единственный след — счётчик (simplicity-first, ADR-0056).
- Результат:
{ isValid, url, referrerDisplayName? }.
- slug — недоверенный вход из URL/cookie: нормализация lowercase + формат
- Клиент при
isValid = trueпоказывает плашку «Вас пригласил {referrerDisplayName}»; приisValid = false— очищает cookie и ничего не показывает (edge case «битый slug», attach-rules.md). - Гость пользуется каталогом; при последующем входе/регистрации auth-модалка читает cookie и подставляет
slug полем
referrerUrlво вход/регистрацию (UC-IDN-001) — продолжение в UC-REF-003/004/005.
Альтернативные Потоки
A1. Slug не существует, ссылка удалена или переименована
- Ссылка по slug не резолвится (в т.ч. старый slug после переименования — история slug-ов не ведётся,
INV-REF-L6) →
isValid = false,VisitsCountне инкрементируется. - Клиент очищает cookie. Завершение — привязка по этой ссылке невозможна.
A2. Ссылку открыл авторизованный инвестор (триггер T3)
- Шаги 1–4 совпадают; дополнительно при авторизованном инвесторе (
actor.UserId != null) и включённом флагеReferral:LinkOpenAttachEnabledзапускается процесс привязки по триггеру T3: guard-ы G1–G4, правило R4 матрицы — связь НЕ создаётся, лид запрашивается, если владелец ссылки — агент/застройщик (INV-REF-002); полный алгоритм — attach-rules.md. - Публикуется
ReferralLeadAttachRequestedKafkaEvent(BondId = null,AppliedRule = R4). Возврат к шагу 5 основного потока.
A3. Открытие без query-параметра, но с живой cookie
- Cookie уже установлена ранее (в пределах 30 дней) — клиент её не трогает и
validateReferralLinkне вызывает. - Slug доедет до
referralпри входе/регистрации. Завершение.
Постусловие
- Cookie
referralUrlсо slug действует 30 дней (или очищена приisValid = false). VisitsCountссылки увеличен на 1 — база конверсии «переход → регистрация → связь» в партнёрской статистике per-канал.- Реферальная связь, журнал и лид НЕ созданы (кроме A2/R4 — только лид): гость ещё не идентифицирован.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
ValidationError | 400 | Slug длиннее 64 символов / пустой параметр | Не показывать плашку; cookie не ставить |
— (isValid = false в успешном ответе) | 200 | Ссылка не найдена / удалена / формат slug мусорный (G1) | Очистить cookie, UI без приглашения |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
| — | Не поднимаются: переход фиксируется только счётчиком VisitsCount (ADR-0056) | — |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
ReferralLeadAttachRequestedKafkaEvent | pin.referral.lead-attachments.v1 | Только альтернатива A2 (триггер T3, правило R4, флаг Referral:LinkOpenAttachEnabled) | crm (карточка лида, дедуп пары «инвестор+агент») |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Открытие URL с валидным slug ставит cookie referralUrl c max-age = 2592000 | e2e: проверка cookie |
| AC2 | validateReferralLink для живой ссылки → isValid = true + referrerDisplayName из Name.FirstName реплики владельца | интеграционный тест |
| AC3 | Каждый успешный validate инкрементирует VisitsCount ровно на 1; параллельные вызовы не теряют инкременты | конкурентный тест атомарного инкремента |
| AC4 | Несуществующий/удалённый slug → isValid = false, VisitsCount не растёт, клиент чистит cookie | тест + e2e |
| AC5 | Формально невалидный slug (спецсимволы, > 64) не приводит к запросу в хранилище | unit-тест валидатора |
| AC6 | Повторное открытие с другим slug перезаписывает cookie (действует последняя ссылка) | e2e |
| AC7 | Анонимный вызов не создаёт связь/журнал/лид | проверка отсутствия записей после validate |
| AC8 | При выключенном флаге Referral:LinkOpenAttachEnabled открытие ссылки авторизованным инвестором (T3) не публикует lead-событие — только инкремент VisitsCount | тест с флагом off |
| AC9 | T3 при владельце-неагенте (R5/INV-REF-002) → lead-событие не публикуется даже при включённом флаге | тест владельца-инвестора |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (5): UC-REF-001. Создание реферальной ссылки с каналом и шаринг, UC-REF-003. Регистрация по реферальной ссылке → связь + лид (правило R1), UC-REF-004. Авторизация со ссылкой в окне lazy-регистрации → связь + лид (правило R2), UC-REF-005. Авторизация: «Имя заполнено И регистрация ≥ 4 ч» → только лид, без связи (правило R3), UC-REF-006. Ссылка от инвестора (не агента/застройщика) → связь без лида (модификатор R5)
- scenarios (1): UC-OVR-004. От реферальной ссылки до объекта у собственника
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод: validate-referral-link.md (контракт + инкремент/T3).
- Правила привязки и триггер T3: ../domain/attach-rules.md.
- Domain entity: ../domain/referral-link.md (
VisitsCount, INV-REF-L6). - Frontend: обработчик реферальной cookie (TTL 30 дней); auth-модалка — UC-IDN-001.
- Связанные UC: UC-REF-001 (откуда URL), UC-REF-003, UC-REF-004, UC-REF-005 (что происходит после входа).