Skip to content

Commit

Permalink
Merge pull request #29573 from samh-nl/feat/issue-29320
Browse files Browse the repository at this point in the history
feat: add enable wallet button
  • Loading branch information
MariaHCD authored Oct 19, 2023
2 parents d6cde02 + d6e71ea commit 35920b6
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 37 deletions.
6 changes: 6 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,12 @@ const CONST = {
},
},

KYC_WALL_SOURCE: {
REPORT: 'REPORT', // The user attempted to pay a money request
ENABLE_WALLET: 'ENABLE_WALLET', // The user clicked on the `Enable wallet` button on the Wallet page
TRANSFER_BALANCE: 'TRANSFER_BALANCE', // The user attempted to transfer their wallet balance to their bank account or debit card
},

OS: {
WINDOWS: 'Windows',
MAC_OS: PLATFORM_OS_MACOS,
Expand Down
39 changes: 31 additions & 8 deletions src/components/KYCWall/BaseKYCWall.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class KYCWall extends React.Component {

this.continue = this.continue.bind(this);
this.setMenuPosition = this.setMenuPosition.bind(this);
this.selectPaymentMethod = this.selectPaymentMethod.bind(this);
this.anchorRef = React.createRef(null);

this.state = {
Expand All @@ -39,7 +40,6 @@ class KYCWall extends React.Component {
if (this.props.shouldListenForResize) {
this.dimensionsSubscription = Dimensions.addEventListener('change', this.setMenuPosition);
}
Wallet.setKYCWallSourceChatReportID(this.props.chatReportID);
}

componentWillUnmount() {
Expand Down Expand Up @@ -88,6 +88,18 @@ class KYCWall extends React.Component {
});
}

/**
* @param {String} paymentMethod
*/
selectPaymentMethod(paymentMethod) {
this.props.onSelectPaymentMethod(paymentMethod);
if (paymentMethod === CONST.PAYMENT_METHODS.BANK_ACCOUNT) {
Navigation.navigate(this.props.addBankAccountRoute);
} else if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) {
Navigation.navigate(this.props.addDebitCardRoute);
}
}

/**
* Take the position of the button that calls this method and show the Add Payment method menu when the user has no valid payment method.
* If they do have a valid payment method they are navigated to the "enable payments" route to complete KYC checks.
Expand All @@ -97,6 +109,14 @@ class KYCWall extends React.Component {
* @param {String} iouPaymentType
*/
continue(event, iouPaymentType) {
const currentSource = lodashGet(this.props.walletTerms, 'source', this.props.source);

/**
* Set the source, so we can tailor the process according to how we got here.
* We do not want to set this on mount, as the source can change upon completing the flow, e.g. when upgrading the wallet to Gold.
*/
Wallet.setKYCWallSource(this.props.source, this.props.chatReportID);

if (this.state.shouldShowAddPaymentMenu) {
this.setState({shouldShowAddPaymentMenu: false});
return;
Expand All @@ -111,9 +131,13 @@ class KYCWall extends React.Component {
// Check to see if user has a valid payment method on file and display the add payment popover if they don't
if (
(isExpenseReport && lodashGet(this.props.reimbursementAccount, 'achData.state', '') !== CONST.BANK_ACCOUNT.STATE.OPEN) ||
(!isExpenseReport && !PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, this.props.bankAccountList))
(!isExpenseReport && !PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, this.props.bankAccountList, this.props.shouldIncludeDebitCard))
) {
Log.info('[KYC Wallet] User does not have valid payment method');
if (!this.props.shouldIncludeDebitCard) {
this.selectPaymentMethod(CONST.PAYMENT_METHODS.BANK_ACCOUNT);
return;
}
const clickedElementLocation = getClickedTargetLocation(targetElement);
const position = this.getAnchorPosition(clickedElementLocation);
this.setPositionAddPaymentMenu(position);
Expand All @@ -132,7 +156,7 @@ class KYCWall extends React.Component {
}
}
Log.info('[KYC Wallet] User has valid payment method and passed KYC checks or did not need them');
this.props.onSuccessfulKYC(iouPaymentType);
this.props.onSuccessfulKYC(iouPaymentType, currentSource);
}

render() {
Expand All @@ -149,11 +173,7 @@ class KYCWall extends React.Component {
anchorAlignment={this.props.anchorAlignment}
onItemSelected={(item) => {
this.setState({shouldShowAddPaymentMenu: false});
if (item === CONST.PAYMENT_METHODS.BANK_ACCOUNT) {
Navigation.navigate(this.props.addBankAccountRoute);
} else if (item === CONST.PAYMENT_METHODS.DEBIT_CARD) {
Navigation.navigate(this.props.addDebitCardRoute);
}
this.selectPaymentMethod(item);
}}
/>
{this.props.children(this.continue, this.anchorRef)}
Expand All @@ -169,6 +189,9 @@ export default withOnyx({
userWallet: {
key: ONYXKEYS.USER_WALLET,
},
walletTerms: {
key: ONYXKEYS.WALLET_TERMS,
},
fundList: {
key: ONYXKEYS.FUND_LIST,
},
Expand Down
16 changes: 16 additions & 0 deletions src/components/KYCWall/kycWallPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import bankAccountPropTypes from '../bankAccountPropTypes';
import cardPropTypes from '../cardPropTypes';
import iouReportPropTypes from '../../pages/iouReportPropTypes';
import reimbursementAccountPropTypes from '../../pages/ReimbursementAccount/ReimbursementAccountDraftPropTypes';
import walletTermsPropTypes from '../../pages/EnablePayments/walletTermsPropTypes';
import CONST from '../../CONST';

const propTypes = {
Expand All @@ -26,6 +27,12 @@ const propTypes = {
/** The user's wallet */
userWallet: userWalletPropTypes,

/** Information related to the last step of the wallet activation flow */
walletTerms: walletTermsPropTypes,

/** The source that triggered the KYC wall */
source: PropTypes.oneOf(_.values(CONST.KYC_WALL_SOURCE)).isRequired,

/** When the button is opened via an IOU, ID for the chatReport that the IOU is linked to */
chatReportID: PropTypes.string,

Expand All @@ -49,10 +56,17 @@ const propTypes = {
horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
}),

/** Whether the option to add a debit card should be included */
shouldIncludeDebitCard: PropTypes.bool,

/** Callback for when a payment method has been selected */
onSelectPaymentMethod: PropTypes.func,
};

const defaultProps = {
userWallet: {},
walletTerms: {},
shouldListenForResize: false,
isDisabled: false,
chatReportID: '',
Expand All @@ -66,6 +80,8 @@ const defaultProps = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
},
shouldIncludeDebitCard: true,
onSelectPaymentMethod: () => {},
};

export {propTypes, defaultProps};
1 change: 1 addition & 0 deletions src/components/SettlementButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ function SettlementButton({
addBankAccountRoute={addBankAccountRoute}
addDebitCardRoute={addDebitCardRoute}
isDisabled={isOffline}
source={CONST.KYC_WALL_SOURCE.REPORT}
chatReportID={chatReportID}
iouReport={iouReport}
anchorAlignment={anchorAlignment}
Expand Down
4 changes: 3 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,8 @@ export default {
receiveMoney: 'Receive money in your local currency',
expensifyWallet: 'Expensify Wallet',
sendAndReceiveMoney: 'Send and receive money from your Expensify Wallet.',
enableWalletToSendAndReceiveMoney: 'Enable your Expensify Wallet to start sending and receiving money with friends!',
enableWallet: 'Enable wallet',
bankAccounts: 'Bank accounts',
addBankAccountToSendAndReceive: 'Add a bank account to send and receive payments directly in the app.',
addBankAccount: 'Add bank account',
Expand Down Expand Up @@ -1221,7 +1223,7 @@ export default {
},
additionalDetailsStep: {
headerTitle: 'Additional details',
helpText: 'We need to confirm the following information before we can process this payment.',
helpText: 'We need to confirm the following information before you can send and receive money from your Wallet.',
helpTextIdologyQuestions: 'We need to ask you just a few more questions to finish validating your identity.',
helpLink: 'Learn more about why we need this.',
legalFirstNameLabel: 'Legal first name',
Expand Down
4 changes: 3 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,8 @@ export default {
receiveMoney: 'Recibe dinero en tu moneda local',
expensifyWallet: 'Billetera Expensify',
sendAndReceiveMoney: 'Envía y recibe dinero desde tu Billetera Expensify.',
enableWalletToSendAndReceiveMoney: 'Habilita tu Billetera Expensify para comenzar a enviar y recibir dinero con amigos',
enableWallet: 'Habilitar Billetera',
bankAccounts: 'Cuentas bancarias',
addBankAccountToSendAndReceive: 'Añade una cuenta bancaria para enviar y recibir pagos directamente en la aplicación.',
addBankAccount: 'Agregar cuenta bancaria',
Expand Down Expand Up @@ -1240,7 +1242,7 @@ export default {
},
additionalDetailsStep: {
headerTitle: 'Detalles adicionales',
helpText: 'Necesitamos confirmar la siguiente información antes de que podamos procesar el pago.',
helpText: 'Necesitamos confirmar la siguiente información antes de que puedas enviar y recibir dinero desde tu Billetera.',
helpTextIdologyQuestions: 'Tenemos que preguntarte unas preguntas más para terminar de verificar tu identidad',
helpLink: 'Obtén más información sobre por qué necesitamos esto.',
legalFirstNameLabel: 'Primer nombre legal',
Expand Down
6 changes: 3 additions & 3 deletions src/libs/PaymentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ type AccountType = BankAccount['accountType'] | Fund['accountType'];
/**
* Check to see if user has either a debit card or personal bank account added
*/
function hasExpensifyPaymentMethod(fundList: Record<string, Fund>, bankAccountList: Record<string, BankAccount>): boolean {
function hasExpensifyPaymentMethod(fundList: Record<string, Fund>, bankAccountList: Record<string, BankAccount>, shouldIncludeDebitCard = true): boolean {
const validBankAccount = Object.values(bankAccountList).some((bankAccountJSON) => {
const bankAccount = new BankAccountModel(bankAccountJSON);
return bankAccount.isDefaultCredit();
return bankAccount.getPendingAction() !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && bankAccount.isDefaultCredit();
});

// Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them
const validDebitCard = Object.values(fundList).some((card) => card?.accountData?.additionalData?.isP2PDebitCard ?? false);

return validBankAccount || validDebitCard;
return validBankAccount || (shouldIncludeDebitCard && validDebitCard);
}

function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData']): string {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/BankAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function openPersonalBankAccountSetupView(exitReportID: string) {
}

/**
* Whether after adding a bank account we should continue with the KYC flow
* Whether after adding a bank account we should continue with the KYC flow. If so, we must specify the fallback route.
*/
function setPersonalBankAccountContinueKYCOnSuccess(onSuccessFallbackRoute: string) {
Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {onSuccessFallbackRoute});
Expand Down
9 changes: 5 additions & 4 deletions src/libs/actions/Wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@ function setAdditionalDetailsErrorMessage(additionalErrorMessage) {
}

/**
* Save the ID of the chat whose IOU triggered showing the KYC wall.
* Save the source that triggered the KYC wall and optionally the chat report ID associated with the IOU
*
* @param {String} source
* @param {String} chatReportID
*/
function setKYCWallSourceChatReportID(chatReportID) {
Onyx.merge(ONYXKEYS.WALLET_TERMS, {chatReportID});
function setKYCWallSource(source, chatReportID = '') {
Onyx.merge(ONYXKEYS.WALLET_TERMS, {source, chatReportID});
}

/**
Expand Down Expand Up @@ -333,5 +334,5 @@ export {
updatePersonalDetails,
verifyIdentity,
acceptWalletTerms,
setKYCWallSourceChatReportID,
setKYCWallSource,
};
8 changes: 8 additions & 0 deletions src/libs/models/BankAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ class BankAccount {
return this.json.accountData.additionalData || {};
}

/**
* Get the pending action of the bank account
* @returns {String}
*/
getPendingAction() {
return lodashGet(this.json, 'pendingAction', '');
}

/**
* Return a map needed to setup a withdrawal account
* @returns {Object}
Expand Down
6 changes: 5 additions & 1 deletion src/pages/AddPersonalBankAccountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import HeaderWithBackButton from '../components/HeaderWithBackButton';
import ScreenWrapper from '../components/ScreenWrapper';
import Navigation from '../libs/Navigation/Navigation';
import * as BankAccounts from '../libs/actions/BankAccounts';
import * as PaymentMethods from '../libs/actions/PaymentMethods';
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
import AddPlaidBankAccount from '../components/AddPlaidBankAccount';
import getPlaidOAuthReceivedRedirectURI from '../libs/getPlaidOAuthReceivedRedirectURI';
Expand All @@ -17,7 +18,6 @@ import Form from '../components/Form';
import ROUTES from '../ROUTES';
import * as PlaidDataProps from './ReimbursementAccount/plaidDataPropTypes';
import ConfirmationPage from '../components/ConfirmationPage';
import * as PaymentMethods from '../libs/actions/PaymentMethods';

const propTypes = {
...withLocalizePropTypes,
Expand All @@ -36,6 +36,9 @@ const propTypes = {
/** Any reportID we should redirect to at the end of the flow */
exitReportID: PropTypes.string,

/** Whether we should continue with KYC at the end of the flow */
shouldContinueKYCOnSuccess: PropTypes.bool,

/** Whether the form is loading */
isLoading: PropTypes.bool,

Expand All @@ -52,6 +55,7 @@ const defaultProps = {
isLoading: false,
plaidAccountID: '',
exitReportID: '',
shouldContinueKYCOnSuccess: false,
},
};

Expand Down
13 changes: 11 additions & 2 deletions src/pages/EnablePayments/ActivateStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,23 @@ const propTypes = {
const defaultProps = {
userWallet: {},
walletTerms: {
source: '',
chatReportID: 0,
},
};

function ActivateStep(props) {
const isActivatedWallet = _.contains([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM], props.userWallet.tierName);
const animation = isActivatedWallet ? LottieAnimations.Fireworks : LottieAnimations.ReviewingBankInfo;
const continueButtonText = props.walletTerms.chatReportID ? props.translate('activateStep.continueToPayment') : props.translate('activateStep.continueToTransfer');
let continueButtonText = '';

if (props.walletTerms.chatReportID) {
continueButtonText = props.translate('activateStep.continueToPayment');
} else if (props.walletTerms.source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET) {
continueButtonText = props.translate('common.continue');
} else {
continueButtonText = props.translate('activateStep.continueToTransfer');
}

return (
<>
Expand All @@ -43,7 +52,7 @@ function ActivateStep(props) {
description={props.translate(`activateStep.${isActivatedWallet ? 'activated' : 'checkBackLater'}Message`)}
shouldShowButton={isActivatedWallet}
buttonText={continueButtonText}
onButtonPress={PaymentMethods.continueSetup}
onButtonPress={() => PaymentMethods.continueSetup()}
/>
</>
);
Expand Down
5 changes: 5 additions & 0 deletions src/pages/EnablePayments/walletTermsPropTypes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import PropTypes from 'prop-types';
import _ from 'underscore';
import CONST from '../../CONST';

/** Prop types related to the Terms step of KYC flow */
export default PropTypes.shape({
/** Any error message to show */
errors: PropTypes.objectOf(PropTypes.string),

/** The source that triggered the KYC wall */
source: PropTypes.oneOf(_.values(CONST.KYC_WALL_SOURCE)),

/** When the user accepts the Wallet's terms in order to pay an IOU, this is the ID of the chatReport the IOU is linked to */
chatReportID: PropTypes.string,
});
Loading

0 comments on commit 35920b6

Please sign in to comment.