Skip to content

fix(cross-region): pass targetCountry from country-aware KYC initiate sites#2199

Merged
Hugo0 merged 3 commits into
mainfrom
fix/cross-region-targetCountry
Jun 10, 2026
Merged

fix(cross-region): pass targetCountry from country-aware KYC initiate sites#2199
Hugo0 merged 3 commits into
mainfrom
fix/cross-region-targetCountry

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Pairs with peanut-api-ts#995. Targets `main` — real prod incident, real user (2026-06-08).

Five `sumsubFlow.handleInitiateKyc` call sites knew the user's country (`selectedCountry.id` / `currentCountry.id` / URL param) but omitted the 4th `targetCountry` argument. The BE then had no way to scope the Manteca geo, the Sumsub action externalId was built without a `-XX` suffix, no PENDING Manteca rail was pre-stamped, and when the action went GREEN the webhook handler silently bailed.

Sites patched

File Surface
`components/AddWithdraw/AddWithdrawCountriesList.tsx` (×2) Add/Withdraw country picker
`components/Claim/Link/views/BankFlowManager.view.tsx` (×2) Claim → bank flow
`components/Claim/Link/MantecaFlowManager.tsx` Claim → Manteca flow
`app/(mobile-ui)/add-money/[country]/bank/page.tsx` Add money → `[country]` bank
`app/(mobile-ui)/withdraw/[country]/bank/page.tsx` Withdraw → `[country]` bank

Intentionally NOT patched

`components/Profile/views/UnlockedRegions.view.tsx` LATAM macro click — there's no per-country picker at that surface, so `targetCountry` genuinely isn't known until the user uploads ID inside the Sumsub WebSDK. The companion BE PR adds a fallback that pulls the geo from the applicant's extracted country in that case.

Risk

Very low. Adding a 4th arg whose value the BE only consumes on LATAM/Manteca paths; non-Manteca intents (`EU`, `NA`, `ROW`) ignore it. No FE behavior change for those flows.

