Add wallet balance transfer Page #6878
Conversation
| 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); | ||
| } | ||
|
|
There was a problem hiding this comment.
I am not sure what would be the best way to handle these but I need these to share data across screens.
There was a problem hiding this comment.
There's perhaps no other way to share this data. But the logic can be improved.
cancelWalletTransfer sounds like either:
- We called the API to start a transfer and want to make a new request to cancel it
- We are in the middle of a flow and have not yet called the API to start the transfer and want to cancel it
But then if we look at where this method is called we see it happens when we "confirm" a modal that says {amount} will hit your account shortly!.
If we are just sharing data about a pending transfer then maybe it's best to call this resetWalletTransferData().
But looking into it more - it seems like the primary purpose is to dismiss the confirm modal. So, why not do this instead...
function dismissWalletConfirmModal() {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: false});
}If we need to "clear" the rest of the data like balance, accountID, etc why are we doing that when the modal button is pressed? Shouldn't this always happen when we have a successful transfer here:
App/src/libs/actions/PaymentMethods.js
Lines 112 to 119 in 11b8df1
And not just when a user "confirms"?
App/src/pages/settings/Payments/PaymentsPage.js
Lines 176 to 180 in 11b8df1
There was a problem hiding this comment.
It was just the shorter way to do two things at once. reset the data and hide the confirm modal.
There was a problem hiding this comment.
I understand that instinct, but my feedback would be that the shorter way is not always the easiest to understand. It's usually better to write code that leaves no doubt about what is happening vs. code where we must inspect the details of every method to figure out what it does.
| 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; | ||
| } |
There was a problem hiding this comment.
I don't see any selected account by default. userWallet.walletLinkedAccountID does not have any value.
There was a problem hiding this comment.
I think we have to call the SetWalletLinkedAccount command to set one. @stitesExpensify would maybe know for sure.
There was a problem hiding this comment.
Yeah we have to set the account manually for now. We should start defaulting them in the future though..
|
Note: I can't test the wallet transfer, I would need your help to do that. Thanks. |
| 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); | ||
| } | ||
|
|
There was a problem hiding this comment.
There's perhaps no other way to share this data. But the logic can be improved.
cancelWalletTransfer sounds like either:
- We called the API to start a transfer and want to make a new request to cancel it
- We are in the middle of a flow and have not yet called the API to start the transfer and want to cancel it
But then if we look at where this method is called we see it happens when we "confirm" a modal that says {amount} will hit your account shortly!.
If we are just sharing data about a pending transfer then maybe it's best to call this resetWalletTransferData().
But looking into it more - it seems like the primary purpose is to dismiss the confirm modal. So, why not do this instead...
function dismissWalletConfirmModal() {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: false});
}If we need to "clear" the rest of the data like balance, accountID, etc why are we doing that when the modal button is pressed? Shouldn't this always happen when we have a successful transfer here:
App/src/libs/actions/PaymentMethods.js
Lines 112 to 119 in 11b8df1
And not just when a user "confirms"?
App/src/pages/settings/Payments/PaymentsPage.js
Lines 176 to 180 in 11b8df1
| PaymentMethods.saveWalletTransferAmountAndAccount( | ||
| this.props.userWallet.currentBalance, | ||
| selectedAccount ? selectedAccount.id : '', | ||
| ); |
There was a problem hiding this comment.
Does it make sense to do this in the constructor()? If the currentBalance changes the user will transfer whatever we first had when the page opened and not the new balance.
There was a problem hiding this comment.
Actually, the balance is transferred via the backend. API just takes the account where the user wants to transfer. But there is no other way on the app where wallet values will update automatically. The wallet is only refreshed when we open some pages and the First-time app loads.
But I agree I can subscribe to wallet wherever I need the transfer amount.
There was a problem hiding this comment.
I wanted to remove the temporary data that is added for wallet transfer. we can't clear the data(set that to null) before Confirm modal is shown as the same key ONYXKEYS.WALLET_TRANSFER.shouldShowConfirmModal would be used to show the modal.
After the modal is shown and user close it. We can remove all the temporary data from Onyx by setting it to null. And by setting it to null, we close the modal as well.
There was a problem hiding this comment.
Sorry, I don't understand the explanation. Can you try asking this in the form of a question or point to some examples?
There was a problem hiding this comment.
So we use an Onyx key to store the temporary data used for wallet transfer. And the same key is used to determine the isVisible state of ConfirmModal.
- After the transfer is done, we don't need the temporary data and thus we have to set the Onyx key to null to clear it.
- Confirm Modal is shown to user after API call is made and we need
shouldShowConfirmModalto open this. - If we set the Onyx data to null after API call we won't be able to determine the state for ConfirmModal.
we need a trigger point to clear the Onyx data after wallet transfer i.e. a place where we can set the Key to null. I think the best place would be after user has confirmed the Transfer.
There was a problem hiding this comment.
Sorry, not sure if I totally understand still. But can we solve this problem by using a new key for the modal visibility flag to separate it from the wallet data? That way we can reset or clear the data however we want and have separate controls to show/hide the modal?
There was a problem hiding this comment.
we have to set the Onyx key to null to clear it
Unsure why we need to set this null - what happens if we don't do this?
There was a problem hiding this comment.
Nothing. It will just remove those keys from storage but I think storage is never a problem for us. So I am correcting it now.
|
Updating the PR asap |
| * @param {String} selectedAccountID | ||
| */ | ||
| function saveWalletTransferAccount(selectedAccountID) { | ||
| Onyx.set(ONYXKEYS.WALLET_TRANSFER, { |
There was a problem hiding this comment.
Is it possible to use a merge() here instead of a set()?
There was a problem hiding this comment.
Yeah. It does not hurt. Sorry, forgot the conflict issues between set and merge.
| } | ||
|
|
||
| /** | ||
| * Remove all the data related to wallet transfer and close the ConfirmModal |
There was a problem hiding this comment.
Also think we should not be doing these two things at once. Closing a modal is a very explicit action. Resetting the wallet transfer data is another specific action. Is it easy to tell that calling clearWalletTransfer() has a side effect where a modal closes?
There was a problem hiding this comment.
I agree. Now, I am not worried about the storage so I am refactoring it.
| 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); | ||
| } | ||
|
|
There was a problem hiding this comment.
I understand that instinct, but my feedback would be that the shorter way is not always the easiest to understand. It's usually better to write code that leaves no doubt about what is happening vs. code where we must inspect the details of every method to figure out what it does.
| function saveWalletTransferTransferredAmount(transferAmount) { | ||
| Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount}); | ||
| } |
There was a problem hiding this comment.
I had to store this transferAmount as after user click transfer amount, balance in the userWallet will be set to 0 and then in the ConfirmModal we will not have any way to show the transferred amount.
There was a problem hiding this comment.
I think saveWalletTransferAmount is fine descriptiveness-wise, and not as long 😄
|
PR Updated. I have not added logic to set the Default Account for two reasons:
|
|
PR updated @marcaaron @stitesExpensify |
stitesExpensify
left a comment
There was a problem hiding this comment.
Looking good, we're getting close!
| * Check to see if user has either a debit card or personal bank account added | ||
| * | ||
| * @param {Array} [cardList] | ||
| * @param {Array} [bankAccountList] |
There was a problem hiding this comment.
Why are we only checking for default credit here? Isn't it any bank account that is verified / open?
There was a problem hiding this comment.
I might not understand what "default credit" means. Is it not a personal / deposit account?
I thought VBA is not a valid payment method, but maybe wrong about that too. I asked this once here.
| function saveWalletTransferTransferredAmount(transferAmount) { | ||
| Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount}); | ||
| } |
There was a problem hiding this comment.
I think saveWalletTransferAmount is fine descriptiveness-wise, and not as long 😄
| const canTransfer = transferAmount > CONST.WALLET.TRANSFER_BALANCE_FEE; | ||
| const calculatedFee = PaymentUtils.calculateWalletTransferBalanceFee(this.props.userWallet.currentBalance, selectedPaymentType); | ||
| const transferAmount = this.props.userWallet.currentBalance - calculatedFee; | ||
| const canTransfer = transferAmount > calculatedFee; |
There was a problem hiding this comment.
Shouldn't this line either be this.props.userWallet.currentBalance > calculatedFee or transferAmount > 0?
As it is, you couldn't transfer $0.50 because 50-25 = 25 and 25 !> 25
There was a problem hiding this comment.
Yeah, it is supposed to be transferAmount > 0. Thanks for checking in.
|
Ready @marcaaron @stitesExpensify |
| * Call the API to transfer wallet balance. | ||
| * @param {Object} paymentMethod | ||
| * @param {String|Number} paymentMethod.methodID | ||
| * @param {'bankAccount'|'debitCard'} paymentMethod.type |
There was a problem hiding this comment.
NAB, but these docs are inconsistent with how we do things. If there are multiple types we use * and the last one should be String. I will try to propose a style guide change so it doesn't need to be mentioned.
| */ | ||
| function transferWalletBalance(paymentMethod) { | ||
| const parameters = { | ||
| [paymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT ? 'bankAccountID' : 'fundID']: paymentMethod.methodID, |
There was a problem hiding this comment.
Please use the constants for 'bankAccountID' and 'fundID' or add them if they don't exist.
| Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: true, loading: false}); | ||
| Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); | ||
| }).catch((error) => { | ||
| Log.alert(`[Payments] Failed to transfer wallet balance: ${error.message}`); |
There was a problem hiding this comment.
Not sure about this alert and think we should remove it for now. We can see in the server logs why a request returned a jsonCode other than 200. @stitesExpensify do you agree?
|
|
||
| /** | ||
| * Close the ConfirmModal of Wallet balance transfer | ||
| */ |
There was a problem hiding this comment.
Remove please.
Future reference: avoid docs that don't add any additional information - https://github.com/Expensify/App/blob/main/STYLE.md#jsdocs
|
|
||
| mrn3: { | ||
| marginRight: -12, | ||
| }, |
|
All comments addressed. Ready for final review. |
|
@stitesExpensify Awaiting final review from you and merge. Hope it answers your question #6878 (comment) |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by @stitesExpensify in version: 1.1.29-6 🚀
|
|
🚀 Deployed to production by @roryabraham in version: 1.1.30-3 🚀
|
Please review @marcaaron
Details
Fixed Issues
$ #3922
Tests | QA Steps
Tested On
Screenshots
Web | Mobile Web | Desktop
iOS
Android