diff --git a/assets/images/bolt.svg b/assets/images/bolt.svg new file mode 100644 index 000000000000..2290fbd7ded7 --- /dev/null +++ b/assets/images/bolt.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/assets/images/transfer.svg b/assets/images/transfer.svg new file mode 100644 index 000000000000..03cb3c5f72b2 --- /dev/null +++ b/assets/images/transfer.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/CONST.js b/src/CONST.js index 9e06129f252f..1fe463dcb01c 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -377,6 +377,11 @@ const CONST = { }, WALLET: { + TRANSFER_BALANCE_FEE: 0.30, + TRANSFER_METHOD_TYPE: { + INSTANT: 'instant', + ACH: 'ach', + }, ERROR: { IDENTITY_NOT_FOUND: 'Identity not found', INVALID_SSN: 'Invalid SSN', diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 1bfc29e16510..4c7ea591f3ff 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -168,4 +168,8 @@ export default { // Is Keyboard shortcuts modal open? IS_SHORTCUTS_MODAL_OPEN: 'isShortcutsModalOpen', + + // Stores information about active wallet transfer amount, selectedAccountID, status, etc + WALLET_TRANSFER: 'walletTransfer', + }; diff --git a/src/ROUTES.js b/src/ROUTES.js index a3a33bcdaa96..5f02858208d6 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -33,6 +33,8 @@ export default { SETTINGS_ADD_BANK_ACCOUNT: 'settings/payments/add-bank-account', SETTINGS_ADD_LOGIN: 'settings/addlogin/:type', getSettingsAddLoginRoute: type => `settings/addlogin/${type}`, + SETTINGS_PAYMENTS_TRANSFER_BALANCE: 'settings/payments/transfer-balance', + SETTINGS_PAYMENTS_CHOOSE_TRANSFER_ACCOUNT: 'settings/payments/choose-transfer-account', NEW_GROUP: 'new/group', NEW_CHAT: 'new/chat', REPORT, diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index ab8561c91b2e..945c81cda6ff 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -4,6 +4,7 @@ import ArrowRight from '../../../assets/images/arrow-right.svg'; import BackArrow from '../../../assets/images/back-left.svg'; import Bank from '../../../assets/images/bank.svg'; import Bill from '../../../assets/images/bill.svg'; +import Bolt from '../../../assets/images/bolt.svg'; import Briefcase from '../../../assets/images/briefcase.svg'; import Bug from '../../../assets/images/bug.svg'; import Building from '../../../assets/images/building.svg'; @@ -57,6 +58,7 @@ import RotateLeft from '../../../assets/images/rotate-left.svg'; import Send from '../../../assets/images/send.svg'; import SignOut from '../../../assets/images/sign-out.svg'; import Sync from '../../../assets/images/sync.svg'; +import Transfer from '../../../assets/images/transfer.svg'; import ThreeDots from '../../../assets/images/three-dots.svg'; import Trashcan from '../../../assets/images/trashcan.svg'; import UpArrow from '../../../assets/images/arrow-up.svg'; @@ -73,6 +75,7 @@ export { BackArrow, Bank, Bill, + Bolt, Briefcase, Building, Bug, @@ -126,6 +129,7 @@ export { Send, SignOut, Sync, + Transfer, ThreeDots, Trashcan, UpArrow, diff --git a/src/languages/en.js b/src/languages/en.js index 231416f6ca17..3bf15aa0e1aa 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -92,6 +92,7 @@ export default { bankAccount: 'Bank account', join: 'Join', decline: 'Decline', + transferBalance: 'Transfer Balance', cantFindAddress: 'Can\'t find your address? ', enterManually: 'Enter it manually', }, @@ -335,9 +336,27 @@ export default { }, paymentsPage: { paymentMethodsTitle: 'Payment methods', + allSet: 'All Set!', + transferConfirmText: ({amount}) => `${amount} will hit your account shortly!`, + gotIt: 'Got it, Thanks!', + }, + transferAmountPage: { + transfer: ({amount}) => `Transfer${amount ? ` ${amount}` : ''}`, + instant: 'Instant (Debit Card)', + instantSummary: ({amount}) => `1.5% fee (${amount} minimum)`, + ach: '1-3 Business Days (Bank Account)', + achSummary: 'No fee', + whichAccount: 'Which Account?', + fee: 'Fee', + failedTransfer: 'Failed to transfer balance', + }, + chooseTransferAccountPage: { + chooseAccount: 'Choose Account', }, paymentMethodList: { addPaymentMethod: 'Add payment method', + addDebitCard: 'Add debit card', + addBankAccount: 'Add bank account', accountLastFour: 'Account ending in', cardLastFour: 'Card ending in', addFirstPaymentMethod: 'Add a payment method to send and receive payments directly in the app.', diff --git a/src/languages/es.js b/src/languages/es.js index e37c269231c9..f936749ca409 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -92,6 +92,7 @@ export default { bankAccount: 'Cuenta bancaria', join: 'Unirse', decline: 'Rechazar', + transferBalance: 'Transferencia de saldo', cantFindAddress: '¿No encuentras tu dirección? ', enterManually: 'Ingresar manualmente', }, @@ -335,9 +336,27 @@ export default { }, paymentsPage: { paymentMethodsTitle: 'Métodos de pago', + allSet: 'Todo listo!', + transferConfirmText: ({amount}) => `${amount} llegará a tu cuenta en breve!`, + gotIt: 'Gracias!', + }, + transferAmountPage: { + transfer: ({amount}) => `Transferir${amount ? ` ${amount}` : ''}`, + instant: 'Instante', + instantSummary: ({amount}) => `Tarifa del 1.5% (${amount} mínimo)`, + ach: '1-3 días laborales', + achSummary: 'Sin cargo', + whichAccount: '¿Que cuenta?', + fee: 'Tarifa', + failedTransfer: 'No se pudo transferir el saldo', + }, + chooseTransferAccountPage: { + chooseAccount: 'Elegir cuenta', }, paymentMethodList: { addPaymentMethod: 'Agrega método de pago', + addDebitCard: 'Agregar tarjeta de débito', + addBankAccount: 'Agregar cuenta de banco', 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.', diff --git a/src/libs/API.js b/src/libs/API.js index 60ebf7334203..9904b7756f18 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -1123,6 +1123,21 @@ function CreatePolicyRoom(parameters) { return Network.post(commandName, parameters); } +/** + * Transfer Wallet balance and takes either the bankAccoundID or fundID + * @param {Object} parameters + * @param {String} [parameters.bankAccountID] + * @param {String} [parameters.fundID] + * @returns {Promise} + */ +function Wallet_TransferBalance(parameters) { + const commandName = 'TransferWalletBalance'; + if (!parameters.bankAccountID && !parameters.fundID) { + throw new Error('Must pass either bankAccountID or fundID to TransferWalletBalance'); + } + return Network.post(commandName, parameters); +} + export { Authenticate, AuthenticateWithAccountID, @@ -1180,6 +1195,7 @@ export { ValidateEmail, Wallet_Activate, Wallet_GetOnfidoSDKToken, + Wallet_TransferBalance, GetLocalCurrency, GetCurrencyList, Policy_Create, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 3e2d3c1ddbc2..ccfe29fa6523 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -40,6 +40,8 @@ import WorkspaceMembersPage from '../../../pages/workspace/WorkspaceMembersPage' import WorkspaceBankAccountPage from '../../../pages/workspace/WorkspaceBankAccountPage'; import WorkspaceNewRoomPage from '../../../pages/workspace/WorkspaceNewRoomPage'; import CONST from '../../../CONST'; +import TransferBalancePage from '../../../pages/settings/Payments/TransferBalancePage'; +import ChooseTransferAccountPage from '../../../pages/settings/Payments/ChooseTransferAccountPage'; const defaultSubRouteOptions = { cardStyle: styles.navigationScreenCardStyle, @@ -180,6 +182,14 @@ const SettingsModalStackNavigator = createModalStackNavigator([ Component: SettingsPaymentsPage, name: 'Settings_Payments', }, + { + Component: TransferBalancePage, + name: 'Settings_Payments_Transfer_Balance', + }, + { + Component: ChooseTransferAccountPage, + name: 'Settings_Payments_Choose_Transfer_Account', + }, { Component: SettingsAddPayPalMePage, name: 'Settings_Add_Paypal_Me', diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 2b61a58a5f40..34946309b308 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -52,6 +52,14 @@ export default { path: ROUTES.SETTINGS_PAYMENTS, exact: true, }, + Settings_Payments_Transfer_Balance: { + path: ROUTES.SETTINGS_PAYMENTS_TRANSFER_BALANCE, + exact: true, + }, + Settings_Payments_Choose_Transfer_Account: { + path: ROUTES.SETTINGS_PAYMENTS_CHOOSE_TRANSFER_ACCOUNT, + exact: true, + }, Settings_Add_Paypal_Me: { path: ROUTES.SETTINGS_ADD_PAYPAL_ME, exact: true, diff --git a/src/libs/PaymentUtils.js b/src/libs/PaymentUtils.js new file mode 100644 index 000000000000..4e33a165cb3e --- /dev/null +++ b/src/libs/PaymentUtils.js @@ -0,0 +1,69 @@ +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import * as Expensicons from '../components/Icon/Expensicons'; +import getBankIcon from '../components/Icon/BankIcons'; +import CONST from '../CONST'; +import * as Localize from './Localize'; + +/** + * Get the PaymentMethods list + * @param {Array} bankAccountList + * @param {Array} cardList + * @param {String} [payPalMeUsername=''] + * @returns {Array} + */ +function getPaymentMethods(bankAccountList, cardList, payPalMeUsername = '') { + const combinedPaymentMethods = []; + + _.each(bankAccountList, (bankAccount) => { + // Add all bank accounts besides the wallet + if (bankAccount.type === CONST.BANK_ACCOUNT_TYPES.WALLET) { + return; + } + + const formattedBankAccountNumber = bankAccount.accountNumber + ? `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${bankAccount.accountNumber.slice(-4) + }` + : null; + const {icon, iconSize} = getBankIcon(lodashGet(bankAccount, 'additionalData.bankName', '')); + combinedPaymentMethods.push({ + title: bankAccount.addressName, + description: formattedBankAccountNumber, + methodID: bankAccount.bankAccountID, + icon, + iconSize, + key: `bankAccount-${bankAccount.bankAccountID}`, + }); + }); + + _.each(cardList, (card) => { + const formattedCardNumber = card.cardNumber + ? `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${card.cardNumber.slice(-4)}` + : null; + const {icon, iconSize} = getBankIcon(card.bank, true); + combinedPaymentMethods.push({ + title: card.addressName, + description: formattedCardNumber, + methodID: card.cardNumber, + icon, + iconSize, + key: `card-${card.cardNumber}`, + }); + }); + + if (payPalMeUsername) { + combinedPaymentMethods.push({ + title: 'PayPal.me', + methodID: CONST.PAYMENT_METHODS.PAYPAL, + description: payPalMeUsername, + icon: Expensicons.PayPal, + key: 'payPalMePaymentMethod', + }); + } + + return combinedPaymentMethods; +} + +export default { + getPaymentMethods, +}; diff --git a/src/pages/settings/Payments/ChooseTransferAccountPage.js b/src/pages/settings/Payments/ChooseTransferAccountPage.js new file mode 100644 index 000000000000..eb3fec894eca --- /dev/null +++ b/src/pages/settings/Payments/ChooseTransferAccountPage.js @@ -0,0 +1,27 @@ +import React from 'react'; +import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; +import ScreenWrapper from '../../../components/ScreenWrapper'; +import Navigation from '../../../libs/Navigation/Navigation'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView'; + +const propTypes = { + ...withLocalizePropTypes, +}; + +const ChooseTransferAccountPage = props => ( + + + Navigation.goBack()} + onCloseButtonPress={() => Navigation.dismissModal()} + /> + + +); + +ChooseTransferAccountPage.propTypes = propTypes; + +export default withLocalize(ChooseTransferAccountPage); diff --git a/src/pages/settings/Payments/PaymentMethodList.js b/src/pages/settings/Payments/PaymentMethodList.js index 15f6548a9cef..54d9a3d9817a 100644 --- a/src/pages/settings/Payments/PaymentMethodList.js +++ b/src/pages/settings/Payments/PaymentMethodList.js @@ -3,7 +3,6 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {FlatList} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; import MenuItem from '../../../components/MenuItem'; @@ -13,8 +12,8 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal import ONYXKEYS from '../../../ONYXKEYS'; import CONST from '../../../CONST'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import getBankIcon from '../../../components/Icon/BankIcons'; import bankAccountPropTypes from '../../../components/bankAccountPropTypes'; +import PaymentUtils from '../../../libs/PaymentUtils'; const MENU_ITEM = 'menuItem'; @@ -70,60 +69,12 @@ class PaymentMethodList extends Component { * @returns {Array} */ createPaymentMethodList() { - const combinedPaymentMethods = []; - - _.each(this.props.bankAccountList, (bankAccount) => { - // Add all bank accounts besides the wallet - if (bankAccount.type === CONST.BANK_ACCOUNT_TYPES.WALLET) { - return; - } - - const formattedBankAccountNumber = bankAccount.accountNumber - ? `${this.props.translate('paymentMethodList.accountLastFour')} ${ - bankAccount.accountNumber.slice(-4) - }` - : null; - const {icon, iconSize} = getBankIcon(lodashGet(bankAccount, 'additionalData.bankName', '')); - combinedPaymentMethods.push({ - type: MENU_ITEM, - title: bankAccount.addressName, - - // eslint-disable-next-line - description: formattedBankAccountNumber, - icon, - iconSize, - onPress: e => this.props.onPress(e, bankAccount.bankAccountID), - key: `bankAccount-${bankAccount.bankAccountID}`, - }); - }); - - _.each(this.props.cardList, (card) => { - const formattedCardNumber = card.cardNumber - ? `${this.props.translate('paymentMethodList.cardLastFour')} ${card.cardNumber.slice(-4)}` - : null; - const {icon, iconSize} = getBankIcon(card.bank, true); - combinedPaymentMethods.push({ - type: MENU_ITEM, - title: card.addressName, - // eslint-disable-next-line - description: formattedCardNumber, - icon, - iconSize, - onPress: e => this.props.onPress(e, card.cardNumber), - key: `card-${card.cardNumber}`, - }); - }); - - if (this.props.payPalMeUsername) { - combinedPaymentMethods.push({ - type: MENU_ITEM, - title: 'PayPal.me', - description: this.props.payPalMeUsername, - icon: Expensicons.PayPal, - onPress: e => this.props.onPress(e, 'payPalMe'), - key: 'payPalMePaymentMethod', - }); - } + let combinedPaymentMethods = PaymentUtils.getPaymentMethods(this.props.bankAccountList, this.props.cardList, this.props.payPalMeUsername); + combinedPaymentMethods = _.map(combinedPaymentMethods, paymentMethod => ({ + ...paymentMethod, + type: MENU_ITEM, + onPress: e => this.props.onPress(e, paymentMethod.methodID), + })); // If we have not added any payment methods, show a default empty state if (_.isEmpty(combinedPaymentMethods)) { diff --git a/src/pages/settings/Payments/TransferBalancePage.js b/src/pages/settings/Payments/TransferBalancePage.js new file mode 100644 index 000000000000..eb12627c2a3c --- /dev/null +++ b/src/pages/settings/Payments/TransferBalancePage.js @@ -0,0 +1,27 @@ +import React from 'react'; +import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; +import ScreenWrapper from '../../../components/ScreenWrapper'; +import Navigation from '../../../libs/Navigation/Navigation'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView'; + +const propTypes = { + ...withLocalizePropTypes, +}; + +const TransferBalancePage = props => ( + + + Navigation.goBack()} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + +); + +TransferBalancePage.propTypes = propTypes; + +export default withLocalize(TransferBalancePage);