Перейти к содержанию

Kyc

Методы KycService

Метод Вход Действие → Статус KYC
initializeKyc S3 ключи, userId создать запись KycVerification pending
submitForReview userId status → in_review + fire-and-forget KycQueueService in_review
retryKycProcessing userId повтор (только из ocr_failed) + fire-and-forget in_review
getOcrResult userId расшифровать AES-256-GCM ocrResult blob
getFailureReason userId расшифровать failureCode из ocrResult (только при ocr_failed)
getKycStatus userId вернуть текущий kyc.status
confirmKycData userId, PiiInput PII write + wallet provision + audit pending_operator_review
autoVerify userId, faceMatchScore, ocrResult score ≥ 0.85 → auto_approved; иначе → submitForManualReview auto_approved или manual_review
submitForManualReview userId score < 0.85 → manual_review manual_review
operatorApprove userId оператор одобрил + approvedAt approved
operatorReject userId, reason оператор отклонил: kyc → pending, user → kyc_pending pending
finalizeVerification userId активировать user, заблокировать профиль, push fully_verified

getOcrResult — формат blob

kyc-service пишет в ocrResult AES-256-GCM зашифрованный JSON двух форм: - { "ocrData": {...}, "faceMatchScore": N } — стандартный - flat map { "fullName": ..., "faceMatchScore": N } — упрощённый

getOcrResult() поддерживает оба варианта.

getFailureReason — коды ошибок

Значение Смысл
invalid_passport документ не прошёл OCR
invalid_selfie selfie не прошла face match
processing_error внутренняя ошибка kyc-service

confirmKycData

sequenceDiagram
    App->>KycEndpoint: confirmKycData(piiInput)
    KycEndpoint->>KycService: confirmKycData(userId, piiInput)
    KycService->>KycService: validate PiiInput (fullName, nationalId, nationality, address, phone)
    KycService->>KycService: check profile.isLocked (F-1 guard)
    KycService->>PiiProfileWriter: upsert(userId, piiInput)
    PiiProfileWriter->>DB: encryptedPii + phone hash + masks (вне транзакции)
    KycService->>DB: termsAcceptedAt + audit(kyc_confirmed) + kyc→pending_operator_review + user→pending_operator_review [tx]
    KycService->>WalletCreationService: ensureWallet(userId, THB)
    WalletCreationService->>PM: POST /accounts (HMAC)
    alt PM недоступен
        KycService->>FutureCall: PendingWalletCreationCall (attempt=1, delay=30s)
        Note over FutureCall: backoff 30s → 2m → 10m → 1h, cap attempt=24 (~21h)
    end
    Note over KycService: сбой wallet provision НЕ откатывает confirmKycData

Пользователь получает tbAccountId до апрува оператора — UI показывает "wallet exists, balance 0".

finalizeVerification

Выполняется атомарно внутри одной DB-транзакции:

  1. kyc_verifications.statusfully_verified, operatorReviewedAt = now
  2. users.statusactive
  3. user_profiles.isLockedtrue, lockedAt = now (F-1: профиль становится иммутабельным)
  4. Audit log: action=profile_locked, performedBy=operator
  5. accountTierSTANDARD (если accountTierOverriddenByAdmin == false)

После транзакции (fire-and-forget):

  1. Push "KYC подтверждён" через NotificationPublisher (locale из profile.languageCode)
  2. [PLAN] IPPS Stage 2 — регистрация внешнего кошелька: POST /accounts/external/ipps в PM (endpoint не реализован, stub logging)

Locale для push-уведомления

languageCode locale
TH th
RU, KZ, BY, UA ru
иное en

autoVerify

Порог faceMatchScore >= 0.85 — хардкод в KycService.autoVerify().

  • При прохождении: kyc.statusauto_approved, accountTierSTANDARD (если не overridden admin)
  • При провале: делегирует в submitForManualReview()manual_review

KYC state machine

pending
  └─ submitForReview() ──────────────► in_review
                                          ├─ kyc-service OCR OK ──► ocr_complete
                                          │                             └─ confirmKycData() ──► pending_operator_review
                                          │                                                        ├─ autoVerify ≥ 0.85 ──► auto_approved
                                          │                                                        │                            └─ finalizeVerification() ──► fully_verified
                                          │                                                        └─ autoVerify < 0.85 ──► manual_review
                                          │                                                                                    ├─ operatorApprove() ──► approved
                                          │                                                                                    │                          └─ finalizeVerification() ──► fully_verified
                                          │                                                                                    └─ operatorReject() ──► pending (→ restart)
                                          └─ kyc-service OCR fail ──► ocr_failed
                                                                          └─ retryKycProcessing() (only from ocr_failed)