Skip to content

fix(kyc): resume Sumsub SDK after PWA reload (TASK-20406) - #2644

Merged
Hugo0 merged 2 commits into
mainfrom
fix/sumsub-pwa-resume
Aug 7, 2026
Merged

fix(kyc): resume Sumsub SDK after PWA reload (TASK-20406)#2644
Hugo0 merged 2 commits into
mainfrom
fix/sumsub-pwa-resume

Conversation

@kushagrasarathe

@kushagrasarathe kushagrasarathe commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem — TASK-20406

On Android the standalone PWA is evicted from memory when a user switches apps mid-KYC (opening the camera/gallery — the exact thing the flow pushes users toward — is the common trigger). On return the page cold-reloads and the Sumsub SDK's open-state (useState) resets to closed, so the user falls out of the SDK back to the screen they launched KYC from and has to start the flow again. iOS PWA is not affected. Reported by 2 users.

This does not occur in the native app (Sumsub runs as a native screen), but ~98% of Android KYC still runs on the PWA today, so a PWA-side mitigation is worth shipping until native is the default Android path.

Scope — what this fixes

  • Fixed: falling out of the SDK on reload. The SDK reopens for the same in-progress applicant instead of the user dropping out of KYC.
  • Not fixable in PWA (out of scope): un-submitted form fields typed into Sumsub's cross-origin iframe — we can't reach into it to save/restore them. Anything already submitted to Sumsub (uploaded ID, face scan) is server-side and resumes.

Fix

Persist "the SDK is open" to the URL (?kyc=true) via a new useSumsubReloadResume hook. On mount, if the flag is set but the SDK is closed, re-initiate: mint a fresh token for the existing applicant and reopen the SDK. Wired into both KYC entry points — the shared multi-phase flow and the Rain card flow.

handleInitiateKyc now returns whether the SDK actually opened (return true on the token/open path), so a resume that can't reopen (already-approved user, or a remediation flow a bare initiate can't reconstruct) clears the flag instead of retrying on every future reload.

Relationship to #2316

Supersedes #2316. That PR predated the native-SDK refactor (7b372030a), which removed the intro consent screen and its autoStart machinery — so on resume the SDK now launches straight into Sumsub with no extra step. This branch is rebased onto current main with that obsolete machinery dropped.

Test

  • npm run typecheck — clean
  • KYC suites (useMultiPhaseKycFlow, useSumsubKycFlow, useSumsubReloadResume, Kyc) — 46 pass
  • Full suite — 2696 pass; npm run build — clean

Manual (preview, Android): start KYC → enter Sumsub → switch to another app / open gallery → return → SDK reopens for the same applicant instead of dropping you out of the flow.

On Android the standalone PWA is evicted from memory when the user switches
apps mid-KYC (opening the camera/gallery is the common trigger). On return
the page cold-reloads, the SDK's open-state resets, and the user is dropped
back to the start. Persist the open-state to the URL (?kyc=true) so a reload
can re-acquire a token for the same applicant and reopen the SDK.

New useSumsubReloadResume hook drives it; wired into the shared multi-phase
flow and the Rain card flow. handleInitiateKyc now returns whether the SDK
opened so a resume that can't reopen clears the flag instead of retrying on
every reload.

Rebased onto current main; supersedes #2316 (which predated the native-SDK
refactor and its now-removed StartVerificationView autoStart machinery).
@notion-workspace

Copy link
Copy Markdown

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 7, 2026 5:48pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 45 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 178d5170-faec-4369-b62e-eed75609098a

📥 Commits

Reviewing files that changed from the base of the PR and between f975ad7 and 9b2e06f.

📒 Files selected for processing (5)
  • package.json
  • src/app/(mobile-ui)/card/page.tsx
  • src/hooks/__tests__/useSumsubReloadResume.test.tsx
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/hooks/useSumsubReloadResume.ts
📝 Walkthrough

Walkthrough

The PR adds Sumsub verification resume support after a PWA reload. It persists reload state in the kyc query parameter, retries incomplete card applications, reopens Sumsub with a fresh token, and reports whether SDK initiation succeeded.

Changes

Sumsub reload-resume flow

Layer / File(s) Summary
Persist and resume Sumsub state
src/hooks/useSumsubReloadResume.ts
The new hook stores Sumsub open state in the kyc query parameter, performs one resume attempt on mount, clears failed resumes, and synchronizes the flag with SDK state.
Return SDK initiation results
src/hooks/useSumsubKycFlow.ts
handleInitiateKyc returns true when the Sumsub SDK opens and false for errors, direct bridge handling, completed flows, unsupported regions, missing tokens, and exceptions.
Integrate card and multi-phase resume
src/app/(mobile-ui)/card/page.tsx, src/hooks/useMultiPhaseKycFlow.ts
The card flow retries incomplete applications and reopens Sumsub with a fresh token. Resumed attempts update initiation analytics and propagate the SDK-open result.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: hugo0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: resuming the Sumsub SDK after a PWA reload.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sumsub-pwa-resume

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2838 ran, 0 failed, 0 skipped, 33.0s

📊 Coverage (unit)

metric %
statements 65.8%
branches 50.5%
functions 55.9%
lines 66.6%
⏱ 10 slowest test cases
time test
2.0s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.6s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.3s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.2s src/components/Kyc/states/__tests__/KycStates.test.tsx › does not pass the click event to action-required resume
0.2s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/utils/__tests__/auth-token.test.ts › should read from cookies
0.2s src/utils/__tests__/auth-token.test.ts › is none — never guarded — when only the guarded marker is present
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7127.92 → 7135.63 (+7.71)
Findings: +3 net (+22 new, -19 resolved)

