Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

22875 Add API Calls when clicking “Reveal details” buttons #29017

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f73d601
first commit
lukemorawski Oct 6, 2023
5985ec1
move to reducer and error handling
lukemorawski Oct 6, 2023
df10ff7
removed unnecessary helper function
lukemorawski Oct 9, 2023
36fb11a
formatting
lukemorawski Oct 9, 2023
bfdb546
var name change to isLoading
lukemorawski Oct 9, 2023
9a8962c
better linting
lukemorawski Oct 9, 2023
c7dd8fa
moved action types to const
lukemorawski Oct 9, 2023
ccf8f4e
action types fix and cardID added to api call
lukemorawski Oct 9, 2023
ef623a4
address refactoring
lukemorawski Oct 10, 2023
f7ba68a
refactoring
lukemorawski Oct 11, 2023
9f173af
Merge branch 'main' into 22875-api_call_on_reveal_card_details
lukemorawski Oct 11, 2023
8361350
removed comment
lukemorawski Oct 12, 2023
36a6d4e
revealCardDetails move
lukemorawski Oct 13, 2023
28ca868
Merge branch 'main' into 22875-api_call_on_reveal_card_details
lukemorawski Oct 17, 2023
a7bcbb6
added offline disabling
lukemorawski Oct 17, 2023
f1aad79
Merge branch 'main' into 22875-api_call_on_reveal_card_details
lukemorawski Oct 17, 2023
42cb473
reject with generic error message
lukemorawski Oct 18, 2023
6dac4ca
Merge branch 'main' into 22875-api_call_on_reveal_card_details
lukemorawski Oct 19, 2023
dcb188b
Update src/libs/actions/Card.js
lukemorawski Oct 20, 2023
8254860
linting
lukemorawski Oct 20, 2023
586f20e
Merge branch 'main' into 22875-api_call_on_reveal_card_details
lukemorawski Oct 23, 2023
aaa7042
merging with latest changes
lukemorawski Oct 23, 2023
95d4a43
Update src/languages/en.ts
lukemorawski Oct 24, 2023
934344a
removed error handling
lukemorawski Oct 25, 2023
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
29 changes: 27 additions & 2 deletions src/libs/actions/Card.js
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -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<Object>} - promise with card details object
*/
function revealVirtualCardDetails(cardID) {
return new Promise((resolve, reject) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we wrapping the call in this Promise? We don't use this pattern anywhere else

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukemorawski if you could explain, just curious

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something like this would also work..

function revealVirtualCardDetails(cardID) {
    // eslint-disable-next-line rulesdir/no-api-side-effects-method
    return API.makeRequestWithSideEffects('RevealVirtualCardDetails', {cardID})
        .then((response) => {
            if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) {
                throw Localize.translateLocal('cardPage.cardDetailsLoadingFailure');
            }

            return response;
        })
        .catch((err) => {
            throw err.message;
        });
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. but this isn't a blocker, maybe @lukemorawski can remove the promise later?

// 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};
53 changes: 46 additions & 7 deletions src/pages/settings/Wallet/ExpensifyCardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 <NotFoundPage />;
Expand All @@ -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);
Expand Down Expand Up @@ -97,6 +108,32 @@ function ExpensifyCardPage({

{hasDetectedIndividualFraud && !hasDetectedDomainFraud ? (
<>
{details.pan ? (
<CardDetails
pan={details.pan}
expiration={details.expiration}
cvv={details.cvv}
privatePersonalDetails={{address: details.address}}
domain={domain}
/>
) : (
<MenuItemWithTopDescription
description={translate('cardPage.virtualCardNumber')}
title={CardUtils.maskCard(virtualCard.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardNumber}
shouldShowRightComponent
rightComponent={
<Button
medium
text={translate('cardPage.cardDetails.revealDetails')}
onPress={handleRevealDetails}
isDisabled={isLoading || isOffline}
isLoading={isLoading}
/>
}
/>
)}
<DangerCardSection
title={translate('cardPage.suspiciousBannerTitle')}
description={translate('cardPage.suspiciousBannerDescription')}
Expand All @@ -123,12 +160,12 @@ function ExpensifyCardPage({
/>
{!_.isEmpty(virtualCard) && (
<>
{shouldShowCardDetails ? (
{details.pan ? (
<CardDetails
// This is just a temporary mock, it will be replaced in this issue https://github.com/orgs/Expensify/projects/58?pane=issue&itemId=33286617
pan="1234123412341234"
expiration="11/02/2024"
cvv="321"
pan={details.pan}
expiration={details.expiration}
cvv={details.cvv}
privatePersonalDetails={{address: details.address}}
domain={domain}
/>
) : (
Expand All @@ -143,6 +180,8 @@ function ExpensifyCardPage({
medium
text={translate('cardPage.cardDetails.revealDetails')}
onPress={handleRevealDetails}
isDisabled={isLoading || isOffline}
isLoading={isLoading}
/>
}
/>
Expand Down
15 changes: 15 additions & 0 deletions src/types/onyx/Card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Loading