Skip to content

feat: migrate KYC and KYB UX to Didit - #65

Open
aledefra wants to merge 3 commits into
developfrom
codex/didit-migration
Open

feat: migrate KYC and KYB UX to Didit#65
aledefra wants to merge 3 commits into
developfrom
codex/didit-migration

Conversation

@aledefra

@aledefra aledefra commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the Sumsub-only verification screen with a provider-neutral KYC/KYB session flow
  • show explicit consent before creating a provider session
  • embed Didit hosted sessions with a strict verify.didit.me/session/... URL boundary
  • retain Sumsub rendering for rollback/grandfathered compatibility
  • poll backend-authoritative account state and stop after a terminal result
  • add deterministic desktop/mobile preview coverage and verification contract tests
  • allow local development to point at a local backend through an optional same-origin Vite proxy

Why

The UI must switch to Didit at the same cutover as the backend while keeping credentials out of browser URLs and preserving the familiar Sumsub-style verification journey.

Merging this PR does not activate Didit by itself; the backend provider switch remains the source of truth.

Companion

Verification

  • npm test — 15 tests
  • npm run lint
  • npm run build
  • desktop and 430px mobile visual checks
  • npm audit --omit=dev — 0 critical advisories; existing unrelated dependency debt remains
  • independent critic review

Local Didit Sandbox E2E

Verified the full local chain using a disposable PostgreSQL database and a real SIWE-authenticated test account:

  1. local dApp created a Didit individual-verification session through the local backend
  2. Didit hosted flow opened and preserved the backend's internal KYC UUID as vendor data
  3. Didit sent signed status.updated webhooks through a temporary HTTPS tunnel; the backend returned HTTP 202
  4. duplicate deliveries were accepted idempotently and produced one processed event per provider event ID
  5. authoritative reconciliation updated the local verification session and KYC record
  6. dApp polling displayed the backend result: submitted and under review

The browser-automated camera could not satisfy Didit's liveness check. A Sandbox approval was therefore simulated in the Didit console. Because the session lacked the required liveness, proof-of-address, and questionnaire evidence, the backend correctly failed closed to onHold with approval_evidence_missing instead of blindly trusting the provider's Approved label.

No production database or production webhook configuration was touched. The temporary tunnel, local services, disposable database, test credentials, and browser state were cleaned up; the Didit Sandbox webhook was restored to its previous disabled state.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying ratio1-dapp-test with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3393653
Status: ✅  Deploy successful!
Preview URL: https://ef397940.ratio1-dapp-test.pages.dev
Branch Preview URL: https://codex-didit-migration.ratio1-dapp-test.pages.dev

View logs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the dApp’s KYC/KYB user journey from a Sumsub-only screen to a provider-neutral verification session flow that can embed Didit hosted sessions (while retaining Sumsub support for rollback), adds an explicit consent gate, and introduces backend-authoritative status polling plus deterministic preview/test coverage.

Changes:

  • Introduces shared verification session typing + runtime parsing/validation (including strict Didit hosted URL boundary) and wires a new /verification/session backend API into the frontend.
  • Reworks the /kyc page to show a consent gate, launch/refresh provider sessions (Didit iframe or Sumsub SDK), and poll account status to a completed “status updated” stage.
  • Adds Vitest + CI test step, along with unit tests for session parsing and polling behavior, and expands /playwright-preview with deterministic verification states.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/typedefs/verification.ts Adds provider-neutral verification session types and a strict parser (including Didit URL validation).
