From 532274b4895e5db03ef9022b74ba2ce6c5dd4bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Mon, 1 Jun 2026 11:22:17 -0300 Subject: [PATCH 1/2] fix(add-money): country-scope the bank-deposit gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When viewing /add-money/, narrow the 'is the bank channel ready' gate to that country's rail jurisdiction. Without this, a rejected rail in an unrelated jurisdiction trips blocked-rejection here and surfaces 'We couldn't unlock this — Verification issue' on a country whose own rail is fine. Triggering incident (2026-06-01): a sync run flipped 1,595 users' BANK_TRANSFER_MX BRIDGE rails to REJECTED (catalog seed artifact, not a real Bridge endorsement — Mexico is SPEI). The catalog deactivation hides those rows going forward, but this scoping fix is the broader guardrail: any future blocked rail in country X must not block country Y. --- .../AddWithdraw/AddWithdrawCountriesList.tsx | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/AddWithdraw/AddWithdrawCountriesList.tsx b/src/components/AddWithdraw/AddWithdrawCountriesList.tsx index 87ef8f235..ea57892a6 100644 --- a/src/components/AddWithdraw/AddWithdrawCountriesList.tsx +++ b/src/components/AddWithdraw/AddWithdrawCountriesList.tsx @@ -31,6 +31,7 @@ import { SumsubKycModals } from '@/components/Kyc/SumsubKycModals' import { InitiateKycModal } from '@/components/Kyc/InitiateKycModal' import { useCapabilities } from '@/hooks/useCapabilities' import { getKycModalVariant, getGateUserMessage } from '@/utils/capability-gate' +import { railJurisdictionForBank } from '@/utils/bridge.utils' import { useTosGuard } from '@/hooks/useTosGuard' import { BridgeTosStep } from '@/components/Kyc/BridgeTosStep' import { useModalsContext } from '@/context/ModalsContext' @@ -89,13 +90,37 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => { const formRef = useRef<{ handleSubmit: () => void }>(null) const [isSupportedTokensModalOpen, setIsSupportedTokensModalOpen] = useState(false) + // read country from path params (web: /add-money/india) or query params (native: /add-money?country=india) + const countryFromQuery = searchParams.get('country') + const viewFromQuery = searchParams.get('view') + const rawCountry = countryFromQuery || params.country + const countryPathParts = Array.isArray(rawCountry) ? rawCountry : [rawCountry].filter(Boolean) + const isBankPage = viewFromQuery === 'bank' || countryPathParts[countryPathParts.length - 1] === 'bank' + const countrySlugFromUrl = + isBankPage && !viewFromQuery ? countryPathParts.slice(0, -1).join('-') : countryPathParts.join('-') + + const currentCountry = countryData.find( + (country) => country.type === 'country' && country.path === countrySlugFromUrl + ) + // Provider-blind bank-channel deposit gate + "any bank rail pending" // signal (used to open the under-review status modal AFTER the gate // already returned `ready`). Reads through useCapabilities's role-aware // primitives — see utils/capability-gate.ts + utils/rail-channel.ts. + // + // SCOPE: when the user is on /add-money/, narrow the gate to + // that country's rail jurisdiction. Without this, a rejected rail in an + // unrelated jurisdiction (e.g. a Bridge BANK_TRANSFER_MX REJECTED row + // from the 2026-06-01 sync) trips `blocked-rejection` here and the user + // sees "We couldn't unlock this" on a country whose own rail is fine. + // Matches the scoping already in /add-money/[country]/bank/page.tsx. const { isKycApproved, gateFor, bankRails } = useCapabilities() const isUserKycApproved = isKycApproved - const gate = useMemo(() => gateFor('deposit', { channel: 'bank' }), [gateFor]) + const bankCountry = useMemo(() => railJurisdictionForBank(currentCountry?.id), [currentCountry?.id]) + const gate = useMemo( + () => gateFor('deposit', { channel: 'bank', country: bankCountry }), + [gateFor, bankCountry] + ) const isBankRailUnderReview = useMemo(() => bankRails().some((rail) => rail.status === 'pending'), [bankRails]) const { guardWithTos, showBridgeTos, hideTos } = useTosGuard() const { setIsSupportModalOpen } = useModalsContext() @@ -109,19 +134,6 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => { if (sumsubFlow.showWrapper) setIsKycModalOpen(false) }, [sumsubFlow.showWrapper]) - // read country from path params (web: /add-money/india) or query params (native: /add-money?country=india) - const countryFromQuery = searchParams.get('country') - const viewFromQuery = searchParams.get('view') - const rawCountry = countryFromQuery || params.country - const countryPathParts = Array.isArray(rawCountry) ? rawCountry : [rawCountry].filter(Boolean) - const isBankPage = viewFromQuery === 'bank' || countryPathParts[countryPathParts.length - 1] === 'bank' - const countrySlugFromUrl = - isBankPage && !viewFromQuery ? countryPathParts.slice(0, -1).join('-') : countryPathParts.join('-') - - const currentCountry = countryData.find( - (country) => country.type === 'country' && country.path === countrySlugFromUrl - ) - /** returns true if the user is gated (caller should return early) */ const checkBridgeGate = useCallback( (onAfterTos?: () => void): boolean => { From 4f2c6ac75fdc59935c5aa4f5001d28de84e8743c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Mon, 1 Jun 2026 11:26:35 -0300 Subject: [PATCH 2/2] fix: format --- src/components/AddWithdraw/AddWithdrawCountriesList.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/AddWithdraw/AddWithdrawCountriesList.tsx b/src/components/AddWithdraw/AddWithdrawCountriesList.tsx index ea57892a6..cd3245c24 100644 --- a/src/components/AddWithdraw/AddWithdrawCountriesList.tsx +++ b/src/components/AddWithdraw/AddWithdrawCountriesList.tsx @@ -117,10 +117,7 @@ const AddWithdrawCountriesList = ({ flow }: AddWithdrawCountriesListProps) => { const { isKycApproved, gateFor, bankRails } = useCapabilities() const isUserKycApproved = isKycApproved const bankCountry = useMemo(() => railJurisdictionForBank(currentCountry?.id), [currentCountry?.id]) - const gate = useMemo( - () => gateFor('deposit', { channel: 'bank', country: bankCountry }), - [gateFor, bankCountry] - ) + const gate = useMemo(() => gateFor('deposit', { channel: 'bank', country: bankCountry }), [gateFor, bankCountry]) const isBankRailUnderReview = useMemo(() => bankRails().some((rail) => rail.status === 'pending'), [bankRails]) const { guardWithTos, showBridgeTos, hideTos } = useTosGuard() const { setIsSupportModalOpen } = useModalsContext()