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

POST /api/v1/profile/verification — Создание черновика заявки KYC (createVerificationRequest)

Назначение

Создать черновик (Draft) заявки на простую верификацию инвестора (KYC/AML) по персоне Relation = Self владельца аккаунта и, опционально, предзаполнить его из шаблона реквизитов. Черновик — рабочая заготовка: инвестор дозаполняет данные персоны/сканы и затем отправляет заявку операцией submitVerification (submit-verification.md) — только там строится иммутабельный зашифрованный снапшот и публикуется событие в шину. На создании снапшот не формируется, шифрование данных не выполняется, PII в ответ не попадает. Верифицируется всегда учётная запись (OwnerUserId); предмет проверки — персона Self (INV-VRF-3). Операцию вызывает инвестор из ЛК.

Metadata

ПолеЗначение
Сервис/контекстprofile
API areapublic
Feature groupverification
Статусdraft
Документdocs/06-services/profile/api/public/create-verification-request.md

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

ПроверкаЗначение
Actor typeUser (Bearer JWT), UserType = Investor (INV-VRF-5)
Auth policy / Permissionsauthenticated; permission profile.verification.submit
Scope (RBAC)self
Record-levelOwnerUserId = UserId из sub (из body не принимается)
Tenant isolationне применяется (ADR-0052: инвестор вне tenant)
Auditcreate: CreatedByUserId = UserId, Version = 1, событие VerificationRequestCreatedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/profile/verification
Success status201 + Location: /api/v1/profile/verification/{id}
Idempotency headerIdempotency-Key (опционально; двойной сабмит формы дополнительно гасит INV-VRF-1)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса (body CreateVerificationRequestForm)

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДавалидный JWT401 без токена
personIdbodyGuidДаNotEmpty; должна ссылаться на активную персону владельца с Relation = Self (INV-VRF-3)чужая/не Self → см. ошибки
requisiteTemplateIdbodyGuid?Нетесли задан — NotEmpty; активный шаблон владельца (для предзаполнения UI); информационная ссылка — фактический состав фиксируется снапшотом на Submit, не здесьчужой/архивный → PROFILE_TEMPLATE_NOT_FOUND

Модель ответа VerificationRequestModel

VerificationRequestModel — результат команды (проекция свежесозданного черновика: без снапшота и PII); её дом — этот документ. Полную read-проекцию заявки (MyVerificationModel: статус, решение ревьюера, сканы) отдаёт get-my-verification.md.

ПолеТипОбязательностьИсточникMaskingОписание
idGuidДаVerificationRequestEntity.IdИдентификатор заявки.
statusVerificationRequestStatus (string)Даentity.StatusНа создании всегда Draft (enum — verification-request.md).
personIdGuidДаentity.PersonIdПерсона-предмет проверки (Self).
requisiteTemplateIdGuid?Нетentity.RequisiteTemplateIdШаблон предзаполнения (информационно) либо null.
versionlongДаentity.VersionОптимистичная версия агрегата (1 при создании).

