DELETE /api/investors/me/referral-links/{id} — Удалить реферальную ссылку (deleteReferralLink)
Назначение
Мягко удалить собственную реферальную ссылку: строка сохраняется (DeletedAt), slug
освобождается (уникальность slug действует только среди не удалённых ссылок), журнал регистраций
и реферальные связи по ссылке остаются нетронутыми (основание выплат). Default-ссылку удалить нельзя
(INV-REF-L5 — на неё завязан шаринг фронта по умолчанию). Общий конверт — README.md.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | referral |
| API area | public |
| Feature group | referral-links |
| Статус | approved |
| Документ | docs/06-services/referral/api/public/delete-referral-link.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User |
| Auth policy / Permissions | authenticated; permission referral.links.manage-self |
| Scope (RBAC) | self |
| Record-level | INV-REF-L4: только владелец; чужая ссылка → REFERRAL_LINK_NOT_FOUND |
| Audit | delete (soft): DeletedAt/DeletedByUserId + событие ReferralLinkDeletedEvent |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | DELETE |
| Route | /api/investors/me/referral-links/{id} |
| Success status | 200 (возвращает тело DeleteReferralLinkResult, не 204) |
| Idempotency | повторный DELETE удалённой ссылки → REFERRAL_LINK_NOT_FOUND (не идемпотентен по ответу) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
Body отсутствует.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | валидный токен | 401 |
id | route | Guid | Да | валидный Guid; ссылка существует, не удалена, принадлежит актору, не default | record-level INV-REF-L4; guard INV-REF-L5 |
Модель ответа DeleteReferralLinkResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
id | string (Guid) | Да | ReferralLinkEntity.Id | — | Идентификатор удалённой ссылки (подтверждение). |
Алгоритм
- Контекст и доступ. Пользователь авторизован; требуется permission
referral.links.manage-self. - Проверка входа.
id— непустой валидный Guid, иначеValidationError. - Проверка владения и состояния. Ссылка должна существовать, быть не удалённой и принадлежать
актору (
OwnerUserId == UserId); иначе (в том числе уже удалённая) →REFERRAL_LINK_NOT_FOUND(INV-REF-L4). Если ссылка помечена как default (IsDefault == true) → отказREFERRAL_LINK_DEFAULT_UNDELETABLE(INV-REF-L5). Журнал/связи не проверяются — soft-delete их не затрагивает (referral-link.md, раздел «Связи»). - Мягкое удаление. Ссылка помечается удалённой (
DeletedAt = now(),DeletedByUserId = UserId), прочие поля не меняются; переход состоянияActive → Deleted(referral-link.md), восстановление не предусмотрено. Slug автоматически освобождается (уникальность среди не удалённых). Параллельное изменение той же ссылки →CONCURRENCY_CONFLICT. Фиксируется событиеReferralLinkDeletedEvent. В ответ возвращаетсяid.
Последствия для процесса привязки: удалённая ссылка не проходит validateReferralLink
(isValid = false → фронт очищает cookie) и не резолвится guard-ом G1
(attach-rules.md) — новые регистрации/связи по ней не создаются. Slug
становится доступен для повторного использования другими пользователями; журнал (getLinkRegistrations
показывает исторический linkUrl) и связи по ссылке живут дальше.
Гонка «удаление ↔ привязка по тому же slug»: если процесс привязки (consumer T1/T2 или resolve)
зарезолвил ссылку до коммита удаления — связь/запись журнала создаются штатно и переживают
последующий soft-delete (журнал и связь ссылаются по Id, INV-REF-L6/INV-REF-R2); если удаление
зафиксировано раньше — guard G1 не находит ссылку и привязка не выполняется (no-op, warning). Обе ветви
корректны: потерянной или дублированной связи не возникает.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
| — (auth) | 401 | Нет/невалиден токен | «Требуется авторизация» | re-login |
ValidationError | 400 | id — не Guid | перечень полей | исправить |
REFERRAL_LINK_NOT_FOUND | 400 | Не существует / чужая / уже удалена (INV-REF-L4) | «Ссылка не найдена» | no retry |
REFERRAL_LINK_DEFAULT_UNDELETABLE | 400 | IsDefault == true (INV-REF-L5) | «Ссылку по умолчанию удалить нельзя» | no retry |
CONCURRENCY_CONFLICT | 400 | Конкурентное изменение | «Данные изменены, повторите» | reload + retry |
Предусловия и постусловия
Предусловия: ссылка существует, не удалена, принадлежит актору (INV-REF-L4) и не является default (INV-REF-L5).
Постусловия (при успехе): ссылка помечена удалённой (DeletedAt/DeletedByUserId), переход
Active → Deleted, восстановление не предусмотрено; slug освобождён (уникальность действует среди не
удалённых — INV-REF-L1) и доступен для повторного использования другими пользователями; журнал
регистраций и реферальные связи по ссылке сохранены (основание выплат, linkUrl в журнале
исторический); поднято ReferralLinkDeletedEvent. Далее ссылка не проходит validateReferralLink
(isValid = false) и не резолвится guard-ом G1 — новые регистрации/связи по ней не создаются.
Acceptance Criteria
- Given авторизованный владелец и его не-default ссылка, When
DELETE …/referral-links/{id}, Then200 {id}(не 204); ссылка soft-deleted, slug освобождён, журнал и связи не тронуты, событие удаления поднято. - Given повторный DELETE уже удалённой ссылки, When запрос, Then
REFERRAL_LINK_NOT_FOUND(операция не идемпотентна по ответу). - Given ссылка default (
IsDefault = true), When DELETE, ThenREFERRAL_LINK_DEFAULT_UNDELETABLE(INV-REF-L5). - Given ссылка чужая или не существует, When DELETE, Then
REFERRAL_LINK_NOT_FOUND(INV-REF-L4, чужая/несуществующая/удалённая неразличимы — нет user enumeration). - Given параллельное изменение той же ссылки, When DELETE, Then
CONCURRENCY_CONFLICT(reload + retry). - Given
idне Guid, When запрос, ThenValidationError; без токена —401.
Совместимость и наблюдаемость
- Контракт стабилен: 200 +
{id}(не 204) — менять на 204 нельзя без обновления клиента (breaking). - Logs:
UserId,LinkId— без PII; metrics:referral_links_deleted_total; traces — сквозной trace черезtraceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): POST /api/investors/me/referral-links/default — Ссылка по умолчанию, POST /api/v1/referral-links — Создание реферальной ссылки (с каналом), Referral API — реестр операций и общие контрактные модели
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: referral-link.md (soft-delete, INV-REF-L4/L5).
- Соседние операции: get-or-create-default-referral-link.md (почему default неудаляемая), validate-referral-link.md (поведение после удаления).
- Реестр и конверт: README.md.