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-транзакции:
kyc_verifications.status→fully_verified,operatorReviewedAt= nowusers.status→activeuser_profiles.isLocked→true,lockedAt= now (F-1: профиль становится иммутабельным)- Audit log: action=
profile_locked, performedBy=operator accountTier→STANDARD(еслиaccountTierOverriddenByAdmin == false)
После транзакции (fire-and-forget):
- Push "KYC подтверждён" через
NotificationPublisher(locale изprofile.languageCode) - [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.status→auto_approved,accountTier→STANDARD(если не 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)