From cbf66b6345ad563588c7282c0d2ee7e4dd3065e2 Mon Sep 17 00:00:00 2001 From: kushagrasarathe <76868364+kushagrasarathe@users.noreply.github.com> Date: Fri, 15 May 2026 19:09:24 +0530 Subject: [PATCH 1/2] Fix migrated KYC withdrawal gating --- .../__tests__/kyc-withdrawal-gate.test.tsx | 90 +++++++++++++++++++ src/hooks/useIdentityVerification.tsx | 25 +++++- src/hooks/useUnifiedKycStatus.ts | 4 +- 3 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/hooks/__tests__/kyc-withdrawal-gate.test.tsx diff --git a/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx b/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx new file mode 100644 index 000000000..10b749510 --- /dev/null +++ b/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx @@ -0,0 +1,90 @@ +import { renderHook } from '@testing-library/react' +import { useIdentityVerification } from '../useIdentityVerification' +import useUnifiedKycStatus from '../useUnifiedKycStatus' + +jest.mock('@/assets', () => ({ + EUROPE_GLOBE_ICON: 'europe', + LATAM_GLOBE_ICON: 'latam', + NORTH_AMERICA_GLOBE_ICON: 'north-america', + REST_OF_WORLD_GLOBE_ICON: 'rest-of-world', +})) + +jest.mock('@/components/AddMoney/consts', () => ({ + BRIDGE_ALPHA3_TO_ALPHA2: { USA: 'US' }, + MantecaSupportedExchanges: { AR: 'ARGENTINA', BR: 'BRAZIL' }, + countryData: [ + { id: 'AR', type: 'country', title: 'Argentina', path: 'argentina', iso2: 'AR' }, + { id: 'US', type: 'country', title: 'United States', path: 'united-states', iso2: 'US' }, + ], +})) + +jest.mock('@/context/authContext', () => ({ + useAuth: jest.fn(), +})) + +import { useAuth } from '@/context/authContext' + +const mockUseAuth = useAuth as jest.MockedFunction + +const enabledMantecaArRail = { + id: 'rail-1', + railId: 'rail-def-1', + status: 'ENABLED', + rail: { + id: 'rail-def-1', + provider: { code: 'MANTECA', name: 'Manteca' }, + method: { code: 'BANK_TRANSFER_AR', name: 'Bank Transfer AR', country: 'AR', currency: 'ARS' }, + }, +} + +function setUser(authUser: Record) { + mockUseAuth.mockReturnValue({ + user: authUser, + } as unknown as ReturnType) +} + +describe('kyc withdrawal gating', () => { + afterEach(() => jest.resetAllMocks()) + + it('treats migrated SUMSUB ACTIVE rows as approved', () => { + setUser({ + user: { + bridgeKycStatus: null, + kycVerifications: [ + { + provider: 'SUMSUB', + status: 'ACTIVE', + updatedAt: '2026-03-26T23:02:30.330Z', + }, + ], + }, + rails: [], + }) + + const { result } = renderHook(() => useUnifiedKycStatus()) + + expect(result.current.isSumsubApproved).toBe(true) + expect(result.current.isKycApproved).toBe(true) + }) + + it('allows Argentina Manteca withdrawal when migrated KYC and rails are enabled', () => { + setUser({ + user: { + bridgeKycStatus: null, + kycVerifications: [ + { + provider: 'SUMSUB', + mantecaGeo: 'AR', + status: 'ACTIVE', + updatedAt: '2026-03-17T14:47:34.702Z', + }, + ], + }, + rails: [enabledMantecaArRail], + }) + + const { result } = renderHook(() => useIdentityVerification()) + + expect(result.current.isVerifiedForCountry('AR')).toBe(true) + }) +}) diff --git a/src/hooks/useIdentityVerification.tsx b/src/hooks/useIdentityVerification.tsx index bfb19bb8f..24e1c04be 100644 --- a/src/hooks/useIdentityVerification.tsx +++ b/src/hooks/useIdentityVerification.tsx @@ -4,11 +4,11 @@ import useKycStatus from './useKycStatus' import useUnifiedKycStatus from './useUnifiedKycStatus' import { useMemo, useCallback } from 'react' import { useAuth } from '@/context/authContext' -import { MantecaKycStatus } from '@/interfaces' import { BRIDGE_ALPHA3_TO_ALPHA2, MantecaSupportedExchanges, countryData } from '@/components/AddMoney/consts' import { getFlagUrl } from '@/constants/countryCurrencyMapping' import { type KYCRegionIntent } from '@/app/actions/types/sumsub.types' import React from 'react' +import { isKycStatusApproved } from '@/constants/kyc.consts' /** Represents a geographic region with its display information */ export type Region = { @@ -134,17 +134,34 @@ export const useIdentityVerification = () => { (code: string) => { const upper = code.toUpperCase() - // Check if user has active Manteca verification for this specific country const mantecaActive = user?.user.kycVerifications?.some( (v) => v.provider === 'MANTECA' && (v.mantecaGeo || '').toUpperCase() === upper && - v.status === MantecaKycStatus.ACTIVE + isKycStatusApproved(v.status) + ) ?? false + + const migratedMantecaActive = + user?.user.kycVerifications?.some( + (v) => + v.provider === 'SUMSUB' && + (v.mantecaGeo || '').toUpperCase() === upper && + isKycStatusApproved(v.status) + ) ?? false + + const hasEnabledMantecaRail = + user?.rails?.some( + (rail) => + rail.rail.provider.code === 'MANTECA' && + rail.rail.method.country.toUpperCase() === upper && + rail.status === 'ENABLED' ) ?? false // Manteca countries need country-specific verification, others just need Bridge KYC - return isMantecaSupportedCountry(upper) ? mantecaActive : isUserBridgeKycApproved + return isMantecaSupportedCountry(upper) + ? mantecaActive || migratedMantecaActive || hasEnabledMantecaRail + : isUserBridgeKycApproved }, [user, isUserBridgeKycApproved, isMantecaSupportedCountry] ) diff --git a/src/hooks/useUnifiedKycStatus.ts b/src/hooks/useUnifiedKycStatus.ts index bc5b410ae..49461108f 100644 --- a/src/hooks/useUnifiedKycStatus.ts +++ b/src/hooks/useUnifiedKycStatus.ts @@ -4,7 +4,7 @@ import { useAuth } from '@/context/authContext' import { MantecaKycStatus } from '@/interfaces' import { useMemo } from 'react' import { type SumsubKycStatus } from '@/app/actions/types/sumsub.types' -import { isSumsubStatusInProgress } from '@/constants/kyc.consts' +import { isKycStatusApproved, isSumsubStatusInProgress } from '@/constants/kyc.consts' /** * single source of truth for kyc status across all providers (bridge, manteca, sumsub). @@ -32,7 +32,7 @@ export default function useUnifiedKycStatus() { [user] ) - const isSumsubApproved = useMemo(() => sumsubVerification?.status === 'APPROVED', [sumsubVerification]) + const isSumsubApproved = useMemo(() => isKycStatusApproved(sumsubVerification?.status), [sumsubVerification]) const sumsubStatus = useMemo(() => (sumsubVerification?.status as SumsubKycStatus) ?? null, [sumsubVerification]) From 25b6a13bf4acf23397d0312807addc649f3cb14b Mon Sep 17 00:00:00 2001 From: kushagrasarathe <76868364+kushagrasarathe@users.noreply.github.com> Date: Fri, 15 May 2026 19:22:03 +0530 Subject: [PATCH 2/2] Tighten migrated Manteca KYC semantics --- .../Home/KycCompletedModal/index.tsx | 10 ++--- .../__tests__/kyc-withdrawal-gate.test.tsx | 43 +++++++++++++++++-- src/hooks/useIdentityVerification.tsx | 22 +--------- src/hooks/useUnifiedKycStatus.ts | 5 ++- 4 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/components/Home/KycCompletedModal/index.tsx b/src/components/Home/KycCompletedModal/index.tsx index 58181b0e9..4f28c017b 100644 --- a/src/components/Home/KycCompletedModal/index.tsx +++ b/src/components/Home/KycCompletedModal/index.tsx @@ -4,12 +4,12 @@ import ActionModal from '@/components/Global/ActionModal' import type { IconName } from '@/components/Global/Icons/Icon' import InfoCard from '@/components/Global/InfoCard' import { useAuth } from '@/context/authContext' -import { MantecaKycStatus } from '@/interfaces' import { countryData, MantecaSupportedExchanges, type CountryData } from '@/components/AddMoney/consts' import useUnifiedKycStatus from '@/hooks/useUnifiedKycStatus' import { useIdentityVerification } from '@/hooks/useIdentityVerification' import posthog from 'posthog-js' import { ANALYTICS_EVENTS, MODAL_TYPES } from '@/constants/analytics.consts' +import { isKycStatusApproved } from '@/constants/kyc.consts' const KycCompletedModal = ({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) => { const { user } = useAuth() @@ -28,13 +28,13 @@ const KycCompletedModal = ({ isOpen, onClose }: { isOpen: boolean; onClose: () = const { getVerificationUnlockItems } = useIdentityVerification() const kycApprovalType = useMemo(() => { + if (isBridgeApproved && isMantecaApproved) return 'all' + if (isMantecaApproved) return 'manteca' if (isSumsubApproved) { if (sumsubVerificationRegionIntent === 'LATAM') return 'manteca' return 'bridge' } - if (isBridgeApproved && isMantecaApproved) return 'all' if (isBridgeApproved) return 'bridge' - if (isMantecaApproved) return 'manteca' return 'none' }, [isBridgeApproved, isMantecaApproved, isSumsubApproved, sumsubVerificationRegionIntent]) @@ -51,9 +51,9 @@ const KycCompletedModal = ({ isOpen, onClose }: { isOpen: boolean; onClose: () = // get the manteca approved country user?.user.kycVerifications?.forEach((v) => { if ( - v.provider === 'MANTECA' && + (v.provider === 'MANTECA' || v.provider === 'SUMSUB') && supportedCountries.includes((v.mantecaGeo || '').toUpperCase()) && - v.status === MantecaKycStatus.ACTIVE + isKycStatusApproved(v.status) ) { approvedCountry = v.mantecaGeo } diff --git a/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx b/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx index 10b749510..5e20839fa 100644 --- a/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx +++ b/src/hooks/__tests__/kyc-withdrawal-gate.test.tsx @@ -46,13 +46,14 @@ function setUser(authUser: Record) { describe('kyc withdrawal gating', () => { afterEach(() => jest.resetAllMocks()) - it('treats migrated SUMSUB ACTIVE rows as approved', () => { + it('treats migrated SUMSUB ACTIVE Manteca rows as approved', () => { setUser({ user: { bridgeKycStatus: null, kycVerifications: [ { provider: 'SUMSUB', + mantecaGeo: 'AR', status: 'ACTIVE', updatedAt: '2026-03-26T23:02:30.330Z', }, @@ -64,10 +65,11 @@ describe('kyc withdrawal gating', () => { const { result } = renderHook(() => useUnifiedKycStatus()) expect(result.current.isSumsubApproved).toBe(true) + expect(result.current.isMantecaApproved).toBe(true) expect(result.current.isKycApproved).toBe(true) }) - it('allows Argentina Manteca withdrawal when migrated KYC and rails are enabled', () => { + it('allows Argentina Manteca withdrawal when migrated KYC is country-scoped', () => { setUser({ user: { bridgeKycStatus: null, @@ -80,11 +82,46 @@ describe('kyc withdrawal gating', () => { }, ], }, - rails: [enabledMantecaArRail], + rails: [], + }) + + const { result } = renderHook(() => useIdentityVerification()) + + expect(result.current.isVerifiedForCountry('AR')).toBe(true) + }) + + it('allows Argentina Manteca withdrawal for a normal active Manteca row', () => { + setUser({ + user: { + bridgeKycStatus: null, + kycVerifications: [ + { + provider: 'MANTECA', + mantecaGeo: 'AR', + status: 'ACTIVE', + updatedAt: '2025-10-30T01:12:06.099Z', + }, + ], + }, + rails: [], }) const { result } = renderHook(() => useIdentityVerification()) expect(result.current.isVerifiedForCountry('AR')).toBe(true) }) + + it('does not allow Argentina Manteca withdrawal from an enabled rail alone', () => { + setUser({ + user: { + bridgeKycStatus: null, + kycVerifications: [], + }, + rails: [enabledMantecaArRail], + }) + + const { result } = renderHook(() => useIdentityVerification()) + + expect(result.current.isVerifiedForCountry('AR')).toBe(false) + }) }) diff --git a/src/hooks/useIdentityVerification.tsx b/src/hooks/useIdentityVerification.tsx index 24e1c04be..5dd16e369 100644 --- a/src/hooks/useIdentityVerification.tsx +++ b/src/hooks/useIdentityVerification.tsx @@ -137,31 +137,13 @@ export const useIdentityVerification = () => { const mantecaActive = user?.user.kycVerifications?.some( (v) => - v.provider === 'MANTECA' && + (v.provider === 'MANTECA' || v.provider === 'SUMSUB') && (v.mantecaGeo || '').toUpperCase() === upper && isKycStatusApproved(v.status) ) ?? false - const migratedMantecaActive = - user?.user.kycVerifications?.some( - (v) => - v.provider === 'SUMSUB' && - (v.mantecaGeo || '').toUpperCase() === upper && - isKycStatusApproved(v.status) - ) ?? false - - const hasEnabledMantecaRail = - user?.rails?.some( - (rail) => - rail.rail.provider.code === 'MANTECA' && - rail.rail.method.country.toUpperCase() === upper && - rail.status === 'ENABLED' - ) ?? false - // Manteca countries need country-specific verification, others just need Bridge KYC - return isMantecaSupportedCountry(upper) - ? mantecaActive || migratedMantecaActive || hasEnabledMantecaRail - : isUserBridgeKycApproved + return isMantecaSupportedCountry(upper) ? mantecaActive : isUserBridgeKycApproved }, [user, isUserBridgeKycApproved, isMantecaSupportedCountry] ) diff --git a/src/hooks/useUnifiedKycStatus.ts b/src/hooks/useUnifiedKycStatus.ts index 49461108f..5b099649c 100644 --- a/src/hooks/useUnifiedKycStatus.ts +++ b/src/hooks/useUnifiedKycStatus.ts @@ -1,7 +1,6 @@ 'use client' import { useAuth } from '@/context/authContext' -import { MantecaKycStatus } from '@/interfaces' import { useMemo } from 'react' import { type SumsubKycStatus } from '@/app/actions/types/sumsub.types' import { isKycStatusApproved, isSumsubStatusInProgress } from '@/constants/kyc.consts' @@ -18,7 +17,9 @@ export default function useUnifiedKycStatus() { const isMantecaApproved = useMemo( () => user?.user.kycVerifications?.some( - (v) => v.provider === 'MANTECA' && v.status === MantecaKycStatus.ACTIVE + (v) => + (v.provider === 'MANTECA' || (v.provider === 'SUMSUB' && !!v.mantecaGeo)) && + isKycStatusApproved(v.status) ) ?? false, [user] )