src/typedefs/verification.test.ts Adds Vitest coverage for session parsing and URL boundary enforcement.
src/pages/PlaywrightPreview.tsx Adds deterministic preview states for the new verification experience (desktop/mobile friendly).
src/pages/KYC.tsx Replaces the Sumsub-only flow with consent + provider-neutral session launch, embed rendering, and status polling.
src/pages/EmailConfirmation.tsx Updates user-facing copy to reference KYC/KYB instead of only KYC.
src/pages/Dashboard.tsx Uses applicant type to render “KYC” vs “KYB” labels in gating CTA copy.
src/lib/verification-status-polling.ts Introduces a reusable polling helper for backend-authoritative verification status refresh.
src/lib/verification-status-polling.test.ts Adds unit tests ensuring polling stops/cleans up correctly.
src/lib/utils.tsx Adds helper label functions for “KYC” vs “KYB” (short/long variants).
src/lib/routes/routes.tsx Updates route metadata copy and simplifies navigation-route filtering logic.
src/lib/api/backend.tsx Adds createVerificationSession calling /verification/session and parsing the response.
src/components/Verification/VerificationExperience.tsx Adds a shared UX component for consent/loading/provider/error/status stages.
src/components/Profile/sections/PersonalInformation.tsx Updates profile KYC/KYB section to link into the new session flow (no URL token).
src/components/Licenses/modals/LicenseLinkModal.tsx Updates KYC copy to reflect KYC vs KYB using new label helpers.
src/components/Licenses/modals/LicenseBulkLinkModal.tsx Same as above for the bulk-link flow.
package.json Adds npm test and pins vitest as a dev dependency.
package-lock.json Locks Vitest (and transitive deps) into the dependency tree.
.github/workflows/pr_lint_build.yml Adds a CI “Test” step running npm test.
Comments suppressed due to low confidence (1)

src/components/Verification/VerificationExperience.tsx:173

  • Because this link sits inside the clickable , clicking “Terms & Conditions” can toggle the checkbox unexpectedly. Add a click handler to prevent the label default toggle behavior when the link is clicked.
                        <Link
                            className="text-primary font-medium underline"
                            to={localTermsPath}
                            target="_blank"
                            rel="noreferrer"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +160 to +165
<Link
className="text-primary font-medium underline"
to={localPrivacyPolicyPath}
target="_blank"
rel="noreferrer"
>
Comment thread src/pages/KYC.tsx
Comment on lines +126 to +135
const refreshSumsubAccessToken = async (currentSession: SumsubVerificationSession) => {
const refreshedSession = await createVerificationSession(currentSession.applicantType);

if (refreshedSession.provider !== 'sumsub') {
throw new Error('Verification provider changed. Reload the page to continue.');
}

setSession(refreshedSession);
return refreshedSession.accessToken;
};
Copilot AI review requested due to automatic review settings July 29, 2026 16:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

src/components/Profile/sections/PersonalInformation.tsx:71

  • On subscription update failure, the UI remains in the optimistically-toggled state and the user can also fire overlapping requests. Guard against re-entry while a request is in flight, and revert the optimistic toggle when the API call fails.
    const toggleEmailSubscriptionPreference = async () => {
        // We set the new value optimistically
        setSubscribed(!isSubscribed);

        const apiCall: () => Promise<ApiAccount> = isSubscribed ? emailUnsubscribe : emailSubscribe;

vite.config.ts:25

  • The Vite proxy rewrite callback parameter is named path, which shadows the imported Node path module in this file. This is easy to misread and can trigger no-shadow linting rules; rename the parameter to something like requestPath.
                          rewrite: (path) => path.replace(/^\/api/, ''),

src/pages/KYC.tsx:98

  • Polling stops only when the backend reaches a terminal status different from statusAtLaunch. If the account is already in a terminal status at launch (e.g., Approved) this will keep polling indefinitely and never show the confirmed status stage. Stop polling on any terminal status and surface it to the UI.
                if (isStatusOutcome(latestAccount.kycStatus) && latestAccount.kycStatus !== statusAtLaunch.current) {
                    setConfirmedStatus(latestAccount.kycStatus);
                    return true;
                }

src/components/Profile/sections/PersonalInformation.tsx:29

  • Email subscription updates can be triggered multiple times in quick succession, creating concurrent subscribe/unsubscribe requests and leaving the UI in an inconsistent state. Re-introduce an in-flight flag/state for the subscription toggle so the handler can be made idempotent while a request is pending.

This issue also appears on line 67 of the same file.

    // Switches states
    const [isSubscribed, setSubscribed] = useState<boolean>(false);
    const [isCompany, setCompany] = useState<boolean>(false);

    const [isFetchingKycInfo, setFetchingKycInfo] = useState<boolean>(false);

Comment thread src/pages/KYC.tsx
Comment on lines +143 to +150
<iframe
key={session.sessionId}
src={session.url}
title={`${session.applicantType === 'company' ? 'KYB' : 'KYC'} verification powered by Didit`}
allow="camera; microphone; fullscreen; autoplay; encrypted-media"
referrerPolicy="no-referrer"
className="h-[720px] min-h-[70dvh] w-full border-0 bg-white"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants