From eb5c68b8bec7cdf014945b41a638a6d2e77f9119 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 09:35:13 +0000 Subject: [PATCH] fix(card-activate): show Rain KYC reasons on first step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getStepDescription early-returned the generic Rain status text and never consulted kycWarnings, so the rejection labels we now surface from the backend (via the user.updated webhook applicationReason field) were silently dropped on the first step of /card/activate. Pass warnings into getKYCDescription and bullet them under the NEEDS_INFORMATION message — the only Rain state Rain documents as carrying temporary, user-actionable labels. Final rejections (denied/locked/canceled) stay generic so we don't leak compliance labels like SANCTIONS or PEP to the user. --- hooks/useCardSteps/kycDisplayHelpers.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/hooks/useCardSteps/kycDisplayHelpers.ts b/hooks/useCardSteps/kycDisplayHelpers.ts index 0a00db9c..ac873ac1 100644 --- a/hooks/useCardSteps/kycDisplayHelpers.ts +++ b/hooks/useCardSteps/kycDisplayHelpers.ts @@ -114,9 +114,16 @@ function formatKycWarnings(warnings: KycWarning[]): string { } /** - * User-friendly KYC description per Rain application state + * User-friendly KYC description per Rain application state. + * For NEEDS_INFORMATION, surface the specific rejection reasons (Rain only sends + * temporary, user-actionable labels for this state). Other states stay generic — + * final rejections (DENIED/LOCKED/CANCELED) intentionally do not expose the + * underlying compliance labels (e.g. SANCTIONS, PEP). */ -export function getKYCDescription(rainApplicationStatus?: RainApplicationStatus | null): string { +export function getKYCDescription( + rainApplicationStatus?: RainApplicationStatus | null, + kycWarnings?: KycWarning[] | null, +): string { if (!rainApplicationStatus) return DEFAULT_KYC_DESCRIPTION; switch (rainApplicationStatus) { case RainApplicationStatus.APPROVED: @@ -133,8 +140,13 @@ export function getKYCDescription(rainApplicationStatus?: RainApplicationStatus return 'This application was canceled. Contact support if you need to start over.'; case RainApplicationStatus.NEEDS_VERIFICATION: return "Verify your identity to continue. You'll be redirected to complete verification."; - case RainApplicationStatus.NEEDS_INFORMATION: + case RainApplicationStatus.NEEDS_INFORMATION: { + const formatted = formatKycWarnings(kycWarnings ?? []); + if (formatted.length > 0) { + return `We need a bit more information to process your application:\n- ${formatted}`; + } return 'We need a bit more information to process your application.'; + } case RainApplicationStatus.NOT_STARTED: default: return DEFAULT_KYC_DESCRIPTION; @@ -205,12 +217,12 @@ export function getStepDescription( options?.rainApplicationStatus && Object.values(RainApplicationStatus).includes(options.rainApplicationStatus); + const warnings = options?.kycWarnings ?? []; + if (options?.cardIssuer === CardProvider.RAIN && isRecognizedRainStatus) { - return getKYCDescription(options.rainApplicationStatus); + return getKYCDescription(options.rainApplicationStatus, warnings); } - const warnings = options?.kycWarnings ?? []; - // Didit KYC rejected or expired before reaching Rain — show rejection reasons if (options?.kycStatus === KycStatus.REJECTED) { if (warnings.length > 0) {