Test plan

  • `npm run typecheck` clean
  • `AddWithdraw*` jest suite passes
  • Sandbox: complete a Manteca AR flow through `/add-money/argentina/bank`, confirm the action externalId is `manteca-{userId}-AR` and Manteca user creation lands
  • Sandbox: same for BR via `/add-money/brazil/bank`
  • Sandbox: Profile → Unlocked regions → LATAM (macro) → upload BR ID → confirm BE fallback kicks in (covered by peanut-api-ts#995)

Hardening (audit pass, 2026-06-10 — commit 1f92fa07c)

A deep review of the first cut added three hook-level fixes in useSumsubKycFlow:

  1. Choke-point gating — 28 non-Manteca countries carry region: 'latam' in the FE data, so the patched call sites were feeding targetCountry: 'MX'/'CL'/… to the BE, which stamps it as pendingMantecaGeo (first-write-wins → poisons every later geo resolution). The hook now drops any non-AR/BR value, so no call site can regress this.
  2. Native token-refresh parity — the Capacitor refresh callback omitted targetCountry (web refreshToken and the 5s poller pass it), so a native token expiry mid-action minted a token for a different, suffix-less applicant action than the one the user was inside.
  3. Race fix generalized4527d3833 cleared userInitiatedRef only on the unsupported-region branch, but every other terminal-error exit (response.error — e.g. the region_not_supported 400 —, no-token, native failure, throw, and the same paths in handleRestartIdentity/handleSelfHealResubmit) left the guard set while restoring prevStatusRef: a late websocket APPROVED replay fired onKycSuccess on top of the rendered error — the "You're all set" loop again. All terminal exits now clear the guard.

5 new tests: targetCountry forwarded (AR) / dropped (MX); error + throw paths survive a late PENDING→APPROVED two-event sequence (isolates the userInitiatedRef guard from the prevStatusRef guard, which a single APPROVED event can't do); control test that a successful SDK open keeps the guard armed.

Known remaining gap (deliberate, follow-up): MantecaFlowManager's claim-link entry (SendLinkActionList → "Pix"/"Mercado Pago") still sends targetCountry: undefinedregionalMethodType knows the geo but resets to its default on the ?step=regional-claim auth-redirect remount, so a naive fallback could stamp AR for a PIX claim. Proper fix is carrying the method in the URL param. The BE applicant-country fallback (#995) covers this surface meanwhile.

… sites

Pairs with peanut-api-ts#995. Five call sites of `sumsubFlow.handleInitiateKyc`
knew the user's chosen country (selectedCountry.id / currentCountry.id /
URL param) but dropped it on the floor — the 4th `targetCountry` arg was
just omitted.

That cascaded on the BE: no `pendingMantecaGeo` on metadata → no `-XX`
suffix on the Sumsub action externalId → no PENDING Manteca rail pre-stamped
→ when the action went GREEN, the webhook handler couldn't determine which
exchange to submit to and bailed silently (companion PR adds an applicant-
country fallback + Sentry capture for that case).

Sites fixed:
  - components/AddWithdraw/AddWithdrawCountriesList.tsx (2 call sites)
  - components/Claim/Link/views/BankFlowManager.view.tsx (2 call sites)
  - components/Claim/Link/MantecaFlowManager.tsx
  - app/(mobile-ui)/add-money/[country]/bank/page.tsx
  - app/(mobile-ui)/withdraw/[country]/bank/page.tsx

UnlockedRegions.view.tsx intentionally NOT patched: at LATAM-macro click
the country isn't known (no per-country picker at that surface).
The companion BE fallback handles that case from the applicant's extracted
country.
@vercel

vercel Bot commented Jun 8, 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, Comment Jun 10, 2026 12:12pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 749ecc28-987f-4b30-a674-b49c4690da0c

📥 Commits

Reviewing files that changed from the base of the PR and between 1f92fa0 and 6834891.

📒 Files selected for processing (2)
  • src/hooks/__tests__/useSumsubKycFlow.test.ts
  • src/hooks/useSumsubKycFlow.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useSumsubKycFlow.ts

Walkthrough

Propagates selected/current country ID into all sumsubFlow.handleInitiateKyc call sites and hardens useSumsubKycFlow: it normalizes raw target country for Manteca, passes targetCountry into native flows, and clears the user-initiated guard on terminal failures. Tests added for gating and guard behavior.

Changes

KYC Country ID Propagation & Flow Hardening

Layer / File(s) Summary
Call-site: propagate country id in KYC initiation
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx, src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/Claim/Link/MantecaFlowManager.tsx, src/components/Claim/Link/views/BankFlowManager.view.tsx
Multiple UI flows now pass selectedCountry?.id or currentCountry?.id as an additional positional argument to sumsubFlow.handleInitiateKyc in InitiateKycModal onVerify handlers and form submission paths.
Hook: useSumsubKycFlow normalization and guard clearing
src/hooks/useSumsubKycFlow.ts
Imports isMantecaSupportedCountryCode; handleInitiateKyc now accepts rawTargetCountry and derives targetCountry only when supported by Manteca. Adds targetCountry to native SDK/token flows and clears userInitiatedRef on multiple terminal error branches to avoid late websocket APPROVED triggering success.
Tests: targetCountry gating and terminal-error guard behavior
src/hooks/__tests__/useSumsubKycFlow.test.ts
Adds tests verifying Manteca-supported targetCountry forwarding and that terminal errors clear the user-initiated guard so late websocket status sequences do not call onKycSuccess; includes a control case for preserved guard on successful SDK open.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, tests

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: passing targetCountry from country-aware KYC initiate call sites, which directly addresses the core issue identified in the PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the incident, affected sites, implementation details, risk assessment, and test plan.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5832.1 → 5833.62 (+1.52)
Findings: 0 net (+33 new, -33 resolved)

🆕 New findings (33)

  • critical complexity — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 115, MI 56.78, SLOC 332
  • critical complexity — src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 98, MI 47.73, SLOC 404
  • critical complexity — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 97, MI 52.72, SLOC 331
  • critical complexity — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 89, MI 57.5, SLOC 313
  • critical complexity — src/hooks/useSumsubKycFlow.ts — CC 74, MI 52.96, SLOC 325
  • high hotspot — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — 51 commits, +541/-481 lines since 6 months ago
  • high hotspot — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — 40 commits, +622/-437 lines since 6 months ago
  • high hotspot — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — 38 commits, +368/-166 lines since 6 months ago
  • high hotspot — src/hooks/useSumsubKycFlow.ts — 38 commits, +540/-72 lines since 6 months ago
  • high complexity — src/components/Claim/Link/MantecaFlowManager.tsx — CC 33, MI 60.42, SLOC 84
  • medium react-long-component — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:61 — WithdrawBankPage is 495 lines — split it
  • medium react-long-component — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:46 — OnrampBankPage is 411 lines — split it
  • medium high-mdd — src/hooks/useSumsubKycFlow.ts:16 — useSumsubKycFlow: MDD 153.1 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:61 — WithdrawBankPage: MDD 147.9 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:46 — OnrampBankPage: MDD 135.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/AddWithdraw/AddWithdrawCountriesList.tsx:43 — AddWithdrawCountriesList: MDD 135.6 (uses across many lines from declarations)
  • medium high-mdd — src/components/Claim/Link/views/BankFlowManager.view.tsx:57 — BankFlowManager: MDD 106.3 (uses across many lines from declarations)
  • medium high-mdd — src/components/Claim/Link/views/BankFlowManager.view.tsx:290 — handleSuccess: MDD 39.2 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useSumsubKycFlow.ts:142 — : MDD 36.7 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/useSumsubKycFlow.ts:16 — useSumsubKycFlow: DLT 36 (calls 36 distinct functions — high context load)

…and 13 more.

✅ Resolved (33)

  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 115, MI 56.8, SLOC 332
  • src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 98, MI 47.75, SLOC 404
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 97, MI 52.76, SLOC 330
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 89, MI 57.51, SLOC 313
  • src/hooks/useSumsubKycFlow.ts — CC 72, MI 53.65, SLOC 310
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — 50 commits, +539/-480 lines since 6 months ago
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — 39 commits, +614/-435 lines since 6 months ago
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — 37 commits, +366/-165 lines since 6 months ago
  • src/hooks/useSumsubKycFlow.ts — 36 commits, +502/-70 lines since 6 months ago
  • src/components/Claim/Link/MantecaFlowManager.tsx — CC 33, MI 60.44, SLOC 84
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:61 — WithdrawBankPage is 494 lines — split it
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:46 — OnrampBankPage is 410 lines — split it
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:61 — WithdrawBankPage: MDD 144.5 (uses across many lines from declarations)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:46 — OnrampBankPage: MDD 133.9 (uses across many lines from declarations)
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx:43 — AddWithdrawCountriesList: MDD 132.6 (uses across many lines from declarations)
  • src/hooks/useSumsubKycFlow.ts:15 — useSumsubKycFlow: MDD 130.7 (uses across many lines from declarations)
  • src/components/Claim/Link/views/BankFlowManager.view.tsx:57 — BankFlowManager: MDD 105.2 (uses across many lines from declarations)
  • src/components/Claim/Link/views/BankFlowManager.view.tsx:290 — handleSuccess: MDD 37.9 (uses across many lines from declarations)
  • src/hooks/useSumsubKycFlow.ts:15 — useSumsubKycFlow: DLT 34 (calls 34 distinct functions — high context load)
  • src/hooks/useSumsubKycFlow.ts:141 — : MDD 33.8 (uses across many lines from declarations)

…and 13 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/hooks/useSumsubKycFlow.ts 16.7 17.4 +0.7

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1337 ran, 0 failed, 0 skipped, 22.3s

📊 Coverage (unit)

metric %
statements 50.6%
branches 31.7%
functions 35.5%
lines 50.5%
⏱ 10 slowest test cases
time test
0.4s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every stamp stays within canvas at any count
0.2s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Manteca PIX form ready shows merchant card + amount input + pay button
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle maximum length (17 digits) US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (missing 0x prefix)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ETH address
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ENS name
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

…uard on every terminal error

Three hook-level holes around the new targetCountry plumbing:

1. Choke-point gating: call sites pass the raw destination country for
   every latam-region country, but the BE only ever consumes
   targetCountry as a Manteca geo — an unsupported value (MX, CL, …)
   stamps a poisoned pendingMantecaGeo (first-write-wins) that bails
   every later geo resolution. Drop non-AR/BR values in the hook so no
   call site can regress this.

2. Native token-refresh parity: the Capacitor refresh callback omitted
   targetCountry (web refreshToken and the 5s poller pass it), so a
   native token expiry mid-action minted a token for a different,
   suffix-less applicant action than the one the user was inside.

3. Generalize the 4527d38 race fix: it cleared userInitiatedRef only
   on the unsupported-region branch, but every other terminal-error exit
   (response.error — e.g. region_not_supported 400 —, no-token, native
   failure, throw; same in handleRestartIdentity/handleSelfHealResubmit)
   left the guard set while restoring prevStatusRef, so a late websocket
   APPROVED replay fired onKycSuccess on top of the rendered error —
   the exact "You're all set" loop again. Tests use a PENDING→APPROVED
   two-event sequence so the userInitiatedRef guard is isolated from the
   prevStatusRef guard.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useSumsubKycFlow.ts (1)

199-205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore prevStatusRef on every terminal cross-region return, not just response.error/catch.

Lines 166-168 preseed prevStatusRef.current to 'APPROVED'. These three exits clear userInitiatedRef, but they return without restoring that seed, so a later SDK flow that opens with status === 'APPROVED' can miss the final APPROVED transition and never call onKycSuccess.

Proposed fix
                 if (response.data?.actionType === 'unsupported-region') {
                     userInitiatedRef.current = false
+                    if (crossRegion) prevStatusRef.current = savedPrevStatus
                     setError(
                         "Bank deposits aren't available in your region yet. We'll let you know as soon as they go live."
                     )
                     return
                 }
@@
                             if (!SNSMobileSDK) {
                                 userInitiatedRef.current = false
+                                if (crossRegion) prevStatusRef.current = savedPrevStatus
                                 setError('KYC SDK not available. Please update the app.')
                                 return
                             }
@@
                 } else {
                     userInitiatedRef.current = false
+                    if (crossRegion) prevStatusRef.current = savedPrevStatus
                     setError('Could not initiate verification. Please try again.')
                 }

