diff --git a/src/components/ConfirmPopover.js b/src/components/ConfirmPopover.js new file mode 100644 index 0000000000000..d5efa0b407e89 --- /dev/null +++ b/src/components/ConfirmPopover.js @@ -0,0 +1,116 @@ +import React from 'react'; +import {View, TouchableOpacity} from 'react-native'; +import PropTypes from 'prop-types'; +import styles from '../styles/styles'; +import Popover from './Popover'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import compose from '../libs/compose'; +import Text from './Text'; + +const propTypes = { + /** Title of the modal */ + title: PropTypes.string.isRequired, + + /** A callback to call when the form has been submitted */ + onConfirm: PropTypes.func.isRequired, + + /** A callback to call when the form has been closed */ + onCancel: PropTypes.func, + + /** Modal visibility */ + isVisible: PropTypes.bool.isRequired, + + /** Confirm button text */ + confirmText: PropTypes.string, + + /** Cancel button text */ + cancelText: PropTypes.string, + + /** Is the action destructive */ + danger: PropTypes.bool, + + /** Whether we should show the cancel button */ + shouldShowCancelButton: PropTypes.bool, + + /** Where the popover should be positioned */ + anchorPosition: PropTypes.shape({ + top: PropTypes.number, + left: PropTypes.number, + }).isRequired, + + ...withLocalizePropTypes, + + ...windowDimensionsPropTypes, +}; + +const defaultProps = { + confirmText: '', + cancelText: '', + danger: false, + onCancel: () => {}, + shouldShowCancelButton: true, +}; + +const ConfirmPopover = props => ( + + + + {props.title} + + + + {props.confirmText || props.translate('common.yes')} + + + {props.shouldShowCancelButton + && ( + + + {props.cancelText || props.translate('common.no')} + + + )} + + +); + +ConfirmPopover.propTypes = propTypes; +ConfirmPopover.defaultProps = defaultProps; +ConfirmPopover.displayName = 'ConfirmPopover'; +export default compose( + withWindowDimensions, + withLocalize, +)(ConfirmPopover); diff --git a/src/components/PasswordPopover.js b/src/components/PasswordPopover.js new file mode 100644 index 0000000000000..a972521306819 --- /dev/null +++ b/src/components/PasswordPopover.js @@ -0,0 +1,119 @@ +import {TouchableOpacity, View} from 'react-native'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import styles from '../styles/styles'; +import Text from './Text'; +import Popover from './Popover'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import compose from '../libs/compose'; +import withWindowDimensions from './withWindowDimensions'; +import TextInput from './TextInput'; + +const propTypes = { + /** Is the popover currently showing? */ + isVisible: PropTypes.bool.isRequired, + + /** Function that gets called when the user closes the modal */ + onClose: PropTypes.func.isRequired, + + /** Where the popover should be placed */ + anchorPosition: PropTypes.shape({ + top: PropTypes.number, + left: PropTypes.number, + }).isRequired, + + /** Function that gets called when the user clicks the delete / make default button */ + onSubmit: PropTypes.func, + + /** The text that should be displayed in the submit button */ + submitButtonText: PropTypes.string, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + onSubmit: () => {}, + submitButtonText: '', +}; + +class PasswordPopover extends Component { + constructor(props) { + super(props); + + this.passwordInput = undefined; + + this.focusInput = this.focusInput.bind(this); + + this.state = { + password: '', + }; + } + + /** + * Focus the password input + */ + focusInput() { + if (!this.passwordInput) { + return; + } + this.passwordInput.focus(); + } + + render() { + return ( + + + + {this.props.translate('passwordForm.pleaseFillPassword')} + + this.passwordInput = el} + secureTextEntry + autoCompleteType="password" + textContentType="password" + value={this.state.currentPassword} + onChangeText={password => this.setState({password})} + returnKeyType="done" + onSubmitEditing={() => this.props.onSubmit(this.state.password)} + style={styles.mt3} + autoFocus + /> + this.props.onSubmit(this.state.password)} + style={[ + styles.button, + styles.mt3, + styles.w100, + ]} + > + + {this.props.submitButtonText} + + + + + ); + } +} + +PasswordPopover.propTypes = propTypes; +PasswordPopover.defaultProps = defaultProps; +export default compose( + withWindowDimensions, + withLocalize, +)(PasswordPopover); diff --git a/src/languages/en.js b/src/languages/en.js index e95934194aa61..a5a5ddd98f754 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -349,6 +349,13 @@ export default { }, paymentsPage: { paymentMethodsTitle: 'Payment methods', + setDefaultConfirmation: 'Make default payment method', + setDefaultSuccess: 'Default payment method set!', + setDefaultFailure: 'Failed to set default payment method.', + deleteConfirmation: 'Are you sure that you want to delete this account?', + deleteBankAccountSuccess: 'Bank account successfully deleted', + deleteDebitCardSuccess: 'Debit Card successfully deleted', + deletePayPalSuccess: 'PayPal.me successfully deleted', allSet: 'All Set!', transferConfirmText: ({amount}) => `${amount} will hit your account shortly!`, gotIt: 'Got it, Thanks!', @@ -373,6 +380,7 @@ export default { accountLastFour: 'Account ending in', cardLastFour: 'Card ending in', addFirstPaymentMethod: 'Add a payment method to send and receive payments directly in the app.', + defaultPaymentMethod: 'Default', }, preferencesPage: { mostRecent: 'Most recent', diff --git a/src/languages/es.js b/src/languages/es.js index df211fbb78926..9900c3863d2dd 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -349,6 +349,13 @@ export default { }, paymentsPage: { paymentMethodsTitle: 'Métodos de pago', + setDefaultConfirmation: 'Marcar como método de pago predeterminado', + setDefaultSuccess: 'Método de pago configurado', + setDefaultFailure: 'No se ha podido configurar el método de pago.', + deleteConfirmation: '¿Estás seguro de que quieres eliminar esta cuenta?', + deleteBankAccountSuccess: 'Cuenta bancaria eliminada correctamente', + deleteDebitCardSuccess: 'Tarjeta de débito eliminada correctamente', + deletePayPalSuccess: 'PayPal.me eliminada correctamente', allSet: 'Todo listo!', transferConfirmText: ({amount}) => `${amount} llegará a tu cuenta en breve!`, gotIt: 'Gracias!', @@ -373,6 +380,7 @@ export default { accountLastFour: 'Cuenta con terminación', cardLastFour: 'Tarjeta con terminacíon', addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación.', + defaultPaymentMethod: 'Predeterminado', }, preferencesPage: { mostRecent: 'Más recientes', diff --git a/src/libs/API.js b/src/libs/API.js index b1fb0288644a0..1279bb289d01e 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -418,6 +418,17 @@ function CreateLogin(parameters) { return Network.post(commandName, parameters); } +/** + * @param {Object} parameters + * @param {Number} parameters.fundID + * @returns {Promise} + */ +function DeleteFund(parameters) { + const commandName = 'DeleteFund'; + requireParameters(['fundID'], parameters, commandName); + return Network.post(commandName, parameters); +} + /** * @param {Object} parameters * @param {String} parameters.partnerUserID @@ -739,6 +750,19 @@ function SetPassword(parameters) { return Network.post(commandName, parameters); } +/** + * @param {Object} parameters + * @param {String} parameters.password + * @param {String|null} parameters.bankAccountID + * @param {String|null} parameters.fundID + * @returns {Promise} + */ +function SetWalletLinkedAccount(parameters) { + const commandName = 'SetWalletLinkedAccount'; + requireParameters(['password'], parameters, commandName); + return Network.post(commandName, parameters); +} + /** * @param {Object} parameters * @param {String} parameters.subscribed @@ -1007,7 +1031,7 @@ function BankAccount_SetupWithdrawal(parameters) { */ function DeleteBankAccount(parameters) { const commandName = 'DeleteBankAccount'; - requireParameters(['bankAccountID', 'ownerEmail'], parameters, commandName); + requireParameters(['bankAccountID'], parameters, commandName); return Network.post(commandName, parameters); } @@ -1171,6 +1195,7 @@ export { CreateChatReport, CreateLogin, CreatePolicyRoom, + DeleteFund, DeleteLogin, DeleteBankAccount, Get, @@ -1202,6 +1227,7 @@ export { ResetPassword, SetNameValuePair, SetPassword, + SetWalletLinkedAccount, UpdateAccount, UpdatePolicy, User_SignUp, diff --git a/src/libs/PaymentUtils.js b/src/libs/PaymentUtils.js index 3950a6a3214b4..f80ae10a13be2 100644 --- a/src/libs/PaymentUtils.js +++ b/src/libs/PaymentUtils.js @@ -25,9 +25,10 @@ function hasExpensifyPaymentMethod(cardList = [], bankAccountList = []) { * @param {Array} bankAccountList * @param {Array} cardList * @param {String} [payPalMeUsername=''] - * @returns {Array} + * @param {Object} userWallet + * @returns {Array} */ -function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') { +function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '', userWallet) { const combinedPaymentMethods = []; _.each(bankAccountList, (bankAccount) => { @@ -40,6 +41,7 @@ function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') ? `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${bankAccount.accountNumber.slice(-4) }` : null; + const isDefault = userWallet.walletLinkedAccountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && userWallet.walletLinkedAccountID === bankAccount.bankAccountID; const {icon, iconSize} = getBankIcon(lodashGet(bankAccount, 'additionalData.bankName', '')); combinedPaymentMethods.push({ title: bankAccount.addressName, @@ -48,6 +50,9 @@ function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') icon, iconSize, key: `bankAccount-${bankAccount.bankAccountID}`, + accountType: CONST.PAYMENT_METHODS.BANK_ACCOUNT, + accountData: _.extend(bankAccount, {icon}), + isDefault, }); }); @@ -55,6 +60,7 @@ function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') const formattedCardNumber = card.cardNumber ? `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${card.cardNumber.slice(-4)}` : null; + const isDefault = userWallet.walletLinkedAccountType === CONST.PAYMENT_METHODS.DEBIT_CARD && userWallet.walletLinkedAccountID === card.fundID; const {icon, iconSize} = getBankIcon(card.bank, true); combinedPaymentMethods.push({ title: card.addressName, @@ -63,6 +69,9 @@ function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') icon, iconSize, key: `card-${card.cardNumber}`, + accountType: CONST.PAYMENT_METHODS.DEBIT_CARD, + accountData: _.extend(card, {icon}), + isDefault, }); }); @@ -73,6 +82,11 @@ function formatPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') description: payPalMeUsername, icon: Expensicons.PayPal, key: 'payPalMePaymentMethod', + accountType: CONST.PAYMENT_METHODS.PAYPAL, + accountData: { + username: payPalMeUsername, + icon: Expensicons.PayPal, + }, }); } diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index ac0d58fb68f1d..64324ca151678 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -6,6 +6,8 @@ import * as Plaid from './Plaid'; import * as ReimbursementAccount from './ReimbursementAccount'; import ONYXKEYS from '../../ONYXKEYS'; import * as PaymentMethods from './PaymentMethods'; +import Growl from '../Growl'; +import * as Localize from '../Localize'; export { setupWithdrawalAccount, @@ -95,6 +97,27 @@ function addPersonalBankAccount(account, password, plaidLinkToken) { }); } +/** + * Deletes a bank account + * + * @param {Number} bankAccountID + */ +function deleteBankAccount(bankAccountID) { + API.DeleteBankAccount({ + bankAccountID, + }).then((response) => { + if (response.jsonCode === 200) { + Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, {[bankAccountID]: null}); + Growl.show(Localize.translateLocal('paymentsPage.deleteBankAccountSuccess'), CONST.GROWL.SUCCESS, 3000); + } else { + Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000); + } + }).catch(() => { + Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000); + }); +} + export { addPersonalBankAccount, + deleteBankAccount, }; diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js index 38735599f8827..dd0dedb24a078 100644 --- a/src/libs/actions/PaymentMethods.js +++ b/src/libs/actions/PaymentMethods.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import {createRef} from 'react'; import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; @@ -9,6 +10,35 @@ import * as Localize from '../Localize'; import Navigation from '../Navigation/Navigation'; import * as CardUtils from '../CardUtils'; import ROUTES from '../../ROUTES'; +import NameValuePair from './NameValuePair'; + +/** + * Deletes a debit card + * + * @param {Number} fundID + * + * @returns {Promise} + */ +function deleteDebitCard(fundID) { + return API.DeleteFund({fundID}) + .then((response) => { + if (response.jsonCode === 200) { + Growl.show(Localize.translateLocal('paymentsPage.deleteDebitCardSuccess'), CONST.GROWL.SUCCESS, 3000); + Onyx.merge(ONYXKEYS.CARD_LIST, {[fundID]: null}); + } else { + Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000); + } + }) + .catch(() => { + Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000); + }); +} + +function deletePayPalMe() { + NameValuePair.set(CONST.NVP.PAYPAL_ME_ADDRESS, ''); + Onyx.set(ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, null); + Growl.show(Localize.translateLocal('paymentsPage.deletePayPalSuccess'), CONST.GROWL.SUCCESS, 3000); +} /** * Sets up a ref to an instance of the KYC Wall component. @@ -44,17 +74,50 @@ function getPaymentMethods() { excludeNotActivated: true, }) .then((response) => { + // Convert bank accounts/cards from an array of objects, to a map with the bankAccountID as the key + const bankAccounts = _.object(_.map(lodashGet(response, 'bankAccountList', []), bankAccount => [bankAccount.bankAccountID, bankAccount])); + const debitCards = _.object(_.map(lodashGet(response, 'fundList', []), fund => [fund.fundID, fund])); Onyx.multiSet({ [ONYXKEYS.IS_LOADING_PAYMENT_METHODS]: false, [ONYXKEYS.USER_WALLET]: lodashGet(response, 'userWallet', {}), - [ONYXKEYS.BANK_ACCOUNT_LIST]: lodashGet(response, 'bankAccountList', []), - [ONYXKEYS.CARD_LIST]: lodashGet(response, 'fundList', []), + [ONYXKEYS.BANK_ACCOUNT_LIST]: bankAccounts, + [ONYXKEYS.CARD_LIST]: debitCards, [ONYXKEYS.NVP_PAYPAL_ME_ADDRESS]: lodashGet(response, ['nameValuePairs', CONST.NVP.PAYPAL_ME_ADDRESS], ''), }); }); } +/** + * Sets the default bank account or debit card for an Expensify Wallet + * + * @param {String} password + * @param {Number} bankAccountID + * @param {Number} fundID + * + * @returns {Promise} + */ +function setWalletLinkedAccount(password, bankAccountID, fundID) { + return API.SetWalletLinkedAccount({ + password, + bankAccountID, + fundID, + }) + .then((response) => { + if (response.jsonCode === 200) { + Onyx.merge(ONYXKEYS.USER_WALLET, { + walletLinkedAccountID: bankAccountID || fundID, walletLinkedAccountType: bankAccountID ? CONST.PAYMENT_METHODS.BANK_ACCOUNT : CONST.PAYMENT_METHODS.DEBIT_CARD, + }); + Growl.show(Localize.translateLocal('paymentsPage.setDefaultSuccess'), CONST.GROWL.SUCCESS, 5000); + } else { + Growl.show(Localize.translateLocal('paymentsPage.setDefaultFailure'), CONST.GROWL.ERROR, 5000); + } + }) + .catch(() => { + Growl.show(Localize.translateLocal('paymentsPage.setDefaultFailure'), CONST.GROWL.ERROR, 5000); + }); +} + /** * Calls the API to add a new card. * @@ -169,7 +232,10 @@ function dismissWalletConfirmModal() { } export { + deleteDebitCard, + deletePayPalMe, getPaymentMethods, + setWalletLinkedAccount, addBillingCard, kycWallRef, continueSetup, diff --git a/src/pages/settings/Payments/PaymentMethodList.js b/src/pages/settings/Payments/PaymentMethodList.js index 1e2c3b12424b7..245fadbe689ae 100644 --- a/src/pages/settings/Payments/PaymentMethodList.js +++ b/src/pages/settings/Payments/PaymentMethodList.js @@ -31,10 +31,10 @@ const propTypes = { payPalMeUsername: PropTypes.string, /** Array of bank account objects */ - bankAccountList: PropTypes.arrayOf(bankAccountPropTypes), + bankAccountList: PropTypes.objectOf(bankAccountPropTypes), /** Array of card objects */ - cardList: PropTypes.arrayOf(PropTypes.shape({ + cardList: PropTypes.objectOf(PropTypes.shape({ /** The name of the institution (bank of america, etc */ cardName: PropTypes.string, @@ -45,6 +45,14 @@ const propTypes = { cardID: PropTypes.number, })), + userWallet: PropTypes.shape({ + /** The ID of the linked account */ + walletLinkedAccountID: PropTypes.number, + + /** The type of the linked account (debitCard or bankAccount) */ + walletLinkedAccountType: PropTypes.string, + }), + ...withLocalizePropTypes, }; @@ -52,6 +60,10 @@ const defaultProps = { payPalMeUsername: '', bankAccountList: [], cardList: [], + userWallet: { + walletLinkedAccountID: 0, + walletLinkedAccountType: '', + }, isLoadingPayments: false, isAddPaymentMenuActive: false, }; @@ -69,11 +81,11 @@ class PaymentMethodList extends Component { * @returns {Array} */ createPaymentMethodList() { - let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(this.props.bankAccountList, this.props.cardList, this.props.payPalMeUsername); + let combinedPaymentMethods = PaymentUtils.formatPaymentMethods(this.props.bankAccountList, this.props.cardList, this.props.payPalMeUsername, this.props.userWallet); combinedPaymentMethods = _.map(combinedPaymentMethods, paymentMethod => ({ ...paymentMethod, type: MENU_ITEM, - onPress: e => this.props.onPress(e, paymentMethod.methodID), + onPress: e => this.props.onPress(e, paymentMethod.accountType, paymentMethod.accountData), })); // If we have not added any payment methods, show a default empty state @@ -118,6 +130,7 @@ class PaymentMethodList extends Component { iconFill={item.iconFill} iconHeight={item.iconSize} iconWidth={item.iconSize} + badgeText={item.isDefault ? this.props.translate('paymentMethodList.defaultPaymentMethod') : null} wrapperStyle={item.wrapperStyle} /> ); @@ -157,5 +170,8 @@ export default compose( payPalMeUsername: { key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, }), )(PaymentMethodList); diff --git a/src/pages/settings/Payments/PaymentsPage.js b/src/pages/settings/Payments/PaymentsPage.js index 3279ce0adb720..4bddd934b327a 100644 --- a/src/pages/settings/Payments/PaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage.js @@ -1,26 +1,31 @@ import React from 'react'; -import {View} from 'react-native'; +import {View, TouchableOpacity} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import PaymentMethodList from './PaymentMethodList'; import ROUTES from '../../../ROUTES'; import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; +import PasswordPopover from '../../../components/PasswordPopover'; import ScreenWrapper from '../../../components/ScreenWrapper'; import Navigation from '../../../libs/Navigation/Navigation'; import styles from '../../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import compose from '../../../libs/compose'; import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView/index'; +import * as BankAccounts from '../../../libs/actions/BankAccounts'; +import Popover from '../../../components/Popover'; +import MenuItem from '../../../components/MenuItem'; import Text from '../../../components/Text'; import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; import getClickedElementLocation from '../../../libs/getClickedElementLocation'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import CurrentWalletBalance from '../../../components/CurrentWalletBalance'; import ONYXKEYS from '../../../ONYXKEYS'; import Permissions from '../../../libs/Permissions'; +import ConfirmPopover from '../../../components/ConfirmPopover'; import AddPaymentMethodMenu from '../../../components/AddPaymentMethodMenu'; import CONST from '../../../CONST'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import MenuItem from '../../../components/MenuItem'; import walletTransferPropTypes from './walletTransferPropTypes'; import ConfirmModal from '../../../components/ConfirmModal'; @@ -35,6 +40,8 @@ const propTypes = { isLoadingPaymentMethods: PropTypes.bool, ...withLocalizePropTypes, + + ...windowDimensionsPropTypes, }; const defaultProps = { @@ -51,6 +58,11 @@ class PaymentsPage extends React.Component { this.state = { shouldShowAddPaymentMenu: false, + shouldShowDefaultDeleteMenu: false, + shouldShowPasswordPrompt: false, + shouldShowConfirmPopover: false, + selectedPaymentMethod: {}, + formattedSelectedPaymentMethod: {}, anchorPositionTop: 0, anchorPositionLeft: 0, }; @@ -58,6 +70,10 @@ class PaymentsPage extends React.Component { this.paymentMethodPressed = this.paymentMethodPressed.bind(this); this.addPaymentMethodTypePressed = this.addPaymentMethodTypePressed.bind(this); this.hideAddPaymentMenu = this.hideAddPaymentMenu.bind(this); + this.hideDefaultDeleteMenu = this.hideDefaultDeleteMenu.bind(this); + this.makeDefaultPaymentMethod = this.makeDefaultPaymentMethod.bind(this); + this.deletePaymentMethod = this.deletePaymentMethod.bind(this); + this.hidePasswordPrompt = this.hidePasswordPrompt.bind(this); this.navigateToTransferBalancePage = this.navigateToTransferBalancePage.bind(this); } @@ -69,21 +85,52 @@ class PaymentsPage extends React.Component { * Display the delete/default menu, or the add payment method menu * * @param {Object} nativeEvent + * @param {String} accountType * @param {String} account */ - paymentMethodPressed(nativeEvent, account) { - if (account) { - if (account === CONST.PAYMENT_METHODS.PAYPAL) { - Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME); + paymentMethodPressed(nativeEvent, accountType, account) { + const position = getClickedElementLocation(nativeEvent); + if (accountType) { + let formattedSelectedPaymentMethod; + if (accountType === CONST.PAYMENT_METHODS.PAYPAL) { + formattedSelectedPaymentMethod = { + title: 'PayPal.me', + icon: account.icon, + description: account.username, + type: CONST.PAYMENT_METHODS.PAYPAL, + }; + } else if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + formattedSelectedPaymentMethod = { + title: account.addressName, + icon: account.icon, + description: `${this.props.translate('paymentMethodList.accountLastFour')} ${account.accountNumber.slice(-4)}`, + type: CONST.PAYMENT_METHODS.BANK_ACCOUNT, + }; + } else if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + formattedSelectedPaymentMethod = { + title: account.addressName, + icon: account.icon, + description: `${this.props.translate('paymentMethodList.cardLastFour')} ${account.cardNumber.slice(-4)}`, + type: CONST.PAYMENT_METHODS.DEBIT_CARD, + }; } + this.setState({ + shouldShowDefaultDeleteMenu: true, + selectedPaymentMethod: account, + selectedPaymentMethodType: accountType, + anchorPositionTop: position.bottom, + + // We want the position to be 13px to the right of the left border + anchorPositionLeft: position.left + 13, + formattedSelectedPaymentMethod, + }); } else { - const position = getClickedElementLocation(nativeEvent); this.setState({ shouldShowAddPaymentMenu: true, anchorPositionTop: position.bottom, - // We want the position to be 20px to the right of the left border - anchorPositionLeft: position.left + 20, + // We want the position to be 13px to the right of the left border + anchorPositionLeft: position.left + 13, }); } } @@ -121,11 +168,41 @@ class PaymentsPage extends React.Component { this.setState({shouldShowAddPaymentMenu: false}); } + /** + * Hide the default / delete modal + */ + hideDefaultDeleteMenu() { + this.setState({shouldShowDefaultDeleteMenu: false}); + } + + hidePasswordPrompt() { + this.setState({shouldShowPasswordPrompt: false}); + } + + makeDefaultPaymentMethod(password) { + if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + PaymentMethods.setWalletLinkedAccount(password, this.state.selectedPaymentMethod.bankAccountID, null); + } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + PaymentMethods.setWalletLinkedAccount(password, null, this.state.selectedPaymentMethod.fundID); + } + } + + deletePaymentMethod() { + if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PAYPAL) { + PaymentMethods.deletePayPalMe(); + } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + BankAccounts.deleteBankAccount(this.state.selectedPaymentMethod.bankAccountID); + } else if (this.state.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + PaymentMethods.deleteDebitCard(this.state.selectedPaymentMethod.fundID); + } + } + navigateToTransferBalancePage() { Navigation.navigate(ROUTES.SETTINGS_PAYMENTS_TRANSFER_BALANCE); } render() { + const isPayPalMeSelected = this.state.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PAYPAL; return ( @@ -170,6 +247,101 @@ class PaymentsPage extends React.Component { }} onItemSelected={method => this.addPaymentMethodTypePressed(method)} /> + + + {this.props.isSmallScreenWidth && ( + + )} + {Permissions.canUseWallet(this.props.betas) && ( + { + this.setState({ + shouldShowPasswordPrompt: true, + shouldShowDefaultDeleteMenu: false, + passwordButtonText: this.props.translate('paymentsPage.setDefaultConfirmation'), + }); + }} + style={[styles.button, isPayPalMeSelected && styles.buttonDisable, styles.alignSelfCenter, styles.w100]} + disabled={isPayPalMeSelected} + > + + {this.props.translate('paymentsPage.setDefaultConfirmation')} + + + )} + { + this.setState({ + shouldShowDefaultDeleteMenu: false, + shouldShowConfirmPopover: true, + }); + }} + style={[ + styles.button, + styles.buttonDanger, + Permissions.canUseWallet(this.props.betas) && styles.mt4, + styles.alignSelfCenter, + styles.w100, + ]} + > + + {this.props.translate('common.delete')} + + + + + { + this.hidePasswordPrompt(); + this.makeDefaultPaymentMethod(password); + }} + submitButtonText={this.state.passwordButtonText} + isDangerousAction + /> + { + this.setState({ + shouldShowConfirmPopover: false, + }); + this.deletePaymentMethod(); + }} + onCancel={() => { + this.setState({shouldShowConfirmPopover: false}); + }} + shouldShowCancelButton + danger + />