From 13519d199e32738f3d4416a245c245eddc6d7ac4 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 4 Mar 2026 14:00:32 +0700 Subject: [PATCH 01/24] implement reveal PIN --- .../config/scenarios/RevealPin.tsx | 65 +++++++++++++++++++ .../config/scenarios/index.ts | 4 ++ .../config/scenarios/names.ts | 1 + src/languages/de.ts | 1 + src/languages/en.ts | 3 + src/languages/es.ts | 3 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + .../API/parameters/RevealCardPINParams.ts | 5 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/CardPINStore.ts | 44 +++++++++++++ .../Biometrics/VALUES.ts | 8 +++ .../MultifactorAuthentication/index.ts | 18 +++++ .../Wallet/ExpensifyCardPage/index.tsx | 23 ++++++- 20 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx create mode 100644 src/libs/API/parameters/RevealCardPINParams.ts create mode 100644 src/libs/CardPINStore.ts diff --git a/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx b/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx new file mode 100644 index 0000000000000..97d9cf24eb50c --- /dev/null +++ b/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import createScreenWithDefaults from '@components/MultifactorAuthentication/components/OutcomeScreen/createScreenWithDefaults'; +import {DefaultClientFailureScreen} from '@components/MultifactorAuthentication/components/OutcomeScreen/FailureScreen/defaultScreens'; +import type { + MultifactorAuthenticationScenario, + MultifactorAuthenticationScenarioAdditionalParams, + MultifactorAuthenticationScenarioCustomConfig, +} from '@components/MultifactorAuthentication/config/types'; +import {revealPINForCard} from '@libs/actions/MultifactorAuthentication'; +import {setRevealedPIN} from '@libs/CardPINStore'; +import CONST from '@src/CONST'; + +/** + * Payload type for the REVEAL_PIN scenario. + * Contains the cardID for the card whose PIN is being revealed. + */ +type Payload = { + cardID: string; +}; + +/** + * Type guard to verify the payload is a RevealPin payload. + */ +function isRevealPinPayload(payload: MultifactorAuthenticationScenarioAdditionalParams | undefined): payload is Payload { + return !!payload && 'cardID' in payload; +} + +const AuthenticationCanceledFailureScreen = createScreenWithDefaults( + DefaultClientFailureScreen, + { + subtitle: 'multifactorAuthentication.revealPin.authenticationCanceled', + }, + 'AuthenticationCanceledFailureScreen', +); + +/** + * Configuration for the REVEAL_PIN multifactor authentication scenario. + * This scenario is used when a UK/EU cardholder reveals the PIN of their physical card. + * + * Callback behavior: + * - Success: Store the revealed PIN in CardPINStore and return SKIP_OUTCOME_SCREEN + * - Authentication failure: Return SHOW_OUTCOME_SCREEN to show failure screen + */ +export default { + allowedAuthenticationMethods: [CONST.MULTIFACTOR_AUTHENTICATION.TYPE.BIOMETRICS], + action: revealPINForCard, + + callback: async (isSuccessful, callbackInput, payload) => { + if (isSuccessful && isRevealPinPayload(payload)) { + const pin = callbackInput.body?.pin as string | undefined; + if (pin) { + setRevealedPIN(payload.cardID, pin); + } + return CONST.MULTIFACTOR_AUTHENTICATION.CALLBACK_RESPONSE.SKIP_OUTCOME_SCREEN; + } + + return CONST.MULTIFACTOR_AUTHENTICATION.CALLBACK_RESPONSE.SHOW_OUTCOME_SCREEN; + }, + + failureScreens: { + [CONST.MULTIFACTOR_AUTHENTICATION.REASON.EXPO.CANCELED]: , + }, +} as const satisfies MultifactorAuthenticationScenarioCustomConfig; + +export type {Payload}; diff --git a/src/components/MultifactorAuthentication/config/scenarios/index.ts b/src/components/MultifactorAuthentication/config/scenarios/index.ts index 8163a5b2a3999..04e00abb8f1ca 100644 --- a/src/components/MultifactorAuthentication/config/scenarios/index.ts +++ b/src/components/MultifactorAuthentication/config/scenarios/index.ts @@ -5,6 +5,8 @@ import type {Payload as AuthorizeTransactionPayload} from './AuthorizeTransactio import AuthorizeTransaction from './AuthorizeTransaction'; import BiometricsTest from './BiometricsTest'; import {customConfig} from './DefaultUserInterface'; +import type {Payload as RevealPinPayload} from './RevealPin'; +import RevealPin from './RevealPin'; import type {Payload as SetPinOrderCardPayload} from './SetPinOrderCard'; import SetPinOrderCard from './SetPinOrderCard'; @@ -16,6 +18,7 @@ type Payloads = { [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.BIOMETRICS_TEST]: EmptyObject; [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.SET_PIN_ORDER_CARD]: SetPinOrderCardPayload; [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.AUTHORIZE_TRANSACTION]: AuthorizeTransactionPayload; + [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.REVEAL_PIN]: RevealPinPayload; }; /** @@ -25,6 +28,7 @@ const Configs = { [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.BIOMETRICS_TEST]: customConfig(BiometricsTest), [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.SET_PIN_ORDER_CARD]: customConfig(SetPinOrderCard), [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.AUTHORIZE_TRANSACTION]: customConfig(AuthorizeTransaction), + [CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.REVEAL_PIN]: customConfig(RevealPin), } as const satisfies MultifactorAuthenticationScenarioConfigRecord; export default Configs; diff --git a/src/components/MultifactorAuthentication/config/scenarios/names.ts b/src/components/MultifactorAuthentication/config/scenarios/names.ts index 616178d133101..504e401c8201c 100644 --- a/src/components/MultifactorAuthentication/config/scenarios/names.ts +++ b/src/components/MultifactorAuthentication/config/scenarios/names.ts @@ -10,6 +10,7 @@ const SCENARIO_NAMES = { BIOMETRICS_TEST: 'BIOMETRICS-TEST', SET_PIN_ORDER_CARD: 'SET-PIN-ORDER-CARD', AUTHORIZE_TRANSACTION: 'AUTHORIZE-TRANSACTION', + REVEAL_PIN: 'REVEAL-PIN', } as const; /** diff --git a/src/languages/de.ts b/src/languages/de.ts index d0d21a511851f..0a19aad9154d1 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -727,6 +727,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: 'Überprüfung fehlgeschlagen', setPin: {didNotShipCard: 'Wir haben Ihre Karte nicht versendet. Bitte versuchen Sie es erneut.'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/en.ts b/src/languages/en.ts index 2fbeb5e61a3e9..89db2ff7b94f8 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -743,6 +743,9 @@ const translations = { setPin: { didNotShipCard: 'We didn’t ship your card. Please try again.', }, + revealPin: { + authenticationCanceled: 'We couldn’t reveal your PIN. Please try again.', + }, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/es.ts b/src/languages/es.ts index 351987fe2e529..5110db74d421f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -589,6 +589,9 @@ const translations: TranslationDeepObject = { setPin: { didNotShipCard: 'No enviamos tu tarjeta. Por favor, inténtalo de nuevo.', }, + revealPin: { + authenticationCanceled: 'No pudimos revelar tu PIN. Por favor, inténtalo de nuevo.', + }, }, validateCodeModal: { successfulSignInTitle: 'Abracadabra,\n¡sesión iniciada!', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 02f07c9a54f60..561dafd0480e1 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -730,6 +730,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: 'Échec de la vérification', setPin: {didNotShipCard: 'Nous n’avons pas envoyé votre carte. Veuillez réessayer.'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/it.ts b/src/languages/it.ts index 0ff2dfc7946a2..60fe393a24b4b 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -728,6 +728,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: 'Verifica non riuscita', setPin: {didNotShipCard: 'Non abbiamo spedito la tua carta. Riprova.'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/ja.ts b/src/languages/ja.ts index e4e692c87b1a1..f9b228b9a4716 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -725,6 +725,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: '認証に失敗しました', setPin: {didNotShipCard: 'カードを発送できませんでした。もう一度お試しください。'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 5503c9fa4d718..6bce59788555f 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -726,6 +726,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: 'Verificatie mislukt', setPin: {didNotShipCard: 'We hebben je kaart niet verzonden. Probeer het opnieuw.'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/pl.ts b/src/languages/pl.ts index d5b0aa160d8e6..805fb824472ca 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -727,6 +727,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: 'Weryfikacja nie powiodła się', setPin: {didNotShipCard: 'Nie wysłaliśmy twojej karty. Spróbuj ponownie.'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 095cdb86af51a..4e53b78a33cc1 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -725,6 +725,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: 'Falha na verificação', setPin: {didNotShipCard: 'Não enviamos seu cartão. Tente novamente.'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 537be84898d9b..2deeab7f82879 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -718,6 +718,7 @@ const translations: TranslationDeepObject = { }, verificationFailed: '验证失败', setPin: {didNotShipCard: '我们未能寄出您的卡。请重试。'}, + revealPin: {authenticationCanceled: "We couldn't reveal your PIN. Please try again."}, }, validateCodeModal: { successfulSignInTitle: dedent(` diff --git a/src/libs/API/parameters/RevealCardPINParams.ts b/src/libs/API/parameters/RevealCardPINParams.ts new file mode 100644 index 0000000000000..cdb26cb352717 --- /dev/null +++ b/src/libs/API/parameters/RevealCardPINParams.ts @@ -0,0 +1,5 @@ +import type {MultifactorAuthenticationAPIParams} from '@components/MultifactorAuthentication/config/types'; + +type RevealCardPINParams = MultifactorAuthenticationAPIParams<'REVEAL-PIN'>; + +export default RevealCardPINParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index e864fe886429c..41a55ece24af7 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -491,4 +491,5 @@ export type {default as DeleteVacationDelegateParams} from './DeleteVacationDele export type {default as SetTwoFactorAuthExemptEmailForDomainParams} from './SetTwoFactorAuthExemptEmailForDomainParams'; export type {default as ResetDomainMemberTwoFactorAuthParams} from './ResetDomainMemberTwoFactorAuthParams'; export type {default as AuthorizeTransactionParams} from './AuthorizeTransactionParams'; +export type {default as RevealCardPINParams} from './RevealCardPINParams'; export type {default as DenyTransactionParams} from './DenyTransactionParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 247f605a9ef8f..80220daacb0af 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1352,6 +1352,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { AUTHORIZE_TRANSACTION: 'AuthorizeTransaction', DENY_TRANSACTION: 'DenyTransaction', GET_TRANSACTIONS_PENDING_3DS_REVIEW: 'GetTransactionsPending3DSReview', + REVEAL_CARD_PIN: 'RevealCardPIN', } as const; type SideEffectRequestCommand = ValueOf; @@ -1388,6 +1389,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.AUTHORIZE_TRANSACTION]: Parameters.AuthorizeTransactionParams; [SIDE_EFFECT_REQUEST_COMMANDS.DENY_TRANSACTION]: Parameters.DenyTransactionParams; [SIDE_EFFECT_REQUEST_COMMANDS.GET_TRANSACTIONS_PENDING_3DS_REVIEW]: null; + [SIDE_EFFECT_REQUEST_COMMANDS.REVEAL_CARD_PIN]: Parameters.RevealCardPINParams; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/CardPINStore.ts b/src/libs/CardPINStore.ts new file mode 100644 index 0000000000000..52f330187abc0 --- /dev/null +++ b/src/libs/CardPINStore.ts @@ -0,0 +1,44 @@ +import {useSyncExternalStore} from 'react'; + +/** + * In-memory store for revealed card PINs. + * PINs must NOT be persisted to disk (PCI compliance), so we use a module-level store + * with useSyncExternalStore for React integration instead of Onyx. + */ + +type Listener = () => void; + +const listeners = new Set(); +const revealedPINs: Record = {}; + +function subscribe(listener: Listener) { + listeners.add(listener); + return () => listeners.delete(listener); +} + +function notifyListeners() { + for (const listener of listeners) { + listener(); + } +} + +function setRevealedPIN(cardID: string, pin: string) { + revealedPINs[cardID] = pin; + notifyListeners(); +} + +function clearRevealedPIN(cardID: string) { + delete revealedPINs[cardID]; + notifyListeners(); +} + +function getSnapshot() { + return revealedPINs; +} + +function useRevealedPIN(cardID: string): string | undefined { + const pins = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); + return pins[cardID]; +} + +export {setRevealedPIN, clearRevealedPIN, useRevealedPIN}; diff --git a/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts b/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts index 32cd544ff8bb7..53556796696bc 100644 --- a/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts +++ b/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts @@ -62,6 +62,7 @@ const REASON = { ALREADY_REVIEWED: 'Transaction already reviewed', TRANSACTION_APPROVED: 'Transaction approved successfully', TRANSACTION_DENIED: 'Transaction denied successfully', + PIN_REVEALED: 'PIN revealed successfully', }, CHALLENGE: { CHALLENGE_MISSING: 'Challenge is missing', @@ -187,6 +188,13 @@ const API_RESPONSE_MAP = { ...MULTIFACTOR_AUTHENTICATION_COMMAND_BASE_CLIENT_ERRORS, }, }, + + REVEAL_CARD_PIN: { + [HTTP_STATUS.SUCCESS]: REASON.BACKEND.PIN_REVEALED, + [HTTP_STATUS.CLIENT_ERROR]: { + ...MULTIFACTOR_AUTHENTICATION_COMMAND_BASE_CLIENT_ERRORS, + }, + }, } as const; /** diff --git a/src/libs/actions/MultifactorAuthentication/index.ts b/src/libs/actions/MultifactorAuthentication/index.ts index cecf1c18ee6a5..265a7f0686138 100644 --- a/src/libs/actions/MultifactorAuthentication/index.ts +++ b/src/libs/actions/MultifactorAuthentication/index.ts @@ -266,6 +266,23 @@ async function setPersonalDetailsAndShipExpensifyCardsWithPIN(params: Multifacto return parseHttpRequest(undefined, CONST.MULTIFACTOR_AUTHENTICATION.API_RESPONSE_MAP.SET_PERSONAL_DETAILS_AND_SHIP_EXPENSIFY_CARDS_WITH_PIN, undefined); } } +async function revealPINForCard({cardID, signedChallenge, authenticationMethod}: MultifactorAuthenticationScenarioParameters['REVEAL-PIN']) { + try { + const response = await makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.REVEAL_CARD_PIN, {cardID, signedChallenge: JSON.stringify(signedChallenge), authenticationMethod}, {}); + + const {jsonCode, message, pin} = response ?? {}; + const parsed = parseHttpRequest(jsonCode, CONST.MULTIFACTOR_AUTHENTICATION.API_RESPONSE_MAP.REVEAL_CARD_PIN, message); + + return { + ...parsed, + body: {pin: String(pin ?? '')}, + }; + } catch (error) { + Log.hmmm('[MultifactorAuthentication] Failed to reveal PIN for card', {error}); + return parseHttpRequest(undefined, CONST.MULTIFACTOR_AUTHENTICATION.API_RESPONSE_MAP.REVEAL_CARD_PIN, undefined); + } +} + /** Check whether a given transaction is still pending review and update the transactionsPending3DSReview key in Onyx */ async function isTransactionStillPending3DSReview(transactionID: string) { const response = await makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GET_TRANSACTIONS_PENDING_3DS_REVIEW, null, {}); @@ -352,6 +369,7 @@ export { markHasAcceptedSoftPrompt, clearLocalMFAPublicKeyList, setPersonalDetailsAndShipExpensifyCardsWithPIN, + revealPINForCard, isTransactionStillPending3DSReview, denyTransaction, authorizeTransaction, diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx index da402667d4a01..31338c7555da3 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx @@ -13,6 +13,7 @@ import type {LocalizedTranslate} from '@components/LocaleContextProvider'; import {useLockedAccountActions, useLockedAccountState} from '@components/LockedAccountModalProvider'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import {useMultifactorAuthentication} from '@components/MultifactorAuthentication/Context'; import {usePersonalDetails, useSession} from '@components/OnyxListItemProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; @@ -28,6 +29,7 @@ import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import {freezeCard, unfreezeCard} from '@libs/actions/Card'; import {resetValidateActionCodeSent} from '@libs/actions/User'; +import {clearRevealedPIN, useRevealedPIN} from '@libs/CardPINStore'; import {formatCardExpiration, getDomainCards, getTranslationKeyForLimitType, isCardFrozen, maskCard, maskPin} from '@libs/CardUtils'; import {convertToDisplayString, getCurrencyKeyByCountryCode} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -99,6 +101,7 @@ function ExpensifyCardPage({route}: ExpensifyCardPageProps) { const {isOffline} = useNetwork(); const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); + const {executeScenario} = useMultifactorAuthentication(); const isTravelCard = cardList?.[cardID]?.nameValuePairs?.isTravelCard; const shouldDisplayCardDomain = !isTravelCard && (!cardList?.[cardID]?.nameValuePairs?.issuedBy || !cardList?.[cardID]?.nameValuePairs?.isVirtual); const domain = cardList?.[cardID]?.domainName ?? ''; @@ -130,12 +133,14 @@ function ExpensifyCardPage({route}: ExpensifyCardPageProps) { const {cardsDetails, isCardDetailsLoading, cardsDetailsErrors} = useExpensifyCardState(); const {setCardsDetails} = useExpensifyCardActions(); + const revealedPIN = useRevealedPIN(cardID); - // Resets card details when navigating away from the page. + // Resets card details and revealed PIN when navigating away from the page. useFocusEffect( useCallback(() => { return () => { setCardsDetails((oldCardDetails) => ({...oldCardDetails, [cardID]: null})); + clearRevealedPIN(cardID); }; }, [cardID, setCardsDetails]), ); @@ -425,9 +430,23 @@ function ExpensifyCardPage({route}: ExpensifyCardPageProps) { {shouldShowPIN && ( { + executeScenario(CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.REVEAL_PIN, { + cardID: String(currentPhysicalCard?.cardID), + }); + }} + isDisabled={isOffline} + /> + ) : undefined + } /> )} Date: Wed, 4 Mar 2026 14:35:21 +0700 Subject: [PATCH 02/24] update: Reveal Pin --- .../config/scenarios/RevealPin.tsx | 4 ++++ src/libs/MultifactorAuthentication/Biometrics/VALUES.ts | 3 ++- src/pages/settings/Wallet/ExpensifyCardPage/index.tsx | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx b/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx index 97d9cf24eb50c..81c8dfa887155 100644 --- a/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx +++ b/src/components/MultifactorAuthentication/config/scenarios/RevealPin.tsx @@ -8,7 +8,9 @@ import type { } from '@components/MultifactorAuthentication/config/types'; import {revealPINForCard} from '@libs/actions/MultifactorAuthentication'; import {setRevealedPIN} from '@libs/CardPINStore'; +import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; /** * Payload type for the REVEAL_PIN scenario. @@ -51,6 +53,8 @@ export default { if (pin) { setRevealedPIN(payload.cardID, pin); } + Navigation.closeRHPFlow(); + Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(String(payload.cardID))); return CONST.MULTIFACTOR_AUTHENTICATION.CALLBACK_RESPONSE.SKIP_OUTCOME_SCREEN; } diff --git a/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts b/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts index 53556796696bc..4c903cf38c247 100644 --- a/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts +++ b/src/libs/MultifactorAuthentication/Biometrics/VALUES.ts @@ -63,6 +63,7 @@ const REASON = { TRANSACTION_APPROVED: 'Transaction approved successfully', TRANSACTION_DENIED: 'Transaction denied successfully', PIN_REVEALED: 'PIN revealed successfully', + SET_PIN: 'PIN set successfully', }, CHALLENGE: { CHALLENGE_MISSING: 'Challenge is missing', @@ -183,7 +184,7 @@ const API_RESPONSE_MAP = { }, SET_PERSONAL_DETAILS_AND_SHIP_EXPENSIFY_CARDS_WITH_PIN: { - [HTTP_STATUS.SUCCESS]: REASON.BACKEND.AUTHORIZATION_SUCCESSFUL, + [HTTP_STATUS.SUCCESS]: REASON.BACKEND.SET_PIN, [HTTP_STATUS.CLIENT_ERROR]: { ...MULTIFACTOR_AUTHENTICATION_COMMAND_BASE_CLIENT_ERRORS, }, diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx index 31338c7555da3..b1317a36b2dfa 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx @@ -30,7 +30,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {freezeCard, unfreezeCard} from '@libs/actions/Card'; import {resetValidateActionCodeSent} from '@libs/actions/User'; import {clearRevealedPIN, useRevealedPIN} from '@libs/CardPINStore'; -import {formatCardExpiration, getDomainCards, getTranslationKeyForLimitType, isCardFrozen, maskCard, maskPin} from '@libs/CardUtils'; +import {formatCardExpiration, getDomainCards, getTranslationKeyForLimitType, isCardFrozen, isExpensifyCardUkEuSupported, maskCard, maskPin} from '@libs/CardUtils'; import {convertToDisplayString, getCurrencyKeyByCountryCode} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -157,6 +157,7 @@ function ExpensifyCardPage({route}: ExpensifyCardPageProps) { const currency = getCurrencyKeyByCountryCode(currencyList, currentCard?.nameValuePairs?.country ?? currentCard?.nameValuePairs?.feedCountry); const shouldShowPIN = currency !== CONST.CURRENCY.USD; + const canRevealPin = isExpensifyCardUkEuSupported(currentPhysicalCard) && currentPhysicalCard?.state === CONST.EXPENSIFY_CARD.STATE.OPEN && !revealedPIN; const formattedAvailableSpendAmount = convertToDisplayString(currentCard?.availableSpend, currency); const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(currentCard?.nameValuePairs?.limitType); @@ -433,9 +434,9 @@ function ExpensifyCardPage({route}: ExpensifyCardPageProps) { title={maskPin(revealedPIN)} interactive={false} titleStyle={styles.walletCardNumber} - shouldShowRightComponent={!revealedPIN} + shouldShowRightComponent={canRevealPin} rightComponent={ - !revealedPIN ? ( + canRevealPin ? (