From 68395a0cc6230928a0eb3ae255dfa3402e54d250 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 22 Dec 2021 21:42:37 +0530 Subject: [PATCH 01/30] Extend wallet balance to take style prop --- src/components/CurrentWalletBalance.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/CurrentWalletBalance.js b/src/components/CurrentWalletBalance.js index c894466668fe..eee79d77a47f 100644 --- a/src/components/CurrentWalletBalance.js +++ b/src/components/CurrentWalletBalance.js @@ -17,11 +17,15 @@ const propTypes = { currentBalance: PropTypes.number, }), + /** Styles of the amount */ + balanceStyles: PropTypes.arrayOf(PropTypes.object), + ...withLocalizePropTypes, }; const defaultProps = { userWallet: {}, + balanceStyles: [], }; const CurrentWalletBalance = (props) => { @@ -41,7 +45,7 @@ const CurrentWalletBalance = (props) => { ); return ( {`${formattedBalance}`} From d0d742e893e0fabdfe669b7e6f6135f79378c6fb Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 22 Dec 2021 21:43:10 +0530 Subject: [PATCH 02/30] add wallet transfer actions --- src/libs/actions/PaymentMethods.js | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js index 1d982f178700..d1b135c13c72 100644 --- a/src/libs/actions/PaymentMethods.js +++ b/src/libs/actions/PaymentMethods.js @@ -8,6 +8,7 @@ import Growl from '../Growl'; import * as Localize from '../Localize'; import Navigation from '../Navigation/Navigation'; import * as CardUtils from '../CardUtils'; +import Log from '../Log'; /** * Calls the API to get the user's bankAccountList, cardList, wallet, and payPalMe @@ -95,8 +96,70 @@ function clearDebitCardFormErrorAndSubmit() { }); } +/** + * Call the API to transfer wallet balance. + * @param {Object} paymentMethod + * @param {String} paymentMethod.id + * @param {'bankAccount'|'debitCard'} paymentMethod.type + * @returns {Promise} + */ +function transferWalletBalance(paymentMethod) { + const parameters = { + [paymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT ? 'bankAccountID' : 'fundID']: paymentMethod.id, + }; + Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: true}); + + return API.TransferWalletBalance(parameters) + .then((response) => { + if (response.jsonCode !== 200) { + throw new Error(response.message); + } + Onyx.merge(ONYXKEYS.USER_WALLET, {balance: 0}); + Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {completed: true, loading: false}); + Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); + }).catch((error) => { + Log.alert(`[Payments] Failed to transfer wallet balance: ${error.message}`); + Growl.error(Localize.translateLocal('transferAmountPage.failedTransfer')); + Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: false}); + }); +} + +/** + * Set the necessary data for wallet transfer + * @param {Number} currentBalance + * @param {Number} selectedAccountID + */ +function saveWalletTransferAmountAndAccount(currentBalance, selectedAccountID) { + Onyx.set(ONYXKEYS.WALLET_TRANSFER, { + transferAmount: currentBalance - CONST.WALLET.TRANSFER_BALANCE_FEE, + selectedAccountID, + filterPaymentMethodType: null, + loading: false, + completed: false, + }); +} + +/** + * Update selected accountID and other data for wallet transfer + * @param {Object} data + */ +function updateWalletTransferData(data) { + Onyx.merge(ONYXKEYS.WALLET_TRANSFER, data); +} + +/** + * Cancel the wallet transfer + */ +function cancelWalletTransfer() { + Onyx.set(ONYXKEYS.WALLET_TRANSFER, null); +} + export { getPaymentMethods, addBillingCard, clearDebitCardFormErrorAndSubmit, + transferWalletBalance, + saveWalletTransferAmountAndAccount, + updateWalletTransferData, + cancelWalletTransfer, }; From e89e58e3dd9404659983337bcc04dc7d7a49939f Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 22 Dec 2021 21:52:22 +0530 Subject: [PATCH 03/30] add prop types --- .../settings/Payments/paymentPropTypes.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/pages/settings/Payments/paymentPropTypes.js diff --git a/src/pages/settings/Payments/paymentPropTypes.js b/src/pages/settings/Payments/paymentPropTypes.js new file mode 100644 index 000000000000..917045d80bd8 --- /dev/null +++ b/src/pages/settings/Payments/paymentPropTypes.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import CONST from '../../../CONST'; + +/** Array of bank account objects */ +const bankAccountListPropTypes = PropTypes.arrayOf(PropTypes.shape({ + /** The name of the institution (bank of america, etc) */ + addressName: PropTypes.string, + + /** The masked bank account number */ + accountNumber: PropTypes.string, + + /** The bankAccountID in the bankAccounts db */ + bankAccountID: PropTypes.number, + + /** The bank account type */ + type: PropTypes.string, +})); + +/** Array of card objects */ +const cardListPropTypes = PropTypes.arrayOf(PropTypes.shape({ + /** The name of the institution (bank of america, etc) */ + cardName: PropTypes.string, + + /** The masked credit card number */ + cardNumber: PropTypes.string, + + /** The ID of the card in the cards DB */ + cardID: PropTypes.number, +})); + +/** Wallet balance transfer props */ +const walletTransferPropTypes = PropTypes.shape({ + /** Amount being transferred */ + transferAmount: PropTypes.number, + + /** Selected accountID for transfer */ + selectedAccountID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + + /** Type to filter the payment Method list */ + filterPaymentMethodType: PropTypes.oneOf([CONST.PAYMENT_METHODS.DEBIT_CARD, CONST.PAYMENT_METHODS.BANK_ACCOUNT, '']), + + /** Whether the user has intiatied the tranfer and transfer request is submitted to backend. */ + completed: PropTypes.bool, +}); + +export { + bankAccountListPropTypes, + cardListPropTypes, + walletTransferPropTypes, +}; From a616f371d728112311e9f3e2f7c0d65110a1aec7 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 22 Dec 2021 21:52:40 +0530 Subject: [PATCH 04/30] Create wallet transfer page --- .../settings/Payments/TransferBalancePage.js | 245 +++++++++++++++++- src/styles/styles.js | 14 + src/styles/utilities/spacing.js | 4 + src/styles/variables.js | 1 + 4 files changed, 250 insertions(+), 14 deletions(-) diff --git a/src/pages/settings/Payments/TransferBalancePage.js b/src/pages/settings/Payments/TransferBalancePage.js index eb12627c2a3c..68f199939c80 100644 --- a/src/pages/settings/Payments/TransferBalancePage.js +++ b/src/pages/settings/Payments/TransferBalancePage.js @@ -1,27 +1,244 @@ +import _ from 'underscore'; import React from 'react'; +import {View, ScrollView} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import ONYXKEYS from '../../../ONYXKEYS'; import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; import ScreenWrapper from '../../../components/ScreenWrapper'; import Navigation from '../../../libs/Navigation/Navigation'; +import styles from '../../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView'; +import compose from '../../../libs/compose'; +import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView/index'; +import * as Expensicons from '../../../components/Icon/Expensicons'; +import MenuItem from '../../../components/MenuItem'; +import CONST from '../../../CONST'; +import variables from '../../../styles/variables'; +import ExpensifyText from '../../../components/ExpensifyText'; +import Button from '../../../components/Button'; +import FixedFooter from '../../../components/FixedFooter'; +import CurrentWalletBalance from '../../../components/CurrentWalletBalance'; +import * as paymentPropTypes from './paymentPropTypes'; +import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; +import PaymentUtils from '../../../libs/PaymentUtils'; +import userWalletPropTypes from '../../EnablePayments/userWalletPropTypes'; const propTypes = { + /** User's wallet information */ + userWallet: userWalletPropTypes.userWallet, + + /** Array of bank account objects */ + bankAccountList: paymentPropTypes.bankAccountListPropTypes, + + /** Array of card objects */ + cardList: paymentPropTypes.cardListPropTypes, + + /** Wallet balance transfer props */ + walletTransfer: paymentPropTypes.walletTransferPropTypes, + ...withLocalizePropTypes, }; -const TransferBalancePage = props => ( - - - Navigation.goBack()} - onCloseButtonPress={() => Navigation.dismissModal(true)} - /> - - -); +const defaultProps = { + userWallet: {}, + bankAccountList: [], + cardList: [], + walletTransfer: {}, +}; + +class TransferBalancePage extends React.Component { + constructor(props) { + super(props); + + this.paymentTypes = [ + { + key: CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT, + title: this.props.translate('transferAmountPage.instant'), + description: this.props.translate('transferAmountPage.instantSummary', { + amount: this.props.numberFormat( + 0.25, + {style: 'currency', currency: 'USD'}, + ), + }), + icon: Expensicons.Bolt, + type: CONST.PAYMENT_METHODS.DEBIT_CARD, + }, + { + key: CONST.WALLET.TRANSFER_METHOD_TYPE.ACH, + title: this.props.translate('transferAmountPage.ach'), + description: this.props.translate('transferAmountPage.achSummary'), + icon: Expensicons.Bank, + type: CONST.PAYMENT_METHODS.BANK_ACCOUNT, + }, + ]; + + this.transferBalance = this.transferBalance.bind(this); + + const selectedAccount = this.getSelectedAccount(); + PaymentMethods.saveWalletTransferAmountAndAccount( + this.props.userWallet.currentBalance, + selectedAccount ? selectedAccount.id : '', + ); + } + + /** + * Get the selected/default Account for wallet tsransfer + * @returns {Object|undefined} + */ + getSelectedAccount() { + const paymentMethods = PaymentUtils.getPaymentMethods( + this.props.bankAccountList, + this.props.cardList, + ); + + const defaultAccount = _.find( + paymentMethods, + method => method.id === lodashGet(this.props, 'userWallet.walletLinkedAccountID', ''), + ); + const selectedAccount = this.props.walletTransfer.selectedAccountID + ? _.find( + paymentMethods, + method => method.id === this.props.walletTransfer.selectedAccountID, + ) + : defaultAccount; + + return selectedAccount; + } + + /** + * Transfer Wallet balance + * @param {PaymentMethod} selectedAccount + */ + transferBalance(selectedAccount) { + if (!selectedAccount) { + return; + } + PaymentMethods.transferWalletBalance(selectedAccount); + } + + render() { + const selectedAccount = this.getSelectedAccount(); + const selectedPaymentType = selectedAccount && selectedAccount.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT + ? CONST.WALLET.TRANSFER_METHOD_TYPE.ACH + : CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT; + const transferAmount = this.props.walletTransfer.transferAmount.toFixed(2); + const canTransfer = transferAmount > CONST.WALLET.TRANSFER_BALANCE_FEE; + const isButtonDisabled = !canTransfer || !selectedAccount; + + return ( + + + Navigation.goBack()} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + + + + {_.map(this.paymentTypes, paymentType => ( + + ))} + + {this.props.translate('transferAmountPage.whichAccount')} + + {Boolean(selectedAccount) + && ( + + )} + + {this.props.translate('transferAmountPage.fee')} + + + {this.props.numberFormat( + CONST.WALLET.TRANSFER_BALANCE_FEE, + {style: 'currency', currency: 'USD'}, + )} + + + +