From 931b2002310014107c2f65a577c3ff8c8048038a Mon Sep 17 00:00:00 2001 From: Mayank Mittal Date: Fri, 9 Oct 2026 13:14:42 +0530 Subject: [PATCH 1/2] fix(card): ask for a declared country before Didit card KYC too handleProceedToKyc required a user-picked country only on the Sumsub branch. An IP in a Rain-only market routed straight to Didit with nobody asked: a Hong Kong resident whose IP resolved to Singapore got a Rain card customer that nothing rewrites, in a Didit questionnaire that does not offer Hong Kong. The backend refuses a Didit card session only for a country Wirex serves, and an SG IP gave it nothing to refuse on. The declared-country check now runs for both providers. The home setup step reaches this action with no gate having run, so this is where it has to live. Co-Authored-By: Claude Opus 5.5 --- hooks/useCardSteps/useCardSteps.ts | 53 ++++++++++++++++-------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/hooks/useCardSteps/useCardSteps.ts b/hooks/useCardSteps/useCardSteps.ts index 767d4ed84..ddd5e7f02 100644 --- a/hooks/useCardSteps/useCardSteps.ts +++ b/hooks/useCardSteps/useCardSteps.ts @@ -255,9 +255,8 @@ export function useCardSteps( * ended up on Rain permanently: the Didit session creates a card customer * that defaults to Rain, and nothing ever rewrites it. * - * Ask instead of guessing. This is the same screen the Sumsub branch - * below uses, and it is only reached when the IP lookup failed too — a - * country that resolved keeps going without an extra step. + * Ask instead of guessing. This is the same screen the declared-country + * check below sends to; it is split out only so the event says why. */ if (!countryCode) { track(TRACKING_EVENTS.CARD_KYC_FLOW_TRIGGERED, { @@ -292,29 +291,33 @@ export function useCardSteps( router.push(path.CARD_PENDING as any); return; } + } - /** - * Sumsub card sessions REQUIRE a country the user declared themselves — - * an IP guess is refused server-side, because `user.country` pins which - * issuer serves them and nothing writes it back. Ask here rather than - * letting the session fail: this is the entry point the home setup step - * uses (`useHomeSetupSteps` calls this action directly rather than - * `startCardOnboarding`), so it is reached with no gate having run at - * all, and `resolveKycProvider` deliberately does not persist the - * country it routes on. - * - * The selection screen writes `source: 'manual'` and re-enters the flow - * via `/card/activate?countryConfirmed=true`, so this asks once. - */ - if (useCountryStore.getState().countryInfo?.source !== 'manual') { - track(TRACKING_EVENTS.CARD_KYC_FLOW_TRIGGERED, { - action: 'country_selection_required', - kycProvider, - countryCode, - }); - router.push(path.CARD_COUNTRY_SELECTION as any); - return; - } + /** + * Every card verification, Didit included, starts from a country the user + * declared themselves. This is the entry point the home setup step uses + * (`useHomeSetupSteps` calls this action directly rather than + * `startCardOnboarding`), so it is reached with no gate having run at all. + * + * Sumsub refuses an IP guess server-side, so that branch always needed + * this. Didit used to skip it, and the IP answer then picked the issuer: + * a Hong Kong resident whose IP resolved to Singapore was routed to Didit, + * which created a Rain card customer that nothing rewrites, in a Didit + * questionnaire that does not offer Hong Kong. The backend only refuses a + * Didit card session for a country Wirex serves, and an IP in a Rain-only + * market gives it nothing to refuse on. + * + * The selection screen writes `source: 'manual'` and re-enters the flow + * via `/card/activate?countryConfirmed=true`, so this asks once. + */ + if (useCountryStore.getState().countryInfo?.source !== 'manual') { + track(TRACKING_EVENTS.CARD_KYC_FLOW_TRIGGERED, { + action: 'country_selection_required', + kycProvider, + countryCode, + }); + router.push(path.CARD_COUNTRY_SELECTION as any); + return; } router.push((kycProvider === KycProvider.SUMSUB ? path.SUMSUB_KYC : path.KYC) as any); From 6d58cc479585239dcc884ec2bbff3118a4ee06dc Mon Sep 17 00:00:00 2001 From: Mayank Mittal Date: Fri, 9 Oct 2026 13:14:42 +0530 Subject: [PATCH 2/2] fix(card): add the served territories missing from the country picker COUNTRIES listed UN member states only, so nine markets we issue cards in could not be picked: Hong Kong, Macao, Gibraltar, Guernsey, Isle of Man and Jersey (Wirex), and Cayman Islands, Turks and Caicos and Ivory Coast (Rain). Hong Kong users picked China, were told the card is unavailable, and some then picked an unrelated country to get through. The other two readers (VA apply modal, card reveal) only look up a name by code, so adding entries is safe for them. Co-Authored-By: Claude Opus 5.5 --- constants/countries.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/constants/countries.ts b/constants/countries.ts index 01c8baca6..f5bd4ec3e 100644 --- a/constants/countries.ts +++ b/constants/countries.ts @@ -36,6 +36,7 @@ export const COUNTRIES: Country[] = [ { code: 'CM', name: 'Cameroon' }, { code: 'CA', name: 'Canada' }, { code: 'CV', name: 'Cape Verde' }, + { code: 'KY', name: 'Cayman Islands' }, { code: 'CF', name: 'Central African Republic' }, { code: 'TD', name: 'Chad' }, { code: 'CL', name: 'Chile' }, @@ -68,14 +69,17 @@ export const COUNTRIES: Country[] = [ { code: 'GE', name: 'Georgia' }, { code: 'DE', name: 'Germany' }, { code: 'GH', name: 'Ghana' }, + { code: 'GI', name: 'Gibraltar' }, { code: 'GR', name: 'Greece' }, { code: 'GD', name: 'Grenada' }, { code: 'GT', name: 'Guatemala' }, + { code: 'GG', name: 'Guernsey' }, { code: 'GN', name: 'Guinea' }, { code: 'GW', name: 'Guinea-Bissau' }, { code: 'GY', name: 'Guyana' }, { code: 'HT', name: 'Haiti' }, { code: 'HN', name: 'Honduras' }, + { code: 'HK', name: 'Hong Kong' }, { code: 'HU', name: 'Hungary' }, { code: 'IS', name: 'Iceland' }, { code: 'IN', name: 'India' }, @@ -83,10 +87,13 @@ export const COUNTRIES: Country[] = [ { code: 'IR', name: 'Iran' }, { code: 'IQ', name: 'Iraq' }, { code: 'IE', name: 'Ireland' }, + { code: 'IM', name: 'Isle of Man' }, { code: 'IL', name: 'Israel' }, { code: 'IT', name: 'Italy' }, + { code: 'CI', name: 'Ivory Coast' }, { code: 'JM', name: 'Jamaica' }, { code: 'JP', name: 'Japan' }, + { code: 'JE', name: 'Jersey' }, { code: 'JO', name: 'Jordan' }, { code: 'KZ', name: 'Kazakhstan' }, { code: 'KE', name: 'Kenya' }, @@ -105,6 +112,7 @@ export const COUNTRIES: Country[] = [ { code: 'LT', name: 'Lithuania' }, { code: 'LU', name: 'Luxembourg' }, { code: 'MK', name: 'North Macedonia' }, + { code: 'MO', name: 'Macao' }, { code: 'MG', name: 'Madagascar' }, { code: 'MW', name: 'Malawi' }, { code: 'MY', name: 'Malaysia' }, @@ -183,6 +191,7 @@ export const COUNTRIES: Country[] = [ { code: 'TN', name: 'Tunisia' }, { code: 'TR', name: 'Turkey' }, { code: 'TM', name: 'Turkmenistan' }, + { code: 'TC', name: 'Turks and Caicos Islands' }, { code: 'TV', name: 'Tuvalu' }, { code: 'UG', name: 'Uganda' }, { code: 'UA', name: 'Ukraine' },