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

UC-REF-001. Создание реферальной ссылки с каналом и шаринг

Назначение

Пользователь получает свою реферальную ссылку и делится ею (копирование, Telegram, WhatsApp, VK). Default-ссылка выдаётся идемпотентно — повторный запрос возвращает ту же ссылку; именованные ссылки под каналы создаются в разделе «Партнёрская программа». Ссылка нужна, чтобы приводить новых инвесторов: переход по ней запускает правила привязки (attach-rules.md). Связан с контекстом referral.

Metadata

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

Актор

ПолеЗначение
РольЛюбой авторизованный пользователь: Инвестор / Агент / Девелопер (менеджер застройщика) / МенеджерПИН («реферальные ссылки может формировать любой пользователь системы»)
Permissions / scopereferral.links.manage-self (создание/изменение), referral.links.view-self (чтение своих) — каталог permission.md
TenantTenantId = 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-ссылка; раздел «Партнёрская программа» — управление именованными ссылками по каналам

Основной Поток

  1. Пользователь нажимает «Поделиться» (скопировать ссылку или иконка соцсети).
  2. Если реферальная ссылка уже получена ранее — используется она; иначе система выдаёт default-ссылку (операция get-or-create-default-referral-link.md).
  3. Выдача 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.
  4. Клиент строит полный URL из slug: {origin}{path}?referralUrl={slug} (query-параметр referralUrl; бэкенд хранит только slug — referral-link.md).
  5. Действие шаринга: «Скопировать ссылку» → буфер обмена, toast «Ссылка скопирована»; иконка соцсети → окно шаринга Telegram / WhatsApp / VK.
  6. Получатель ссылки открывает URL — продолжение в UC-REF-002 (cookie 30 дней), далее правила привязки attach-rules.md (UC-REF-003…006).

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

A1. Slug уже получен на клиенте

  1. Ссылка уже закеширована на клиенте — возвращается без запроса к API.
  2. Поток продолжается с шага 4 основного потока.

A2. Именованная ссылка под канал («Партнёрская программа»)

  1. Пользователь заполняет форму: url? (slug ≤ 64, опционально — иначе генерация), description? (≤ 256), channel (enum ReferralChannel, дом — referral-link.md; default Direct).
  2. Создание ссылки (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 — из формы.
  3. Переходы/регистрации/связи по этой ссылке попадут в статистику per-канал (get-partner-stats, README.md). Шаринг конкретной ссылки — с той же страницы (шаги 4–5 основного потока).

A3. Ошибка создания default-ссылки

  1. API вернул ошибку → клиент показывает toast «Не удалось создать ссылку».
  2. Шаринг не выполняется; пользователь повторяет действие. Завершение.

Постусловие

  • У пользователя существует ровно одна default-ссылка (IsDefault = true, INV-REF-L2) и/или новая именованная ссылка с заполненным Channel; slug глобально уникален среди неудалённых (INV-REF-L1).
  • Реферальный URL с ?referralUrl={slug} передан получателю выбранным способом шаринга.
  • Новых записей журнала/связей нет — они появляются только при срабатывании правил привязки (attach-rules.md).

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
ValidationError400Формат slug/description/channel нарушен (INV-REF-L3)Подсветить поле формы
REFERRAL_LINK_URL_TAKEN409Slug занят другой неудалённой ссылкой (INV-REF-L1)Сообщение «адрес занят», предложить другой slug
Unauthorized401Токен отсутствует/истёкОткрыть 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
AC7TenantId ссылки = CompanyId владельца; null у инвестора (ADR-0052)проверка колонки после создания агентом и инвестором
AC8Именованная ссылка с зарезервированным slug (admin/api/www) → ValidationError (INV-REF-L3)тест на каждом зарезервированном slug
AC9Исчерпание 3 попыток генерации случайного slug → retriable-ошибка сервера (5xx); клиент повторяет, slug вводить не нужнотест с замоканной коллизией генератора

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

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

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