diff --git a/src/libs/actions/Card.js b/src/libs/actions/Card.js index 92b23e2103ee..97c902876a3a 100644 --- a/src/libs/actions/Card.js +++ b/src/libs/actions/Card.js @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; - +import CONST from '../../CONST'; /** * @param {Number} cardID */ @@ -146,4 +146,29 @@ function clearCardListErrors(cardID) { Onyx.merge(ONYXKEYS.CARD_LIST, {[cardID]: {errors: null, isLoading: false}}); } -export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud}; +/** + * Makes an API call to get virtual card details (pan, cvv, expiration date, address) + * This function purposefully uses `makeRequestWithSideEffects` method. For security reason + * card details cannot be persisted in Onyx and have to be asked for each time a user want's to + * reveal them. + * + * @param {String} cardID - virtual card ID + * + * @returns {Promise} - promise with card details object + */ +function revealVirtualCardDetails(cardID) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects('RevealVirtualCardDetails', {cardID}) + .then((response) => { + if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) { + reject(); + return; + } + resolve(response); + }) + .catch(reject); + }); +} + +export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails}; diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js index e198d449d57d..672db26cea82 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.js +++ b/src/pages/settings/Wallet/ExpensifyCardPage.js @@ -18,9 +18,11 @@ import * as Expensicons from '../../../components/Icon/Expensicons'; import * as CardUtils from '../../../libs/CardUtils'; import Button from '../../../components/Button'; import CardDetails from './WalletPage/CardDetails'; +import * as Card from '../../../libs/actions/Card'; import MenuItem from '../../../components/MenuItem'; import CONST from '../../../CONST'; import assignedCardPropTypes from './assignedCardPropTypes'; +import useNetwork from '../../../hooks/useNetwork'; import theme from '../../../styles/themes/default'; import DotIndicatorMessage from '../../../components/DotIndicatorMessage'; import * as Link from '../../../libs/actions/Link'; @@ -50,12 +52,14 @@ function ExpensifyCardPage({ params: {domain}, }, }) { + const {isOffline} = useNetwork(); const {translate} = useLocalize(); const domainCards = CardUtils.getDomainCards(cardList)[domain]; const virtualCard = _.find(domainCards, (card) => card.isVirtual) || {}; const physicalCard = _.find(domainCards, (card) => !card.isVirtual) || {}; - const [shouldShowCardDetails, setShouldShowCardDetails] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [details, setDetails] = useState({}); if (_.isEmpty(virtualCard) && _.isEmpty(physicalCard)) { return ; @@ -64,7 +68,14 @@ function ExpensifyCardPage({ const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(physicalCard.availableSpend || virtualCard.availableSpend || 0); const handleRevealDetails = () => { - setShouldShowCardDetails(true); + setIsLoading(true); + // We can't store the response in Onyx for security reasons. + // That is this action is handled manually and the response is stored in a local state + // Hence the eslint disable here. + // eslint-disable-next-line rulesdir/no-thenable-actions-in-views + Card.revealVirtualCardDetails(virtualCard.cardID) + .then(setDetails) + .finally(() => setIsLoading(false)); }; const hasDetectedDomainFraud = _.some(domainCards, (card) => card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN); @@ -97,6 +108,32 @@ function ExpensifyCardPage({ {hasDetectedIndividualFraud && !hasDetectedDomainFraud ? ( <> + {details.pan ? ( + + ) : ( + + } + /> + )} {!_.isEmpty(virtualCard) && ( <> - {shouldShowCardDetails ? ( + {details.pan ? ( ) : ( @@ -143,6 +180,8 @@ function ExpensifyCardPage({ medium text={translate('cardPage.cardDetails.revealDetails')} onPress={handleRevealDetails} + isDisabled={isLoading || isOffline} + isLoading={isLoading} /> } /> diff --git a/src/types/onyx/Card.ts b/src/types/onyx/Card.ts index 2e013957f56f..395515c1ca08 100644 --- a/src/types/onyx/Card.ts +++ b/src/types/onyx/Card.ts @@ -19,4 +19,19 @@ type Card = { isLoading?: boolean; }; +type TCardDetails = { + pan: string; + expiration: string; + cvv: string; + address: { + street: string; + street2: string; + city: string; + state: string; + zip: string; + country: string; + }; +}; + export default Card; +export type {TCardDetails};