04 kyc state machine
Машина состояний KYC-верификации: от загрузки документов до fully_verified.
Диаграмма состояний¶
stateDiagram-v2
[*] --> pending: initializeKyc()
pending --> in_review: submitForReview()
in_review --> ocr_complete: OCR успешно (kyc-service)
in_review --> ocr_failed: OCR ошибка
ocr_failed --> in_review: retryKycProcessing()
ocr_complete --> pending_operator_review: confirmKycData()
pending_operator_review --> auto_approved: faceMatchScore >= 0.85
pending_operator_review --> manual_review: faceMatchScore < 0.85
auto_approved --> approved: operatorApprove() [или авто]
manual_review --> approved: operatorApprove()
approved --> pending: operatorReject()
approved --> fully_verified: finalizeVerification()
Описание статусов¶
| Статус | Кто устанавливает | Что происходит | Следующий шаг |
|---|---|---|---|
pending |
initializeKyc() |
Запись KYC создана, документы ещё не загружены | Пользователь загружает фото документов |
in_review |
submitForReview() |
Документы отправлены в kyc-service через Redis Stream | OCR обработка |
ocr_failed |
kyc-service callback | OCR не смог распознать документы | retryKycProcessing() → in_review |
ocr_complete |
kyc-service callback | OCR успешно вернул данные + faceMatchScore |
Пользователь подтверждает данные |
pending_operator_review |
confirmKycData() |
PII сохранён, кошелёк создан; ждёт auto-verify или оператора | autoVerify() или оператор |
auto_approved |
autoVerify() |
faceMatchScore >= 0.85; переходит без оператора |
operatorApprove() (авто) |
manual_review |
autoVerify() |
faceMatchScore < 0.85; требует ручной проверки |
operatorApprove() оператором |
approved |
operatorApprove() |
Оператор одобрил; ждёт финализации | finalizeVerification() |
fully_verified |
finalizeVerification() |
KYC завершён; user.status → active, профиль заблокирован |
Нет |
Файл модели: lib/src/models/kyc_verification.spy.yaml
autoVerify¶
Метод KycService.autoVerify() вызывается после pending_operator_review:
| Условие | Переход | Примечание |
|---|---|---|
faceMatchScore >= 0.85 |
auto_approved |
Tier повышается до STANDARD если не переопределён админом |
faceMatchScore < 0.85 |
manual_review |
submitForManualReview() — уведомление операторам |
Порог 0.85 зашит как константа в kyc_service.dart строка 301: if (faceMatchScore >= 0.85).
Файл: lib/src/services/kyc_service.dart
confirmKycData()¶
Выполняется атомарно в несколько этапов:
- Валидация PiiInput —
fullName,phone,nationalId,addressне пустые - F-1 Guard —
profile.isLocked == true→ отклонить (профиль уже финализирован) - Upsert user_profile через
PiiProfileWriter(вне транзакции — отдельный DB call с hash/mask/blob) - Атомарная транзакция:
users.termsAcceptedAt = nowkyc_verifications.status = pending_operator_reviewusers.status = pending_operator_reviewWalletCreationService.ensureWallet()→ TigerBeetle account (через PMPOST /accounts)- На ошибку создания кошелька →
PendingWalletCreationCall(retry: 2 мин → 10 мин → 1 ч × 21, max 24 попытки, ~21 ч суммарно)
Файл: lib/src/services/kyc_service.dart, строки 135–285
finalizeVerification()¶
Вызывается оператором после approved → переводит в fully_verified:
kyc_verifications.status → fully_verifiedusers.status → activeuser_profiles.isLocked → true(F-1: PII становится иммутабельным)accountTier → STANDARD(если!accountTierOverriddenByAdmin)- Audit log:
action: 'profile_locked' - Push-уведомление «KYC подтверждён» (локаль по профилю)
- [PLAN] IPPS Stage 2 — регистрация внешнего кошелька через
PM POST /accounts/external/ipps(не реализовано; TODO в строке 482)
Файл: lib/src/services/kyc_service.dart, строки 396–520