🆕 New findings (22)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 137, MI 56.17, SLOC 526
  • critical complexity — src/hooks/useSumsubKycFlow.ts — CC 80, MI 52.52, SLOC 374
  • critical complexity — src/hooks/useMultiPhaseKycFlow.ts — CC 68, MI 57.15, SLOC 347
  • high hotspot — src/hooks/useSumsubKycFlow.ts — 49 commits, +778/-226 lines since 6 months ago
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 48 commits, +1120/-490 lines since 6 months ago
  • medium high-mdd — src/hooks/useSumsubKycFlow.ts:64 — useSumsubKycFlow: MDD 192.4 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:57 — CardPage: MDD 173.0 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useMultiPhaseKycFlow.ts:99 — useMultiPhaseKycFlow: MDD 122.8 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/card/page.tsx:57 — CardPage: DLT 62 (calls 62 distinct functions — high context load)
  • medium high-dlt — src/hooks/useMultiPhaseKycFlow.ts:99 — useMultiPhaseKycFlow: DLT 39 (calls 39 distinct functions — high context load)
  • medium high-mdd — src/hooks/useSumsubKycFlow.ts:222 — : MDD 32.4 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:587 — renderState: MDD 23.5 (uses across many lines from declarations)
  • medium method-complexity — src/app/(mobile-ui)/card/page.tsx:587 — CC 22 SLOC 59
  • medium complexity — src/hooks/useSumsubReloadResume.ts — CC 22, MI 64.72, SLOC 63
  • medium method-complexity — src/hooks/useSumsubKycFlow.ts:222 — CC 19 SLOC 73
  • medium react-effect-derives-state — src/app/(mobile-ui)/card/page.tsx:278 — small useEffect that only sets state from deps
  • medium nextjs-missing-use-client — src/hooks/useSumsubReloadResume.ts:1 — Hooks used without use client directive
  • low high-mdd — src/app/(mobile-ui)/card/page.tsx:449 — : MDD 15.8 (uses across many lines from declarations)
  • low high-mdd — src/app/(mobile-ui)/card/page.tsx:288 — : MDD 14.3 (uses across many lines from declarations)
  • low missing-return-type — src/hooks/useMultiPhaseKycFlow.ts:51 — confirmBridgeTosAndAwaitRails: exported fn missing return type annotation

…and 2 more.

✅ Resolved (19)

  • src/app/(mobile-ui)/card/page.tsx — CC 132, MI 56.25, SLOC 514
  • src/hooks/useSumsubKycFlow.ts — CC 80, MI 52.63, SLOC 371
  • src/hooks/useMultiPhaseKycFlow.ts — CC 66, MI 57.44, SLOC 330
  • src/hooks/useSumsubKycFlow.ts — 48 commits, +771/-222 lines since 6 months ago
  • src/app/(mobile-ui)/card/page.tsx — 46 commits, +1099/-487 lines since 6 months ago
  • src/hooks/useSumsubKycFlow.ts:64 — useSumsubKycFlow: MDD 191.0 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:56 — CardPage: MDD 167.9 (uses across many lines from declarations)
  • src/hooks/useMultiPhaseKycFlow.ts:98 — useMultiPhaseKycFlow: MDD 120.6 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:56 — CardPage: DLT 61 (calls 61 distinct functions — high context load)
  • src/hooks/useMultiPhaseKycFlow.ts:98 — useMultiPhaseKycFlow: DLT 37 (calls 37 distinct functions — high context load)
  • src/hooks/useSumsubKycFlow.ts:222 — : MDD 32.3 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:569 — renderState: MDD 23.5 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:569 — CC 22 SLOC 59
  • src/hooks/useSumsubKycFlow.ts:222 — CC 19 SLOC 70
  • src/app/(mobile-ui)/card/page.tsx:277 — small useEffect that only sets state from deps
  • src/app/(mobile-ui)/card/page.tsx:448 — : MDD 15.8 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:287 — : MDD 14.3 (uses across many lines from declarations)
  • src/hooks/useMultiPhaseKycFlow.ts:50 — confirmBridgeTosAndAwaitRails: exported fn missing return type annotation
  • src/hooks/useMultiPhaseKycFlow.ts:98 — useMultiPhaseKycFlow: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/hooks/useSumsubReloadResume.ts 0.0 6.8 +6.8

The resume re-initiated with no arguments. On the LATAM surfaces (qr-pay,
withdraw/manteca, MantecaAddMoney, MantecaFlowManager) the flow is built as
useMultiPhaseKycFlow({}) and the intent is passed at call time, so a resume
there fell back to the hook default: wrong region intent, and crossRegion and
targetCountry dropped. targetCountry is the Manteca geo stamp, which is
first-write-wins. Worse, a bare initiate still returns a token, so the resume
reported success, the flag stayed set, and the user landed silently in the
wrong verification level — a harder failure than the drop-out being fixed.

Persist the initiate arguments alongside the flag and replay them verbatim.
The URL value carries the arguments instead of a bare boolean, and is
validated on read because the param is user-editable.

Adds the useSumsubReloadResume test the PR described but did not include.
Jest needs nuqs transformed to load its testing adapter, following the
existing ESM allowlist in transformIgnorePatterns.
@Hugo0
Hugo0 merged commit 1f79d56 into main Aug 7, 2026
27 checks passed
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