Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import ConnectionCompletePage from '@pages/ConnectionCompletePage';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import {AttachmentModalContextProvider} from '@pages/media/AttachmentModalScreen/AttachmentModalContext';
import RequireTwoFactorAuthenticationOverlay from '@pages/RequireTwoFactorAuthenticationOverlay';
import ExpensifyCardContextProvider from '@pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardContextProvider';
import TravelCVVContextProvider from '@pages/settings/Wallet/TravelCVVPage/TravelCVVContextProvider';

import * as Modal from '@userActions/Modal';
Expand Down Expand Up @@ -174,7 +173,6 @@ function AuthScreens() {
FullScreenContextProvider,
SearchRouterContextProvider,
ProductTrainingContextProvider,
ExpensifyCardContextProvider,
TravelCVVContextProvider,
KYCWallContextProvider,
WideRHPContextProvider,
Expand Down
28 changes: 23 additions & 5 deletions src/libs/RevealedCardSecretsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ import {useSyncExternalStore} from 'react';
* compliance, so we use module-level stores with useSyncExternalStore for React
* integration instead of Onyx.
*
* Two independent stores live here:
* Two secret stores live here:
* - Physical card PIN — surfaced by the reveal-PIN flow on physical cards.
* - Virtual card details (PAN/expiration/CVV) — surfaced by the SCA reveal
* flow on virtual cards, where the MFA scenario callback runs outside React
* and needs a place to drop the details for the card page to pick up.
* - Virtual card details (PAN/expiration/CVV) — surfaced when revealing a
* virtual card, either through the SCA reveal flow (UK/EU cards, where the
* MFA scenario callback runs outside React) or the magic-code reveal flow
* (US cards). Both drop the details here for the card page to pick up.
*
* A third store tracks the virtual-card reveal loading flag. It is not a secret,
* but it is colocated here because the magic-code reveal request runs on a
* separate screen (the confirm-magic-code RHP) from the card page that renders
* the loading state, so both screens need a shared, non-persisted place for it.
*
* Each store keeps only one entry at a time so at most one card's secret is in
* memory. The two stores are independent: revealing a PIN does not clear the
* memory. The stores are independent: revealing a PIN does not clear the
* virtual-card details and vice versa.
*/

Expand Down Expand Up @@ -65,6 +71,7 @@ function createRevealedSecretStore<T>() {

const physicalCardPinStore = createRevealedSecretStore<string>();
const virtualCardDetailsStore = createRevealedSecretStore<ExpensifyCardDetails>();
const virtualCardDetailsLoadingStore = createRevealedSecretStore<boolean>();

const setRevealedPhysicalCardPin = physicalCardPinStore.set;
const clearRevealedPhysicalCardPin = physicalCardPinStore.clear;
Expand All @@ -81,6 +88,14 @@ const useRevealedVirtualCardDetails = virtualCardDetailsStore.useValue;
*/
const useAllRevealedVirtualCardDetails = virtualCardDetailsStore.useAll;

const setVirtualCardDetailsLoading = virtualCardDetailsLoadingStore.set;
const clearVirtualCardDetailsLoading = virtualCardDetailsLoadingStore.clear;
/**
* Returns the entire virtual-card reveal-loading map, keyed by cardID. Read inside a
* `.map()` over virtual cards, where calling a per-card hook would violate the Rules of Hooks.
*/
const useAllVirtualCardDetailsLoading = virtualCardDetailsLoadingStore.useAll;

export {
setRevealedPhysicalCardPin,
clearRevealedPhysicalCardPin,
Expand All @@ -89,4 +104,7 @@ export {
clearRevealedVirtualCardDetails,
useRevealedVirtualCardDetails,
useAllRevealedVirtualCardDetails,
setVirtualCardDetailsLoading,
clearVirtualCardDetailsLoading,
useAllVirtualCardDetailsLoading,
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {DomainCardNavigatorParamList, SettingsNavigatorParamList} from '@libs/Navigation/types';
import {setRevealedVirtualCardDetails, setVirtualCardDetailsLoading} from '@libs/RevealedCardSecretsStore';

import {getNormalizedSubPageValues} from '@pages/MissingPersonalDetails/utils';

Expand All @@ -18,13 +19,10 @@ import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import type {ExpensifyCardDetails} from '@src/types/onyx/Card';
import type {Errors} from '@src/types/onyx/OnyxCommon';

import React, {useState} from 'react';

import {useExpensifyCardActions} from './ExpensifyCardContextProvider';

type ExpensifyCardVerifyAccountPageProps =
| PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.WALLET.DOMAIN_CARD_CONFIRM_MAGIC_CODE>
| PlatformStackScreenProps<DomainCardNavigatorParamList, typeof SCREENS.DOMAIN_CARD.DOMAIN_CARD_CONFIRM_MAGIC_CODE>;
Expand All @@ -34,7 +32,6 @@ function ExpensifyCardVerifyAccountPage({route}: ExpensifyCardVerifyAccountPageP
const {translate} = useLocalize();
const [validateError, setValidateError] = useState<Errors>({});
const primaryLogin = usePrimaryContactMethod();
const {setIsCardDetailsLoading, setCardsDetails, setCardsDetailsErrors} = useExpensifyCardActions();
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE);

Expand All @@ -47,31 +44,23 @@ function ExpensifyCardVerifyAccountPage({route}: ExpensifyCardVerifyAccountPageP
};

const handleRevealCardDetails = (validateCode: string) => {
setIsCardDetailsLoading((prevState: Record<number, boolean>) => ({
...prevState,
[cardID]: true,
}));
// We can't store the response in Onyx for security reasons.
// That is why this action is handled manually and the response is stored in a local state.
// Hence eslint disable here.
setVirtualCardDetailsLoading(cardID, true);
// Card secrets (PAN/expiration/CVV) must NOT be persisted to disk for PCI compliance,
// so the revealed details are kept in the in-memory RevealedCardSecretsStore instead of Onyx.

const personalDetailsForm = getNormalizedSubPageValues(privatePersonalDetails);
const personalDetailsParams = buildSetPersonalDetailsAndShipExpensifyCardsParams(personalDetailsForm, countryCode);

setPersonalDetailsAndRevealExpensifyCard(personalDetailsParams, Number.parseInt(cardID, 10), validateCode)
.then((value) => {
setCardsDetails((prevState: Record<number, ExpensifyCardDetails | null>) => ({...prevState, [cardID]: value}));
setCardsDetailsErrors((prevState) => ({
...prevState,
[cardID]: '',
}));
setRevealedVirtualCardDetails(cardID, value);
navigateBack();
})
.catch((error: TranslationPaths) => {
setValidateError(getMicroSecondOnyxErrorWithTranslationKey(error));
})
.finally(() => {
setIsCardDetailsLoading((prevState: Record<number, boolean>) => ({...prevState, [cardID]: false}));
setVirtualCardDetailsLoading(cardID, false);
});
};

Expand Down
Loading
Loading