UC-REF-004. Авторизация со ссылкой в окне lazy-регистрации → связь + лид (правило R2)
Назначение
Привязка при авторизации существующего инвестора с реферальной ссылкой: если «Имя пустое» ИЛИ «дата
регистрации менее 4 часов» — lazy-регистрация ещё не завершена, пользователь считается приведённым по
ссылке, и создаются реферальная связь (для будущих выплат), запись журнала Initial и — при
владельце-агенте/застройщике — лид в CRM. Правило R2 AuthorizationLazyWindow матрицы
attach-rules.md. Связан с контекстом referral.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-REF-004 |
| Контекст | referral (см. docs/06-services/referral/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | System (обработка события входа; m2m ACL Kafka на топик pin.identity.sign-ins.v1); инициатор — Инвестор, авторизующийся с реф-cookie |
| Permissions / scope | permission не применяется (system actor); доступ — ACL топика |
| Tenant | создаваемые связь/журнал получают TenantId = link.TenantId (ADR-0052) |
| Record-level | не применяется на записи; чтение — по ReferredUserId/OwnerUserId |
Предусловие
- Учётка инвестора уже существует (создана lazy-регистрацией: бронь/заявка по телефону, либо обычной регистрацией менее 4 часов назад).
- Инвестор открыл реферальную ссылку — cookie
referralUrlжива (UC-REF-002). - Событие входа
UserSignedInKafkaEventнесёт опциональное полеReferrerUrl(slug из cookie) — контракт user-signed-in.md; без него триггер T2 неисполним событийно (решение 4 README.md). - Условие окна:
ReferralUserReplica.IsNameFilled = falseИЛИnow() - RegisteredAt < Referral:LazyRegistrationWindow(default04:00:00). - Guard-ы G1–G5 attach-rules.md выполнены.
Триггер
| Поле | Значение |
|---|---|
| Источник | Kafka event |
| Триггерящий элемент | Событие UserSignedInKafkaEvent (топик pin.identity.sign-ins.v1) при ReferrerUrl != null (триггер T2, attach-rules.md) |
Основной Поток
Ниже — дельта входа T2/R2 к общему алгоритму привязки (дом — attach-rules.md).
- Инвестор входит через auth-модалку; клиент подставляет
referrerUrlиз cookie в подтверждение входа (UC-IDN-001). identityпубликуетUserSignedInKafkaEvent(pin.identity.sign-ins.v1) сUserId,SessionId,SignInMethod,ReferrerUrl = slug; время события = момент входа (user-signed-in.md).- Контекст и идемпотентность. Повтор отсекается по
MessageIdи guard G5; приReferrerUrl == nullпроцесс не запускается вовсе. Формат slug валиден; ссылка резолвится и не удалена (G1). Читаются: реплика пользователяReferralUserReplica(Type,Status,IsNameFilled,RegisteredAt; отсутствие реплики → retry: событиеuser-upsertedмогло ещё не примениться); владелец ссылки (для INV-REF-002); наличие активной связи — с блокировкой от гонки (G5). - Выбор правила.
IsNameFilled = falseИЛИвремя события - RegisteredAt < Referral:LazyRegistrationWindow→ правило R2;RegisteredAt = nullв реплике → окно считается истёкшим (fail-closed) → правило R3 (UC-REF-005, альтернатива A1). Создаётся связь:ReferredUserId = event.UserId,ReferrerUserId = link.OwnerUserId,ReferralLinkId = link.Id,TenantId = link.TenantId,BoundAt= время события,AppliedRule = R2,Status = Active; инвариант INV-REF-B2. Фиксируется доменное событиеReferralBondCreatedEvent. - Журнал
Initial. Пишется запись cAppliedRule = R2,RegisteredAt= время события, денормализацияLinkUrl/ReferrerUserId/TenantId(INV-REF-R4),LeadRequested= исход INV-REF-002; связь ссылается на запись журнала (RegistrationId). - Интеграционные события. Публикуется
ReferralBondCreatedKafkaEvent(pin.referral.bonds.v1, key =ReferredUserId,AppliedRule = R2); при владельцеAgent/DeveloperManagerсоStatus = Active(INV-REF-002) — дополнительноReferralLeadAttachRequestedKafkaEvent(pin.referral.lead-attachments.v1, key =InvestorUserId,BondId = bond.Id,AppliedRule = R2). - Согласованность. Связь + журнал + события фиксируются в одном согласованном изменении; конфликт единственности активной связи (гонка с T1 при почти одновременной регистрации+авторизации) → идемпотентное завершение (ровно одна связь). Событие подтверждается (ack).
- Downstream.
crm— карточка лида;rewards— основание выплат;notifications— уведомление рефереру;messenger— контакт (как в UC-REF-003, шаг 9).
Альтернативные Потоки
A1. Имя заполнено И регистрация ≥ 4 ч
- Условие R2 не выполнено → правило R3: связь не создаётся, только лид — сценарий UC-REF-005. Завершение здесь.
A2. Активная связь уже существует (G5, INV-REF-001)
- Пользователь ранее привязан (например, R1 при регистрации по другой ссылке) → связь и журнал не создаются; автоматическая перепривязка запрещена (решение 3 README.md).
- Лид по матрице создаётся (владелец — агент/застройщик): агент B получает карточку лида, связь остаётся с агентом A (edge case attach-rules.md). Событие подтверждается.
A3. Владелец ссылки — не агент/застройщик
- Связь создаётся по R2, lead-событие не публикуется (
LeadRequested = false, R5/INV-REF-002) — детально UC-REF-006.
A4. Вход без ReferrerUrl / guard-нарушение
ReferrerUrl == null→ процесс не запускается; G1–G4 не пройдены → no-op с warning (таблица guard-ов attach-rules.md). Событие подтверждается.
A5. Повторная доставка события
- Совпал
MessageIdлибо связь уже есть (G5) → идемпотентный no-op; дублей журнала/событий нет. Событие подтверждается.
Постусловие
- Создана
ReferralBondEntity(Status = Active,AppliedRule = R2,BoundAt= момент входа) — единственная активная (INV-REF-001). - Журнальная запись
InitialсAppliedRule = R2учтена в статистике ссылки/канала. - При владельце-агенте/застройщике запрошена карточка лида в CRM;
rewards/notifications/messengerполучили событие связи.
Возможные Ошибки
| Код | HTTP | Условие | Поведение |
|---|---|---|---|
| — (no-op, warning) | — (Kafka consumer, HTTP нет) | Guard G1–G4: битый slug, не инвестор, self-attach, blocked | Метрика no-op guard-ов |
| — (идемпотентный no-op) | — | G5: активная связь уже есть; повторная доставка | Событие подтверждается |
| retry consumer-а | — | Реплика пользователя/владельца ещё не создана (лаг топика users) | Retry до появления реплики |
| DLQ после исчерпания retry | — | Инфраструктурный сбой | Runbook referral-consumer-lag (README.md) |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
ReferralBondCreatedEvent | Создание связи (шаг 4) | in-proc аудит |
ReferralRegistrationRecordedEvent | Запись журнала Initial (шаг 5) | in-proc |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
ReferralBondCreatedKafkaEvent | pin.referral.bonds.v1 | Связь создана по R2 (шаг 6) | rewards, notifications, messenger |
ReferralLeadAttachRequestedKafkaEvent | pin.referral.lead-attachments.v1 | Владелец — Agent/DeveloperManager (INV-REF-002) | crm |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Вход с ReferrerUrl при пустом имени → связь Active + журнал Initial (AppliedRule = R2) | интеграционный тест: реплика IsNameFilled = false |
| AC2 | Вход с ReferrerUrl при заполненном имени, но регистрации 3 ч назад → связь создаётся (окно 4 ч) | тест на границе окна |
| AC3 | Ровно на границе RegisteredAt + 4 ч = время события окно истекло (строгое <) → R3, связь не создаётся | boundary-тест |
| AC4 | RegisteredAt = null в реплике → fail-closed: R3, связь не создаётся | тест пользователя без RegisteredAt |
| AC5 | Порог читается из Referral:LazyRegistrationWindow (default 04:00:00) | конфиг-тест с окном 1 ч |
| AC6 | Активная связь уже есть → новая не создаётся, лид запрашивается (при владельце-агенте) | тест A2 |
| AC7 | Гонка T1+T2 (регистрация и вход почти одновременно) → ровно одна связь | конкурентный тест единственности активной связи |
| AC8 | Повторная доставка события входа не создаёт дублей | двойная доставка |
| AC9 | ReferrerUrl есть, но ссылка удалена/переименована (старый slug не резолвится, INV-REF-L6) → G1 no-op: ни связи, ни лида, ни журнала | тест удалённой/переименованной ссылки |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (4): UC-REF-002. Аноним открыл реферальную ссылку (cookie 30 дней, validateReferralLink), UC-REF-003. Регистрация по реферальной ссылке → связь + лид (правило R1), UC-REF-005. Авторизация: «Имя заполнено И регистрация ≥ 4 ч» → только лид, без связи (правило R3), UC-REF-006. Ссылка от инвестора (не агента/застройщика) → связь без лида (модификатор R5)
- scenarios (2): UC-OVR-002. Запрос консультации у инвест-консультанта ПИН (+2a), UC-OVR-004. От реферальной ссылки до объекта у собственника
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- Правила привязки (дом): ../domain/attach-rules.md — правило R2, триггер T2, «окно lazy-регистрации», guard-ы G1–G5.
- Consumer: ../consumers/user-signed-in.md.
- Требование к identity (поле
ReferrerUrl?): README.md, решение 4; контракт — user-signed-in.md. - Domain entity: ../domain/referral-bond.md, ../domain/referral-registration.md.
- События: ../events/bond-created.md, ../events/lead-attach-requested.md.
- Связанные UC: UC-REF-003 (R1), UC-REF-005 (R3), UC-REF-006 (R5), UC-IDN-001.