feat: prepare Didit KYC and KYB migration - #74
Open
aledefra wants to merge 3 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prepares the backend for migrating KYC/KYB onboarding from Sumsub to Didit by introducing a provider-neutral verification session + webhook persistence model, adding Didit-specific validation/risk logic, and wiring new endpoints/health probes and CI coverage while keeping Sumsub as the default provider.
Changes:
- Add verification persistence primitives (sessions, durable webhook events, notifications) plus migration preflight for unique KYC emails.
- Introduce Didit webhook signature verification/canonicalization, decline/risk classification, and policy/config plumbing.
- Add new verification + health HTTP endpoints, background worker lifecycle wiring, and a dry-run-by-default cutover CLI with runbook and CI workflow.
Reviewed changes
Copilot reviewed 69 out of 70 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| storage/verificationWorkflowStorer_test.go | Adds storage-level workflow tests for locking, idempotency, projection application, and dead-letter behavior. |
| storage/verificationStorer.go | Implements persistence APIs for verification sessions and durable webhook events. |
| storage/verificationStorer_test.go | Adds unit tests for verification session/event persistence and idempotency. |
| storage/kycStorer.go | Makes KYC upsert concurrency-safe with conflict detection and explicit field assignments. |
| storage/kycStorer_test.go | Adds tests for KYC upsert semantics, uniqueness migration preflight, and concurrency. |
| storage/init_test.go | Introduces isolated-schema PostgreSQL harness for storage tests (opt-in by env). |
| storage/healthStorer.go | Adds DB ping helper for readiness probes. |
| storage/conn.go | Adds KYC email uniqueness preflight and migrates new verification tables. |
| storage/allocationStorer_test.go | Ensures storage DB harness is required and adds missing UserInfo setup for test. |
| storage/accountStorer.go | Adds transactional account update + KYC creation helper. |
| storage/accountKycStorer_test.go | Adds tests ensuring transaction rollback/commit behavior for account+KYC updates. |
| service/verificationWorker_test.go | Adds tests for worker logic (retry backoff, terminal mapping, inactive worker). |
| service/verificationSessionIntegration_test.go | Adds PostgreSQL integration tests for concurrent Didit retry session creation and Sumsub monitoring recovery. |
| service/verificationService_test.go | Adds tests for session response validation, Didit URL allowlist, mapping/policy behaviors, and monitoring rules. |
| service/testdata/didit/error_rate_limited_429.json | Adds Didit API error fixture (429). |
| service/testdata/didit/error_invalid_workflow_400.json | Adds Didit API error fixture (400). |
| service/testdata/didit/decision_user_declined_minimum_age.json | Adds Didit decision fixture for minimum-age decline. |
| service/testdata/didit/decision_user_declined_expired_document.json | Adds Didit decision fixture for expired document decline. |
| service/testdata/didit/decision_user_approved.json | Adds Didit decision fixture for approved user. |
| service/testdata/didit/decision_user_approved_incomplete.json | Adds Didit decision fixture for incomplete approval payload. |
| service/testdata/didit/decision_business_declined_blocked_country.json | Adds Didit decision fixture for blocked-country KYB decline. |
| service/testdata/didit/decision_business_approved.json | Adds Didit decision fixture for approved business. |
| service/testdata/didit/create_session_201.json | Adds Didit create-session success fixture. |
| service/sumsubService.go | Adds provider ownership helpers and revoke-only grandfathered monitoring processing. |
| service/sumsubMonitoringPolicy_test.go | Adds tests asserting revoke-only monitoring behavior and ownership constraints. |
| service/diditWebhookValidation_test.go | Adds tests for Didit webhook envelope + family/kind validation rules. |
| service/diditSignature.go | Implements Didit signature verification (v2/raw/simple), envelope decode, and canonical JSON serialization. |
| service/diditSignature_test.go | Adds signature vectors and negative tests for Didit signature verification. |
| service/diditRisk.go | Implements Didit decline classification policy (reason codes, warnings, AML signals, country restrictions). |
| service/diditRisk_test.go | Adds tests for decline classification, projections, and restricted country set. |
| service/diditPolicy_test.go | Adds tests for building runtime Didit KYC/KYB policy sets and mapping constraints. |
| service/diditClient_sandbox_test.go | Adds opt-in live sandbox contract test for Didit client. |
| service/accountService.go | Uses transactional account update + KYC creation during email confirmation. |
| proxy/server.go | Wires VerificationService into server lifecycle, starts worker on Run, stops on Shutdown. |
| proxy/handlers/verification.go | Adds provider-neutral verification session endpoint and Didit webhook ingestion endpoint. |
| proxy/handlers/verification_test.go | Adds test ensuring oversized Didit webhooks are rejected before service execution. |
| proxy/handlers/sumsub.go | Adds Sumsub disablement when inactive, provider-ownership checks, and durable monitoring persistence with body limits. |
| proxy/handlers/sumsub_test.go | Adds test ensuring oversized Sumsub webhooks are rejected early. |
| proxy/handlers/health.go | Adds /health/live and /health/ready endpoints with DB ping + provider reporting. |
| model/verification.go | Adds GORM models/constants for verification sessions, webhook events, and notifications. |
| model/verification_test.go | Adds schema/index tests for verification model unique indexes. |
| model/kyc.go | Adds verification_provider ownership, unique email constraint, and cutover preservation helper. |
| model/kyc_test.go | Adds tests for provider-cutover preservation helper. |
| model/didit.go | Adds Didit API models (sessions/decisions/entities/features/warnings). |
| go.mod | Adds dependency for country handling (github.com/biter777/countries). |
| go.sum | Updates dependency checksums accordingly. |
| docs/didit-migration-runbook.md | Adds detailed operator runbook for cutover invariants, dry-run/apply, and safety gates. |
| deployScripts/deploy_testnet.py | Adds verification provider + Didit env vars to deployment template. |
| deployScripts/deploy_mainnet.py | Adds verification provider + Didit env vars to deployment template. |
| deployScripts/deploy_devnet.py | Adds verification provider + Didit env vars to deployment template. |
| config/didit_verification_config_test.go | Adds tests for Didit env override parsing/validation behavior. |
| config/config.testnet.json | Adds Verification/Didit config blocks for testnet defaults. |
| config/config.mainnet.json | Adds Verification/Didit config blocks for mainnet defaults (inactive Didit IDs by default). |
| config/config.go | Adds Verification and Didit config structs plus env override parsing. |
| config/config.devnet.json | Adds Verification/Didit config blocks and questionnaire IDs for devnet defaults. |
| cmd/verification-cutover/main.go | Adds dry-run-by-default cutover CLI entry point. |
| cmd/verification-cutover/cutover_test.go | Adds unit tests for cutover option + aggregate validation and DATABASE_LINK parsing. |
| cmd/verification-cutover/cutover_integration_test.go | Adds PostgreSQL integration test for apply transaction invariants. |
| cmd/main.go | Updates graceful shutdown wiring to stop verification worker and trap SIGTERM. |
| .github/workflows/test.yml | Adds CI workflow with PostgreSQL service and targeted/race test coverage for new verification paths. |
| .env.example | Documents new verification provider + Didit environment variables. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+147
to
+150
| if event.EventId == "" || | ||
| event.EventType == "" { | ||
| return false, errors.New("verification webhook provider, environment, event id, and event type are required") | ||
| } |
| require.Equal(t, latestSandbox.Uuid, latest.Uuid) | ||
| } | ||
|
|
||
| func TestUpdateVerificationSessionPersistsEmptyAndNilValues(t *testing.T) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Why
Ratio1 is migrating individual KYC and company KYB onboarding from Sumsub to Didit without overlapping providers or weakening approval, invoicing, AML, country, or access-control gates.
The backend remains configured for Sumsub by default. Merging this PR does not activate Didit or modify production data.
Companion
Data and rollout safety
sumsubapprovedandfinalRejectedlegacy KYCs are preservedVerification
go vetgo build ./...Local Didit Sandbox E2E
Verified the full local chain with a disposable PostgreSQL 15 database, local backend, local dApp, and a real SIWE-authenticated test account:
vendor_datastatus.updatedwebhooks through a temporary HTTPS tunnel; the backend returned HTTP 202The browser-automated camera could not satisfy Didit's liveness check, so a Sandbox approval was simulated in the Didit console to exercise the terminal webhook path. Since the decision lacked required liveness, proof-of-address, and questionnaire evidence, the backend correctly failed closed to
onHoldwithapproval_evidence_missing; it did not create an approved identity/invoicing projection.No production database or production webhook was touched. The temporary tunnel, local services, disposable database, generated credentials, and test artifacts were cleaned up, and the Didit Sandbox webhook destination was restored to its previous disabled state.