Перейти к основному содержимому

UC-REF-004. Авторизация со ссылкой в окне lazy-регистрации → связь + лид (правило R2)

Назначение

Привязка при авторизации существующего инвестора с реферальной ссылкой: если «Имя пустое» ИЛИ «дата регистрации менее 4 часов» — lazy-регистрация ещё не завершена, пользователь считается приведённым по ссылке, и создаются реферальная связь (для будущих выплат), запись журнала Initial и — при владельце-агенте/застройщике — лид в CRM. Правило R2 AuthorizationLazyWindow матрицы attach-rules.md. Связан с контекстом referral.

Metadata

ПолеЗначение
IDUC-REF-004
Контекстreferral (см. docs/06-services/referral/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольSystem (обработка события входа; m2m ACL Kafka на топик pin.identity.sign-ins.v1); инициатор — Инвестор, авторизующийся с реф-cookie
Permissions / scopepermission не применяется (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 (default 04: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).

  1. Инвестор входит через auth-модалку; клиент подставляет referrerUrl из cookie в подтверждение входа (UC-IDN-001).
  2. identity публикует UserSignedInKafkaEvent (pin.identity.sign-ins.v1) с UserId, SessionId, SignInMethod, ReferrerUrl = slug; время события = момент входа (user-signed-in.md).
  3. Контекст и идемпотентность. Повтор отсекается по MessageId и guard G5; при ReferrerUrl == null процесс не запускается вовсе. Формат slug валиден; ссылка резолвится и не удалена (G1). Читаются: реплика пользователя ReferralUserReplica (Type, Status, IsNameFilled, RegisteredAt; отсутствие реплики → retry: событие user-upserted могло ещё не примениться); владелец ссылки (для INV-REF-002); наличие активной связи — с блокировкой от гонки (G5).
  4. Выбор правила. 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.
  5. Журнал Initial. Пишется запись c AppliedRule = R2, RegisteredAt = время события, денормализация LinkUrl/ReferrerUserId/TenantId (INV-REF-R4), LeadRequested = исход INV-REF-002; связь ссылается на запись журнала (RegistrationId).
  6. Интеграционные события. Публикуется 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).
  7. Согласованность. Связь + журнал + события фиксируются в одном согласованном изменении; конфликт единственности активной связи (гонка с T1 при почти одновременной регистрации+авторизации) → идемпотентное завершение (ровно одна связь). Событие подтверждается (ack).
  8. Downstream. crm — карточка лида; rewards — основание выплат; notifications — уведомление рефереру; messenger — контакт (как в UC-REF-003, шаг 9).

Альтернативные Потоки

A1. Имя заполнено И регистрация ≥ 4 ч

  1. Условие R2 не выполнено → правило R3: связь не создаётся, только лид — сценарий UC-REF-005. Завершение здесь.

A2. Активная связь уже существует (G5, INV-REF-001)

  1. Пользователь ранее привязан (например, R1 при регистрации по другой ссылке) → связь и журнал не создаются; автоматическая перепривязка запрещена (решение 3 README.md).
  2. Лид по матрице создаётся (владелец — агент/застройщик): агент B получает карточку лида, связь остаётся с агентом A (edge case attach-rules.md). Событие подтверждается.

A3. Владелец ссылки — не агент/застройщик

  1. Связь создаётся по R2, lead-событие не публикуется (LeadRequested = false, R5/INV-REF-002) — детально UC-REF-006.

A4. Вход без ReferrerUrl / guard-нарушение

  1. ReferrerUrl == null → процесс не запускается; G1–G4 не пройдены → no-op с warning (таблица guard-ов attach-rules.md). Событие подтверждается.

A5. Повторная доставка события

  1. Совпал 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
ReferralBondCreatedKafkaEventpin.referral.bonds.v1Связь создана по R2 (шаг 6)rewards, notifications, messenger
ReferralLeadAttachRequestedKafkaEventpin.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-тест
AC4RegisteredAt = null в реплике → fail-closed: R3, связь не создаётсятест пользователя без RegisteredAt
AC5Порог читается из Referral:LazyRegistrationWindow (default 04:00:00)конфиг-тест с окном 1 ч
AC6Активная связь уже есть → новая не создаётся, лид запрашивается (при владельце-агенте)тест A2
AC7Гонка T1+T2 (регистрация и вход почти одновременно) → ровно одна связьконкурентный тест единственности активной связи
AC8Повторная доставка события входа не создаёт дублейдвойная доставка
AC9ReferrerUrl есть, но ссылка удалена/переименована (старый slug не резолвится, INV-REF-L6) → G1 no-op: ни связи, ни лида, ни журналатест удалённой/переименованной ссылки

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные Документы