Алгоритм

  1. Контекст и доступ. Операцию выполняет инвестор (актор User, UserId из токена, permission profile.verification.submit); tenant не применяется (ADR-0052). Повторный запрос с тем же Idempotency-Key возвращает ранее сохранённый результат без повторного создания.
  2. Проверка входа. personId задан; requisiteTemplateId — если задан, не пустой. Нарушение → ValidationError. Семантические проверки персоны/статуса верификации — на шаге 3.
  3. Проверка предусловий (сначала владелец, затем предмет проверки):
    • Профиль владельца существует; иначе PROFILE_NOT_FOUND (профиль ещё не создан консюмером identity). Профиль активен (Status = Active, иначе PROFILE_USER_BLOCKED, INV-PRF-3) и в режиме самоуправления (ContactsManagedBy = SelfService, иначе PROFILE_MANAGED_EXTERNALLY, INV-PRF-2 — менеджеры застройщика управляются внешней системой); тип пользователя — Investor (иначе PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE, INV-VRF-5/INV-PRF-4).
    • Аккаунт ещё не верифицирован: VerificationStatus != Verified; иначе PROFILE_ALREADY_VERIFIED (INV-VRF-9): новая заявка не нужна.
    • Нет незавершённой заявки владельца в статусах Draft/Submitted/InReview; иначе PROFILE_VERIFICATION_ALREADY_IN_PROGRESS (INV-VRF-1).
    • Персона personId существует и принадлежит владельцу; иначе PROFILE_PERSON_NOT_FOUND. Персона активна и её Relation = Self; иначе PROFILE_VERIFICATION_PERSON_NOT_SELF (INV-VRF-3).
    • Только если requisiteTemplateId задан: активный шаблон владельца; иначе PROFILE_TEMPLATE_NOT_FOUND (чужой/архивный/несуществующий). Проверяется только владение и активность; согласованность состава с персоной валидируется при Submit.
  4. Бизнес-правила и маппинг. Снапшот не строится: SnapshotCipher = null, SnapshotContractVersion = null, KeyVersion = null, ScanList = DocumentScanList.Empty (INV-VRF-7 — снапшот появляется только на Submit). Создаётся черновик: Status = Draft, CreatedByUserId = UserId, Version = 1; переход [*] → Draft (INV-VRF-1/3/5/9 уже проверены); фиксируется факт VerificationRequestCreatedEvent. Profile.VerificationStatus не меняется (переход в Pending — только на Submit, INV-PRF-6).
  5. Согласованность. В одном согласованном изменении сохраняются заявка и запись идемпотентности; гонку двойного Draft страхует уникальность активной заявки владельца (конфликт → PROFILE_VERIFICATION_ALREADY_IN_PROGRESS).
  6. Результат. Модель VerificationRequestModel (id, status = Draft, personId, requisiteTemplateId?, version = 1). HTTP 201 + Location: /api/v1/profile/verification/{id}. Интеграционных событий нет — событие в шину (VerificationRequestSubmittedKafkaEvent, pin.profile.verification-requests.v1) публикуется лишь на Submit (verification-request-submitted.md).

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/просрочен JWTбез телаre-login
ValidationError400personId/requisiteTemplateId пусты/некорректныперечень невалидных полейno retry
PROFILE_NOT_FOUND404Профиль не создан консюмером«Профиль ещё создаётся»retry с backoff
PROFILE_USER_BLOCKED400Status != Active (INV-PRF-3)«Учётная запись заблокирована»no retry
PROFILE_MANAGED_EXTERNALLY403ContactsManagedBy = TalentSync«Данные управляются внешней системой»no retry
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE403UserType != Investor (INV-VRF-5)«Доступно только инвесторам»no retry
PROFILE_PERSON_NOT_FOUND404Персона не найдена / чужая«Персона не найдена»no retry
PROFILE_VERIFICATION_PERSON_NOT_SELF409Relation != Self или персона в архиве (INV-VRF-3)«Верификация доступна только по основной персоне»no retry
PROFILE_TEMPLATE_NOT_FOUND404requisiteTemplateId задан, но чужой/архивный/несуществующий«Шаблон реквизитов не найден»no retry
PROFILE_VERIFICATION_ALREADY_IN_PROGRESS409Есть заявка Draft/Submitted/InReview (INV-VRF-1)«Заявка на верификацию уже в работе»no retry
PROFILE_ALREADY_VERIFIED409VerificationStatus = Verified (INV-VRF-9)«Аккаунт уже верифицирован»no retry

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

  • Форма расширяется только optional-полями (additive); удаление/переименование personId — breaking (migration + rollout). Снапшот и PII в контракте создания отсутствуют по построению.
  • Logs: UserId, RequestId, PersonId, RequisiteTemplateId? (без значений PII — их на создании нет); metrics: profile_api_operations_total{operation="createVerificationRequest",result}, profile_verification_drafts_created_total; traces — OTel; dashboard profile-overview.

Acceptance Criteria

  • Given инвестор с активной персоной Self, без незавершённой заявки и с аккаунтом не Verified, When POST personId = Self, Then 201 + Location; заявка Draft, снапшот null (SnapshotCipher/SnapshotContractVersion/KeyVersion = null, ScanList = Empty), Version = 1; Profile.VerificationStatus не меняется; событие в шину не публикуется (публикуется только на Submit).
  • Given задан свой активный requisiteTemplateId, When POST, Then 201; шаблон ссылается информационно (фактический состав фиксируется снапшотом на Submit, не здесь).
  • Given requisiteTemplateId чужой/архивный/несуществующий, When POST, Then PROFILE_TEMPLATE_NOT_FOUND (404).
  • Given personId не Self или персона в архиве, When POST, Then PROFILE_VERIFICATION_PERSON_NOT_SELF (409) — INV-VRF-3.
  • Given personId чужой, When POST, Then PROFILE_PERSON_NOT_FOUND (404).
  • Given есть заявка владельца в Draft/Submitted/InReview, When POST, Then PROFILE_VERIFICATION_ALREADY_IN_PROGRESS (409) — INV-VRF-1.
  • Given аккаунт уже Verified, When POST, Then PROFILE_ALREADY_VERIFIED (409) — INV-VRF-9.
  • Given UserType != Investor / TalentSync / профиль заблокирован / профиль не создан, When POST, Then PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE (403) / PROFILE_MANAGED_EXTERNALLY (403) / PROFILE_USER_BLOCKED (400) / PROFILE_NOT_FOUND (404) соответственно.
  • Given повтор с тем же Idempotency-Key, When POST, Then возвращается ранее сохранённый результат без создания второго черновика.
  • Given два конкурентных создания черновика, When оба POST, Then одно 201, второе — PROFILE_VERIFICATION_ALREADY_IN_PROGRESS (409) (страховка уникальностью активной заявки владельца).

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

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

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