diff --git a/src/CONST.js b/src/CONST.js
index 83922e707316..78e1ac329947 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -432,6 +432,12 @@ const CONST = {
OTHER: 'other',
},
+ PAYMENT_METHODS: {
+ PAYPAL: 'payPalMe',
+ DEBIT_CARD: 'debitCard',
+ BANK_ACCOUNT: 'bankAccount',
+ },
+
IOU: {
// Note: These payment types are used when building IOU reportAction message values in the server and should
// not be changed.
diff --git a/src/ROUTES.js b/src/ROUTES.js
index f3b856a469de..463ecdd1126f 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -29,6 +29,7 @@ export default {
SETTINGS_PAYMENTS: 'settings/payments',
SETTINGS_ADD_PAYPAL_ME: 'settings/payments/add-paypal-me',
SETTINGS_ADD_DEBIT_CARD: 'settings/payments/add-debit-card',
+ SETTINGS_ADD_BANK_ACCOUNT: 'settings/payments/add-bank-account',
SETTINGS_ADD_LOGIN: 'settings/addlogin/:type',
getSettingsAddLoginRoute: type => `settings/addlogin/${type}`,
NEW_GROUP: 'new/group',
diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js
new file mode 100644
index 000000000000..5e77e2fd47b8
--- /dev/null
+++ b/src/components/AddPaymentMethodMenu.js
@@ -0,0 +1,71 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
+import Popover from './Popover';
+import MenuItem from './MenuItem';
+import * as Expensicons from './Icon/Expensicons';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import styles from '../styles/styles';
+import compose from '../libs/compose';
+import ONYXKEYS from '../ONYXKEYS';
+import CONST from '../CONST';
+
+const propTypes = {
+ isVisible: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ anchorPosition: PropTypes.shape({
+ top: PropTypes.number,
+ left: PropTypes.number,
+ }),
+
+ /** Username for PayPal.Me */
+ payPalMeUsername: PropTypes.string,
+
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ anchorPosition: {},
+ payPalMeUsername: '',
+};
+
+const AddPaymentMethodMenu = props => (
+
+
+);
+
+AddPaymentMethodMenu.propTypes = propTypes;
+AddPaymentMethodMenu.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withOnyx({
+ payPalMeUsername: {
+ key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS,
+ },
+ }),
+)(AddPaymentMethodMenu);
diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js
index abd2f6b5b8cb..2950755bd77e 100644
--- a/src/components/AddPlaidBankAccount.js
+++ b/src/components/AddPlaidBankAccount.js
@@ -12,6 +12,7 @@ import PlaidLink from './PlaidLink';
import * as BankAccounts from '../libs/actions/BankAccounts';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
+import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus';
import themeColors from '../styles/themes/default';
import compose from '../libs/compose';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
@@ -21,10 +22,9 @@ import * as ReimbursementAccountUtils from '../libs/ReimbursementAccountUtils';
import ReimbursementAccountForm from '../pages/ReimbursementAccount/ReimbursementAccountForm';
import getBankIcon from './Icon/BankIcons';
import Icon from './Icon';
+import ExpensiTextInput from './ExpensiTextInput';
const propTypes = {
- ...withLocalizePropTypes,
-
/** Plaid SDK token to use to initialize the widget */
plaidLinkToken: PropTypes.string,
@@ -78,6 +78,11 @@ const propTypes = {
/** During the OAuth flow we need to use the plaidLink token that we initially connected with */
plaidLinkOAuthToken: PropTypes.string,
+
+ /** Should we require a password to create a bank account? */
+ isPasswordRequired: PropTypes.bool,
+
+ ...withLocalizePropTypes,
};
const defaultProps = {
@@ -90,6 +95,7 @@ const defaultProps = {
text: '',
receivedRedirectURI: null,
plaidLinkOAuthToken: '',
+ isPasswordRequired: false,
};
class AddPlaidBankAccount extends React.Component {
@@ -102,10 +108,14 @@ class AddPlaidBankAccount extends React.Component {
this.state = {
selectedIndex: undefined,
institution: {},
+ password: '',
};
this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props);
this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey);
+ this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, {
+ password: 'passwordForm.error.incorrectLoginOrPassword',
+ }, inputKey);
}
componentDidMount() {
@@ -119,6 +129,10 @@ class AddPlaidBankAccount extends React.Component {
BankAccounts.fetchPlaidLinkToken();
}
+ componentWillUnmount() {
+ BankAccounts.setBankAccountFormValidationErrors({});
+ }
+
/**
* Get list of bank accounts
*
@@ -149,6 +163,11 @@ class AddPlaidBankAccount extends React.Component {
if (_.isUndefined(this.state.selectedIndex)) {
errors.selectedBank = true;
}
+
+ if (this.props.isPasswordRequired && _.isEmpty(this.state.password)) {
+ errors.password = true;
+ }
+
BankAccounts.setBankAccountFormValidationErrors(errors);
return _.size(errors) === 0;
}
@@ -165,6 +184,7 @@ class AddPlaidBankAccount extends React.Component {
bankName,
account,
plaidLinkToken: this.getPlaidLinkToken(),
+ password: this.state.password,
});
}
@@ -233,6 +253,22 @@ class AddPlaidBankAccount extends React.Component {
hasError={this.getErrors().selectedBank}
/>
+ {!_.isUndefined(this.state.selectedIndex) && this.props.isPasswordRequired && (
+
+ this.setState({password: text})}
+ errorText={this.getErrorText('password')}
+ hasError={this.getErrors().password}
+ />
+
+ )}
)}
>
diff --git a/src/languages/en.js b/src/languages/en.js
index 85f944a7380d..94a88cfc9599 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -89,6 +89,7 @@ export default {
more: 'More',
debitCard: 'Debit card',
payPalMe: 'PayPal.me',
+ bankAccount: 'Bank account',
},
attachmentPicker: {
cameraPermissionRequired: 'Camera permission required',
@@ -475,6 +476,7 @@ export default {
},
},
addPersonalBankAccountPage: {
+ enterPassword: 'Enter Expensify password',
alreadyAdded: 'This account has already been added.',
chooseAccountLabel: 'Account',
},
diff --git a/src/languages/es.js b/src/languages/es.js
index db90ccfbec0c..b319106a03bd 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -89,6 +89,7 @@ export default {
more: 'Más',
debitCard: 'Tarjeta de débito',
payPalMe: 'PayPal.me',
+ bankAccount: 'Cuenta bancaria',
},
attachmentPicker: {
cameraPermissionRequired: 'Se necesita permiso para usar la cámara',
@@ -475,6 +476,7 @@ export default {
},
},
addPersonalBankAccountPage: {
+ enterPassword: 'Escribe tu contraseña de Expensify',
alreadyAdded: 'Esta cuenta ya ha sido agregada.',
chooseAccountLabel: 'Cuenta',
},
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 4b998a632ee4..c8d5677ea301 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -182,6 +182,10 @@ const SettingsModalStackNavigator = createModalStackNavigator([
Component: SettingsAddDebitCardPage,
name: 'Settings_Add_Debit_Card',
},
+ {
+ Component: AddPersonalBankAccountPage,
+ name: 'Settings_Add_Bank_Account',
+ },
{
Component: WorkspaceInitialPage,
name: 'Workspace_Initial',
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 4e89c80ec813..be838f7391c3 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -56,6 +56,10 @@ export default {
path: ROUTES.SETTINGS_ADD_DEBIT_CARD,
exact: true,
},
+ Settings_Add_Bank_Account: {
+ path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT,
+ exact: true,
+ },
Settings_Profile: {
path: ROUTES.SETTINGS_PROFILE,
exact: true,
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index 587b33c94ae2..245c4fb02a83 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -1,7 +1,12 @@
import _ from 'underscore';
+import Onyx from 'react-native-onyx';
import CONST from '../../CONST';
import * as API from '../API';
import * as Plaid from './Plaid';
+import * as ReimbursementAccount from './ReimbursementAccount';
+import Navigation from '../Navigation/Navigation';
+import ONYXKEYS from '../../ONYXKEYS';
+import * as PaymentMethods from './PaymentMethods';
export {
setupWithdrawalAccount,
@@ -41,6 +46,7 @@ export {
* @param {String} plaidLinkToken
*/
function addPersonalBankAccount(account, password, plaidLinkToken) {
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
const unmaskedAccount = _.find(Plaid.getPlaidBankAccounts(), bankAccount => (
bankAccount.plaidAccountID === account.plaidAccountID
));
@@ -71,12 +77,22 @@ function addPersonalBankAccount(account, password, plaidLinkToken) {
}),
})
.then((response) => {
- if (response.jsonCode !== 200) {
- alert('There was a problem adding this bank account.');
+ if (response.jsonCode === 200) {
+ PaymentMethods.getPaymentMethods()
+ .then(() => {
+ Navigation.goBack();
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
+ });
return;
}
- alert('Bank account added successfully.');
+ if (response.message === 'Incorrect Expensify password entered') {
+ ReimbursementAccount.setBankAccountFormValidationErrors({password: true});
+ }
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
+ })
+ .catch(() => {
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
});
}
diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js
index 0736f19bca85..187b65ee696f 100644
--- a/src/pages/AddPersonalBankAccountPage.js
+++ b/src/pages/AddPersonalBankAccountPage.js
@@ -27,6 +27,8 @@ const AddPersonalBankAccountPage = props => (
Navigation.goBack()}
/>
{
@@ -35,6 +37,7 @@ const AddPersonalBankAccountPage = props => (
onExitPlaid={Navigation.dismissModal}
receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()}
plaidLinkOAuthToken={props.plaidLinkToken}
+ isPasswordRequired
/>
);
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js
index e31669167471..ea4d2ef7c519 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js
@@ -61,6 +61,7 @@ class ReimbursementAccountForm extends React.Component {
}}
message={this.props.reimbursementAccount.errorModalMessage}
isMessageHtml={this.props.reimbursementAccount.isErrorModalMessageHtml}
+ isLoading={this.props.reimbursementAccount.loading}
/>
);
diff --git a/src/pages/settings/Payments/PaymentsPage.js b/src/pages/settings/Payments/PaymentsPage.js
index d686c3163662..a49b2ada357f 100644
--- a/src/pages/settings/Payments/PaymentsPage.js
+++ b/src/pages/settings/Payments/PaymentsPage.js
@@ -13,16 +13,12 @@ import compose from '../../../libs/compose';
import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView/index';
import ExpensifyText from '../../../components/ExpensifyText';
import * as PaymentMethods from '../../../libs/actions/PaymentMethods';
-import Popover from '../../../components/Popover';
-import * as Expensicons from '../../../components/Icon/Expensicons';
-import MenuItem from '../../../components/MenuItem';
import getClickedElementLocation from '../../../libs/getClickedElementLocation';
import CurrentWalletBalance from '../../../components/CurrentWalletBalance';
import ONYXKEYS from '../../../ONYXKEYS';
import Permissions from '../../../libs/Permissions';
-
-const PAYPAL = 'payPalMe';
-const DEBIT_CARD = 'debitCard';
+import AddPaymentMethodMenu from '../../../components/AddPaymentMethodMenu';
+import CONST from '../../../CONST';
const propTypes = {
...withLocalizePropTypes,
@@ -32,15 +28,11 @@ const propTypes = {
/** Are we loading payment methods? */
isLoadingPaymentMethods: PropTypes.bool,
-
- /** Username for PayPal.Me */
- payPalMeUsername: PropTypes.string,
};
const defaultProps = {
betas: [],
isLoadingPaymentMethods: true,
- payPalMeUsername: '',
};
class PaymentsPage extends React.Component {
@@ -70,7 +62,7 @@ class PaymentsPage extends React.Component {
*/
paymentMethodPressed(nativeEvent, account) {
if (account) {
- if (account === PAYPAL) {
+ if (account === CONST.PAYMENT_METHODS.PAYPAL) {
Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME);
}
} else {
@@ -93,13 +85,22 @@ class PaymentsPage extends React.Component {
addPaymentMethodTypePressed(paymentType) {
this.hideAddPaymentMenu();
- if (paymentType === PAYPAL) {
+ if (paymentType === CONST.PAYMENT_METHODS.PAYPAL) {
Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME);
+ return;
}
- if (paymentType === DEBIT_CARD) {
+ if (paymentType === CONST.PAYMENT_METHODS.DEBIT_CARD) {
Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD);
+ return;
+ }
+
+ if (paymentType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) {
+ Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT);
+ return;
}
+
+ throw new Error('Invalid payment method type selected');
}
/**
@@ -135,29 +136,15 @@ class PaymentsPage extends React.Component {
isAddPaymentMenuActive={this.state.shouldShowAddPaymentMenu}
/>
-
- {!this.props.payPalMeUsername && (
- this.addPaymentMethodTypePressed(PAYPAL)}
- wrapperStyle={styles.pr15}
- />
- )}
- this.addPaymentMethodTypePressed(DEBIT_CARD)}
- wrapperStyle={styles.pr15}
- />
-
+ onItemSelected={method => this.addPaymentMethodTypePressed(method)}
+ />
);
@@ -177,8 +164,5 @@ export default compose(
key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS,
initWithStoredValues: false,
},
- payPalMeUsername: {
- key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS,
- },
}),
)(PaymentsPage);