Also applies to: 247-250, 292-295

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useSumsubKycFlow.ts` around lines 199 - 205, The branch that
handles cross-region terminal exits (e.g., the response.data?.actionType ===
'unsupported-region' path) clears userInitiatedRef.current and returns without
restoring prevStatusRef.current to its seeded value ('APPROVED'), which can
break later SDK flows; update those terminal-return paths (including the
unsupported-region branch and the similar exits around the other noted
locations) to set prevStatusRef.current = 'APPROVED' before returning (while
still clearing userInitiatedRef and calling setError), so prevStatusRef is
restored for subsequent KYC flows and onKycSuccess can fire as expected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useSumsubKycFlow.ts`:
- Around line 154-155: The branch that sets targetCountry currently uses
isMantecaSupportedCountryCode(rawTargetCountry) but does not normalize case, so
update the assignment in useSumsubKycFlow to uppercase the validated value
before storing/sending (i.e., if rawTargetCountry passes
isMantecaSupportedCountryCode, set targetCountry to
rawTargetCountry.toUpperCase()). Ensure any downstream usage in the same hook
(initiation/refresh flows) uses this normalized targetCountry value instead of
the raw input.

---

Outside diff comments:
In `@src/hooks/useSumsubKycFlow.ts`:
- Around line 199-205: The branch that handles cross-region terminal exits
(e.g., the response.data?.actionType === 'unsupported-region' path) clears
userInitiatedRef.current and returns without restoring prevStatusRef.current to
its seeded value ('APPROVED'), which can break later SDK flows; update those
terminal-return paths (including the unsupported-region branch and the similar
exits around the other noted locations) to set prevStatusRef.current =
'APPROVED' before returning (while still clearing userInitiatedRef and calling
setError), so prevStatusRef is restored for subsequent KYC flows and
onKycSuccess can fire as expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1901a1ed-657f-4b03-8299-7b65cdc714ab

📥 Commits

Reviewing files that changed from the base of the PR and between ce7b3ae and 1f92fa0.

📒 Files selected for processing (2)
  • src/hooks/__tests__/useSumsubKycFlow.test.ts
  • src/hooks/useSumsubKycFlow.ts

Comment thread src/hooks/useSumsubKycFlow.ts Outdated
isMantecaSupportedCountryCode uppercases internally, so a lowercase
route-param country passed the predicate but was forwarded raw; the BE
normalizes anyway, but the hook's contract is normalized-or-dropped.
@Hugo0 Hugo0 merged commit d34c758 into main Jun 10, 2026
23 of 25 checks passed
Hugo0 added a commit that referenced this pull request Jun 10, 2026
…-0610

chore: back-merge main → dev (2026-06-10, post #2199/#2202/#2204)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants