UC-REF-001. Создание реферальной ссылки с каналом и шаринг
Назначение
Пользователь получает свою реферальную ссылку и делится ею (копирование, Telegram, WhatsApp, VK).
Default-ссылка выдаётся идемпотентно — повторный запрос возвращает ту же ссылку; именованные ссылки под
каналы создаются в разделе «Партнёрская программа». Ссылка нужна, чтобы приводить новых инвесторов:
переход по ней запускает правила привязки (attach-rules.md). Связан с
контекстом referral.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-REF-001 |
| Контекст | referral (см. docs/06-services/referral/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Любой авторизованный пользователь: Инвестор / Агент / Девелопер (менеджер застройщика) / МенеджерПИН («реферальные ссылки может формировать любой пользователь системы») |
| Permissions / scope | referral.links.manage-self (создание/изменение), referral.links.view-self (чтение своих) — каталог permission.md |
| Tenant | TenantId = CompanyId владельца на момент создания; null для инвестора (ADR-0052) |
| Record-level | ссылки читаются/изменяются только владельцем (OwnerUserId = actor.UserId, INV-REF-L4) |
Предусловие
- Пользователь авторизован (UC-IDN-001); неавторизованному кнопка «Поделиться» сначала открывает auth-модалку, затем продолжает поток.
- Учётка активна (
Status ∉ {Blocked, Archived}). - Для альтернативы A2 (именованная ссылка): выбранный slug соответствует формату INV-REF-L3 и не занят (INV-REF-L1).
Триггер
| Поле | Значение |
|---|---|
| Источник | UI |
| Триггерящий элемент | Кнопка «Поделиться» на карточках объектов недвижимости, отелей, в избранном и подборках — default-ссылка; раздел «Партнёрская программа» — управление именованными ссылками по каналам |
Основной Поток
- Пользователь нажимает «Поделиться» (скопировать ссылку или иконка соцсети).
- Если реферальная ссылка уже получена ранее — используется она; иначе система выдаёт default-ссылку (операция get-or-create-default-referral-link.md).
- Выдача default-ссылки (идемпотентная, upsert-семантика INV-REF-L2). Требуется активная учётка
(guard активности — на входе) и право
referral.links.manage-self; отдельного ключа идемпотентности не нужно.- Если у пользователя уже есть default-ссылка (
OwnerUserId = actor.UserId,IsDefault = true, не удалена) — возвращается она (идемпотентность). - Иначе создаётся новая
ReferralLinkEntity:OwnerUserId = actor.UserId,TenantId = actor.CompanyId(nullу инвестора),Url= сгенерированный slug (10 символов[a-z0-9], CSPRNG, до 3 повторов при коллизии — правило поляUrl),Description = null,IsDefault = true,Channel = Direct,VisitsCount = 0,CreatedByUserId = actor.UserId. Фиксируется доменное событиеReferralLinkCreatedEvent. - Конкурентный первый запрос из двух вкладок нарушает единственность default-ссылки (INV-REF-L2) → повторное чтение и идемпотентный возврат уже созданной ссылки.
- Результат:
id,url(slug),description,isDefault.
- Если у пользователя уже есть default-ссылка (
- Клиент строит полный URL из slug:
{origin}{path}?referralUrl={slug}(query-параметрreferralUrl; бэкенд хранит только slug — referral-link.md). - Действие шаринга: «Скопировать ссылку» → буфер обмена, toast «Ссылка скопирована»; иконка соцсети → окно шаринга Telegram / WhatsApp / VK.
- Получатель ссылки открывает URL — продолжение в UC-REF-002 (cookie 30 дней), далее правила привязки attach-rules.md (UC-REF-003…006).
Альтернативные Потоки
A1. Slug уже получен на клиенте
- Ссылка уже закеширована на клиенте — возвращается без запроса к API.
- Поток продолжается с шага 4 основного потока.
A2. Именованная ссылка под канал («Партнёрская программа»)
- Пользователь заполняет форму:
url?(slug ≤ 64, опционально — иначе генерация),description?(≤ 256),channel(enumReferralChannel, дом — referral-link.md; defaultDirect). - Создание ссылки (create-referral-link.md) с отличиями от
шага 3: проверка формата slug
^[a-z0-9][a-z0-9-]{2,63}$после lowercase, запрет зарезервированныхadmin/api/www(INV-REF-L3); если slug занят другой неудалённой ссылкой — ошибкаREFERRAL_LINK_URL_TAKEN(INV-REF-L1);IsDefault = false,ChannelиDescription— из формы. - Переходы/регистрации/связи по этой ссылке попадут в статистику per-канал (
get-partner-stats, README.md). Шаринг конкретной ссылки — с той же страницы (шаги 4–5 основного потока).
A3. Ошибка создания default-ссылки
- API вернул ошибку → клиент показывает toast «Не удалось создать ссылку».
- Шаринг не выполняется; пользователь повторяет действие. Завершение.
Постусловие
- У пользователя существует ровно одна default-ссылка (
IsDefault = true, INV-REF-L2) и/или новая именованная ссылка с заполненнымChannel; slug глобально уникален среди неудалённых (INV-REF-L1). - Реферальный URL с
?referralUrl={slug}передан получателю выбранным способом шаринга. - Новых записей журнала/связей нет — они появляются только при срабатывании правил привязки (attach-rules.md).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
ValidationError | 400 | Формат slug/description/channel нарушен (INV-REF-L3) | Подсветить поле формы |
REFERRAL_LINK_URL_TAKEN | 409 | Slug занят другой неудалённой ссылкой (INV-REF-L1) | Сообщение «адрес занят», предложить другой slug |
Unauthorized | 401 | Токен отсутствует/истёк | Открыть auth-модалку, повторить после входа |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
ReferralLinkCreatedEvent | Создание ссылки (шаг 3 / A2) | in-proc: аудит; снапшот Id, OwnerUserId, Url, Channel, IsDefault (referral-link.md) |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
| — | — | Не публикуются: сама ссылка другим контекстам не нужна, наружу уходят только факты привязки (referral-link.md, раздел «События») | — |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Повторная выдача default-ссылки возвращает ту же ссылку, дубль не создаётся (INV-REF-L2) | два вызова подряд → одинаковый id |
| AC2 | Два конкурентных первых вызова создают ровно одну default-ссылку | параллельный тест: конфликт единственности default-ссылки → идемпотентный возврат |
| AC3 | Создание именованной ссылки с занятым slug → REFERRAL_LINK_URL_TAKEN | создать две ссылки с одним url |
| AC4 | Ссылка создаётся с Channel из формы; без поля — Direct | контракт-тест создания + чтение списка ссылок |
| AC5 | Инвестор, агент и менеджер застройщика — все могут создать ссылку | тест на трёх типах учёток |
| AC6 | Сгенерированный URL содержит ?referralUrl={slug} и origin фронта | unit-тест сборки URL |
| AC7 | TenantId ссылки = CompanyId владельца; null у инвестора (ADR-0052) | проверка колонки после создания агентом и инвестором |
| AC8 | Именованная ссылка с зарезервированным slug (admin/api/www) → ValidationError (INV-REF-L3) | тест на каждом зарезервированном slug |
| AC9 | Исчерпание 3 попыток генерации случайного slug → retriable-ошибка сервера (5xx); клиент повторяет, slug вводить не нужно | тест с замоканной коллизией генератора |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (2): UC-REF-002. Аноним открыл реферальную ссылку (cookie 30 дней, validateReferralLink), UC-REF-006. Ссылка от инвестора (не агента/застройщика) → связь без лида (модификатор R5)
- scenarios (2): UC-OVR-001. Запрос консультации у агента недвижимости, UC-OVR-004. От реферальной ссылки до объекта у собственника
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API методы: get-or-create-default-referral-link.md, create-referral-link.md, get-my-referral-links.md.
- Domain entity: ../domain/referral-link.md (поля, INV-REF-L1…L6, enum
ReferralChannel). - Правила привязки (что происходит по ссылке дальше): ../domain/attach-rules.md.
- Frontend: раздел «Партнёрская программа», компонент шаринга (prototype-mapping.md).
- Связанные UC: UC-REF-002 (открытие ссылки), UC-REF-003 (регистрация по ссылке).