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

POST /api/messenger/contacts/requests/{contactId}/decline — Отклонить заявку в контакты

Назначение

Адресат отклоняет входящую заявку в контакты (Pending → Declined). Профиль инициатора не раскрывается, звонок недоступен, а пара освобождается (выходит из ограничения активной пары ux_contacts_active_pair, т.к. оно действует только для статусов Pending и Accepted) — при желании инициатор позже подаст заявку заново. Вызывает пользователь-адресат; тело ответа не возвращается. Интеграционного события нет (отклонение приватно, кросс-сервисных потребителей нет); инициатору уходит только realtime-push ContactRequestDeclined для обновления интерфейса.

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areapublic
Feature groupcontacts
Статусdraft
Документdocs/06-services/messenger/api/contacts/decline-contact-request.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (только адресат заявки)
Auth policy / Permissionsmessenger.contacts.manage (каталог — ../../../identity/domain/permission.md)
Scope (RBAC)платформа (person-level, вне tenant, ADR-0052)
Record-levelactor == AddresseeUserId (INV-CT-3)
Tenant isolationне применяется — контакты вне tenant
Auditupdate (переход Declined; RespondedByUserId = actor, RespondedAt)

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/messenger/contacts/requests/{contactId}/decline
Success status204 (отклонено); идемпотентный повтор на Declined204
Idempotency headerне применяется (идемпотентность — по целевому статусу Declined, см. Алгоритм)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claimsGuidДа (вычисляется)должен == AddresseeUserIdrecord-level (INV-CT-3)
ContactIdrouteGuidДа!= Guid.Empty; существующая связь

Модель ответа <нет тела>

Успех — 204 No Content (тела нет). Ошибки — ProblemDetails (см. ниже). Итоговый статус связи (Declined) интерфейс получает из realtime-push ContactRequestDeclined либо перезапросом list-contact-requests.md.

Предусловия и постусловия

Предусловия. Актор авторизован и имеет messenger.contacts.manage. Связь contactId существует; актор — адресат заявки (actor == AddresseeUserId, INV-CT-3). Связь в статусе Pending (или уже Declined — для идемпотентного повтора).

Постусловия. Связь в Declined, RespondedAt = now, RespondedByUserId = actor. Профиль инициатора не раскрыт, звонок недоступен. Пара выходит из ограничения активной пары ux_contacts_active_pair (оно действует только для Pending/Accepted) — инициатор может позже подать заявку заново. Интеграционное событие не публикуется (отклонение приватно); инициатору уходит только realtime ContactRequestDeclined.

Алгоритм

  1. Контекст и доступ. Операцию выполняет пользователь-адресат; требуется право messenger.contacts.manage (иначе — FORBIDDEN). Идентификатор связи contactId берётся из маршрута.
  2. Проверка входа. ContactId непуст — иначе VALIDATION_FAILED.
  3. Загрузка связи и проверка прав. Найти связь по contactId; если её нет — NOT_FOUND. Актор должен быть адресатом заявки (actor == AddresseeUserId, INV-CT-3); иначе — MSG_CONTACT_FORBIDDEN.
  4. Бизнес-правила и переход. Идемпотентность: если связь уже Declined — ничего не менять (204). Если связь уже Accepted или Cancelled — операция недопустима (MSG_CONTACT_INVALID_STATE; удаление подтверждённого контакта — remove-contact.md). Иначе перевести связь Pending → Declined: RespondedAt = now, RespondedByUserId = actor. Параллельная гонка Decline/Accept — конфликт MSG_CONTACT_CONFLICT.
  5. Что меняется. Обновляется единственный агрегат связи; пара освобождается для новой заявки. Переход и аудит фиксируются в одном согласованном изменении.
  6. Факты и события. Интеграционное событие не публикуется (отклонение приватно). Инициатору уходит только realtime-push ContactRequestDeclined (адресат уже видит результат).
  7. Результат. 204 No Content. Ошибки — 400/403/404/409.

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событиянет (отклонение приватно)
Проекции / поискосвобождается ограничение активной пары ux_contacts_active_pair — пара доступна для новой заявки
Уведомленияrealtime-push ContactRequestDeclined инициатору (без email/push из notifications)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ContactId пуст/некорректен«Проверьте запрос»no retry
FORBIDDEN403Нет permission«Недостаточно прав»no retry
MSG_CONTACT_FORBIDDEN403INV-CT-3: актор не адресат заявки«Отклонить может только адресат»no retry
NOT_FOUND404Связь не найдена«Заявка не найдена»no retry
MSG_CONTACT_INVALID_STATE409Заявка Accepted/Cancelled«Заявка недоступна для отклонения»no retry
MSG_CONTACT_CONFLICT409Гонка Decline/Accept«Повторите — состояние изменилось»retry (перечитать)

Повтор на уже Declinedне ошибка: идемпотентный 204.

Acceptance Criteria

  • Отклонение (happy). Дано: связь Pending, актор — адресат. Когда отклоняет. Тогда связь → Declined, 204; пара освобождается для новой заявки; инициатору уходит realtime ContactRequestDeclined; профиль не раскрыт, Kafka-события нет.
  • Идемпотентный повтор. Дано: связь уже Declined. Когда актор отклоняет снова. Тогда 204 без изменений.
  • Не адресат. Дано: актор — инициатор/третье лицо. Когда пытается отклонить. Тогда 403 MSG_CONTACT_FORBIDDEN (INV-CT-3).
  • Недопустимый статус. Дано: связь Accepted или Cancelled. Когда актор отклоняет. Тогда 409 MSG_CONTACT_INVALID_STATE (удаление подтверждённого контакта — через remove-contact).
  • Гонка Decline/Accept. Дано: параллельные отклонение и подтверждение. Тогда побеждает один переход; проигравший — 409 MSG_CONTACT_CONFLICT, перечитывает состояние.
  • Нет связи. Дано: contactId не существует. Тогда 404 NOT_FOUND.
  • Повторная заявка после отклонения. Дано: связь Declined, пара свободна. Когда инициатор подаёт заявку заново. Тогда новая заявка создаётся (ограничение активной пары не мешает).

Совместимость и наблюдаемость

  • Контракт стабилен (нет тела ответа); ужесточение guard-ов — breaking для UI-сценариев.
  • Logs: actor, contactId (без ФИО/телефона); metrics messenger_contact_requests_declined_total{result}, latency p95; traces через traceparent.

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

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

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