Skip to content

feat(card): proof-of-address upload on stuck Rain applications#2438

Closed
jjramirezn wants to merge 5 commits into
devfrom
feat/rain-poa-upload
Closed

feat(card): proof-of-address upload on stuck Rain applications#2438
jjramirezn wants to merge 5 commits into
devfrom
feat/rain-poa-upload

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

FE half of the Rain proof-of-address self-heal (backend: peanutprotocol/peanut-api-ts#1194). Users whose card application is stuck on an address/PoA mismatch currently dead-end at "Contact support" on the card status screen; this PR gives them a primary "Upload proof of address" CTA that opens the Sumsub RFI upload in-app.

  • ApplicationStatusScreen: new optional onUploadProofOfAddress prop → primary purple CTA above the existing Contact-support link (requires-info / requires-support / rejected variants only).
  • card/page.tsx: derives the CTA from the capabilities read-model — shown only when the rain rail carries a sumsub next-action with levelKey: 'proof_of_address' (what the backend emits for WRONG_ADDRESS-class denials). Drives a dedicated, deliberately tiny flow: initiateSelfHealResubmission('RAIN') → second SumsubKycWrapper with its own token state → on SDK completion, optimistic "we received your document" banner + overview/capabilities refetch. Initiation failures render inline under the CTA (uploadError).
  • Type widening limited to initiateSelfHealResubmission's provider param (+RAIN_DOCUMENT in the response union) — useSumsubKycFlow is untouched.

Design notes / accepted trade-offs

  • Why not useMultiPhaseKycFlow: review confirmed its post-approval phase machine is bank-onboarding-shaped — a mutating status poll that can hang the "verifying" modal indefinitely for already-approved applicants, possible Bridge-ToS modal fan-out on an unrelated rail, and a completeFlow that re-renders the same requires-info screen (users would re-upload and burn attempts). The PoA flow needs none of it: token → SDK → thank-you + refetch.
  • Two Sumsub surfaces on the card page, deliberately. The existing SumsubKycWrapper is driven by card-apply tokens (applyForCard → poll semantics); the PoA wrapper has its own token state — multiplexing them would fight over token refresh.
  • Optimistic banner: Sumsub review + the backend webhook stamp lag the SDK close by seconds-to-minutes; local poaSubmitted state hides the CTA and shows "we received your document" until the backend's own wait-state reason takes over on refetch.
  • The CTA renders on rejected too: Rain denied with a fixable PoA reason still carries the next-action (the backend's resolved verdict is fixable), and those users can be rescued without support.
  • No URL state: the upload is a modal flow with no shareable intermediate step.

Risks / breaking changes

  • Deploy order: peanut-api-ts#1194 must be live first. Until then the rain rail never carries the PoA next-action, so the CTA simply never renders (fails closed, no breakage) — but if a user somehow POSTs provider: 'RAIN' against the old backend they get a 400 "Invalid provider" toast.
  • No changes to existing Bridge/Manteca self-heal behavior (pure union widening).

QA

  • npm test — new ApplicationStatusScreen cases: CTA renders + fires on requires-info and rejected, absent without the action, never on non-support variants. 4 failing suites on this branch (add-money-states, countryCurrencyMapping, GeneralRecipientInput, request-states) are unrelated to this diff — verifying against base-branch CI.
  • Manual: needs a user whose rain rail is REQUIRES_INFORMATION with rainRemediation.nextAction.documentPurpose === 'proof_of_address' (any of the 6 currently-stuck prod users' state reproduced in sandbox).
  • Note for reviewers: commits are created via GitHub's API (createCommitOnBranch, verified web-flow signature) because local GPG signing was unavailable during this run.

Summary by CodeRabbit

  • New Features

    • Added a self-service option to upload proof of address when card verification requires more information or has been rejected.
    • Added inline error messaging for failed uploads.
    • Added a review-status message after proof of address is submitted.
  • Bug Fixes

    • Improved the card verification flow by supporting proof-of-address resubmissions and status refreshes.

The card status screens for requires-info/rejected only offered
"Contact support", even when the backend classified the application as
PoA-fixable (residence vs POI country mismatch, the digital-nomad case
that drives most manual card support).

When the rain rail carries the sumsub:proof_of_address next-action, the
status screen now renders a primary "Upload proof of address" CTA that
opens the Sumsub RFI flow via the self-heal machinery (provider RAIN on
/users/identity/resubmit — peanut-api-ts#1194 is the backend half). On
completion the rain overview refetches and the backend flips the rail
reason to "document received - being reviewed".

The self-heal flow gets its own SumsubKycModals surface — the existing
SumsubKycWrapper on this page is bound to card-apply tokens with their
own refresh/poll semantics and must not be multiplexed.
@vercel

vercel Bot commented Jul 17, 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 Jul 17, 2026 7:54pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62a67ce7-62a4-4a82-82d0-54d15d2862f3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The card flow adds a Rain proof-of-address self-heal upload action, extends Sumsub resubmission types, updates status-screen CTAs and messaging, and renders a dedicated Sumsub upload wrapper with completion refresh behavior.

Changes

Rain proof-of-address remediation

Layer / File(s) Summary
Self-heal provider support
src/app/actions/sumsub.ts
The self-heal action accepts RAIN, and responses support the RAIN_DOCUMENT required-action value.
Proof-of-address status CTA
src/components/Card/ApplicationStatusScreen.tsx, src/components/Card/__tests__/ApplicationStatusScreen.test.tsx
Supported status variants render the proof-of-address upload CTA, inline errors, and corresponding conditional-rendering tests.
Card flow integration
src/app/(mobile-ui)/card/page.tsx
The card page starts Rain resubmission, tracks PoA state and errors, updates status messaging, and manages the PoA Sumsub wrapper lifecycle.

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

Sequence Diagram(s)

sequenceDiagram
  participant ApplicationStatusScreen
  participant CardPage
  participant initiateSelfHealResubmission
  participant SumsubKycWrapper
  ApplicationStatusScreen->>CardPage: invoke onUploadProofOfAddress
  CardPage->>initiateSelfHealResubmission: request Rain resubmission token
  initiateSelfHealResubmission-->>CardPage: return upload token
  CardPage->>SumsubKycWrapper: render PoA upload flow with token
  SumsubKycWrapper-->>CardPage: complete upload
  CardPage->>CardPage: invalidate Rain overview and refresh user
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: hugo0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a proof-of-address upload flow for stuck Rain card applications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rain-poa-upload

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

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6207.44 → 6210.18 (+2.74)
Findings: +1 net (+14 new, -13 resolved)

🆕 New findings (14)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 125, MI 57.27, SLOC 475
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 47 commits, +1293/-517 lines since 6 months ago
  • high hotspot — src/hooks/useSumsubKycFlow.ts — 46 commits, +720/-153 lines since 6 months ago
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:54 — CardPage: MDD 129.3 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/card/page.tsx:54 — CardPage: DLT 56 (calls 56 distinct functions — high context load)
  • medium hotspot — src/app/actions/sumsub.ts — 26 commits, +222/-70 lines since 6 months ago
  • medium complexity — src/app/actions/sumsub.ts — CC 25, MI 51.96, SLOC 95
  • medium method-complexity — src/app/(mobile-ui)/card/page.tsx:542 — CC 18 SLOC 51
  • medium complexity — src/components/Card/ApplicationStatusScreen.tsx — CC 11, MI 57.73, SLOC 24
  • medium react-effect-derives-state — src/app/(mobile-ui)/card/page.tsx:268 — small useEffect that only sets state from deps
  • low high-mdd — src/components/Card/ApplicationStatusScreen.tsx:57 — ApplicationStatusScreen: MDD 19.0 (uses across many lines from declarations)
  • low high-mdd — src/app/(mobile-ui)/card/page.tsx:424 — : MDD 15.8 (uses across many lines from declarations)
  • low high-mdd — src/app/(mobile-ui)/card/page.tsx:542 — renderState: MDD 16.1 (uses across many lines from declarations)
  • low high-mdd — src/app/(mobile-ui)/card/page.tsx:278 — : MDD 12.6 (uses across many lines from declarations)

✅ Resolved (13)

  • src/app/(mobile-ui)/card/page.tsx — CC 106, MI 56.91, SLOC 419
  • src/hooks/useSumsubKycFlow.ts — 44 commits, +661/-94 lines since 6 months ago
  • src/app/(mobile-ui)/card/page.tsx — 43 commits, +1181/-489 lines since 6 months ago
  • src/app/(mobile-ui)/card/page.tsx:53 — CardPage: MDD 110.2 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:53 — CardPage: DLT 49 (calls 49 distinct functions — high context load)
  • src/app/actions/sumsub.ts — CC 25, MI 51.94, SLOC 95
  • src/components/Card/share-asset/ShareAssetD3.tsx — 24 commits, +1525/-1137 lines since 6 months ago
  • src/app/(mobile-ui)/card/page.tsx:485 — CC 16 SLOC 51
  • src/components/Card/ApplicationStatusScreen.tsx — CC 8, MI 58.68, SLOC 23
  • src/components/Card/ApplicationStatusScreen.tsx:49 — ApplicationStatusScreen: MDD 19.0 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:367 — : MDD 15.8 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:485 — renderState: MDD 14.4 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:221 — : MDD 12.6 (uses across many lines from declarations)

📈 Painscore deltas (top movers)

File Before After Δ
src/app/(mobile-ui)/card/page.tsx 16.0 17.8 +1.8

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2002 ran, 0 failed, 0 skipped, 32.2s

📊 Coverage (unit)

metric %
statements 59.6%
branches 43.3%
functions 48.5%
lines 59.9%
⏱ 10 slowest test cases
time test
3.6s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.0s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
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/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
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 ENS name
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@jjramirezn

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
src/app/(mobile-ui)/card/page.tsx (1)

217-240: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider distinguishing self-heal from card-application in analytics.

CARD_SUMSUB_OPENED is fired here for the self-heal PoA upload, but the same event is used at lines 254, 296, and 322 for the card-application Sumsub flow. Without a distinguishing property (e.g. flow: 'self-heal' vs 'card-application'), funnel analysis can't tell whether the user entered Sumsub to fix a PoA issue or as part of the initial card application.

📊 Suggested analytics distinction
 const onUploadProofOfAddress = poaAction
     ? () => {
-          posthog.capture(ANALYTICS_EVENTS.CARD_SUMSUB_OPENED)
+          posthog.capture(ANALYTICS_EVENTS.CARD_SUMSUB_OPENED, { flow: 'self-heal' })
           void selfHealKycFlow.handleSelfHealResubmit('RAIN')
       }
     : undefined
🤖 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/app/`(mobile-ui)/card/page.tsx around lines 217 - 240, Update the
CARD_SUMSUB_OPENED capture in onUploadProofOfAddress to include a distinguishing
flow property such as self-heal, while preserving the existing event and payload
used by the card-application Sumsub flow elsewhere in the page.
🤖 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.

Nitpick comments:
In `@src/app/`(mobile-ui)/card/page.tsx:
- Around line 217-240: Update the CARD_SUMSUB_OPENED capture in
onUploadProofOfAddress to include a distinguishing flow property such as
self-heal, while preserving the existing event and payload used by the
card-application Sumsub flow elsewhere in the page.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05678352-9bec-442c-a993-f0bcf5be746a

📥 Commits

Reviewing files that changed from the base of the PR and between 679905f and 0fbb81e.

📒 Files selected for processing (5)
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/actions/sumsub.ts
  • src/components/Card/ApplicationStatusScreen.tsx
  • src/components/Card/__tests__/ApplicationStatusScreen.test.tsx
  • src/hooks/useSumsubKycFlow.ts

…urce

Align SelfHealResubmissionResponse.requiredAction with the backend's
RAIN_DOCUMENT value, and tag the self-heal CARD_SUMSUB_OPENED capture
with source: poa-self-heal so it's distinguishable from card-apply.
@jjramirezn

Copy link
Copy Markdown
Contributor Author

CodeRabbit nitpick addressed in 880c28d: the self-heal capture now carries source: poa-self-heal; also aligned requiredAction union with the backend contract (RAIN_DOCUMENT).

Review found the useMultiPhaseKycFlow reuse wrong for this surface:
its post-approval phase machine is bank-onboarding shaped (mutating
status poll that can hang the 'verifying' modal indefinitely, Bridge
ToS fan-out on an unrelated rail, completeFlow re-rendering the same
requires-info screen so users re-upload and burn attempts), and
initiation errors were invisible - a dead primary CTA on a stuck-
application screen.

Replaced with a tiny dedicated flow: call the resubmit action directly,
drive a second SumsubKycWrapper with its own token state, render
initiation failures inline under the CTA, and flip an optimistic
'we received your document' banner on SDK completion until the backend
webhook stamps the rail. The useSumsubKycFlow union widening is
reverted - no longer needed.
@jjramirezn

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 2

🤖 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/app/`(mobile-ui)/card/page.tsx:
- Around line 240-250: Update startPoaUpload to guard against concurrent
execution by tracking an in-flight request and returning immediately when one is
already active; set and clear this guard across all success and error paths.
Also disable the PoA resubmission CTA while the request is in flight,
re-enabling it after completion.
- Around line 715-718: Update the refresh flow around fetchUser() to reset the
local poaSubmitted state after the refreshed user capabilities no longer require
the optimistic PoA banner. Preserve the existing setPoaToken(null),
invalidateOverview(), and fetchUser() behavior while ensuring the upload CTA can
reappear without requiring a remount.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b37128b-8bbc-4b42-964a-c0d7ee3c001d

📥 Commits

Reviewing files that changed from the base of the PR and between 0fbb81e and c5e6503.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/actions/sumsub.ts
  • src/components/Card/ApplicationStatusScreen.tsx
  • src/components/Card/__tests__/ApplicationStatusScreen.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/Card/tests/ApplicationStatusScreen.test.tsx
  • src/components/Card/ApplicationStatusScreen.tsx

Comment thread src/app/(mobile-ui)/card/page.tsx Outdated
Comment thread src/app/(mobile-ui)/card/page.tsx
Concurrent CTA clicks could mint two Sumsub actions (the backend id
collision path deliberately creates a suffixed fresh action); guard
with a ref. Reset poaSubmitted once the backend state takes over so a
re-offered upload isn't suppressed until remount.
@jjramirezn
jjramirezn marked this pull request as ready for review July 17, 2026 06:35
@jjramirezn
jjramirezn requested a review from Hugo0 July 17, 2026 06:35
jjramirezn added a commit that referenced this pull request Jul 17, 2026
Hotfix port to main of peanut-ui#2438 (branch was dev-based; every
touched file is byte-identical between dev and main, so this is the
same reviewed content squashed onto main). Pairs with peanut-api-ts
hotfix/rain-poa-selfheal — deploy the API first.

When the rain rail carries the sumsub:proof_of_address next-action, the
card status screens (requires-info / rejected) render a primary
"Upload proof of address" CTA driving a dedicated lightweight flow:
resubmit action -> second SumsubKycWrapper -> inline initiation errors
-> optimistic "we received your document" banner (auto-reset when the
backend state takes over). Double-click guard; analytics tagged
source: poa-self-heal.

Full review trail on #2438.
@jjramirezn

Copy link
Copy Markdown
Contributor Author

Superseded by #2446 — same content retargeted to main as a hotfix per Jota. Review trail stays here.

@jjramirezn jjramirezn closed this Jul 17, 2026
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.

1 participant