-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add wallet balance transfer Page #6878
Add wallet balance transfer Page #6878
Conversation
src/libs/actions/PaymentMethods.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
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); |
And not just when a user "confirms"?
App/src/pages/settings/Payments/PaymentsPage.js
Lines 176 to 180 in 11b8df1
<ConfirmModal | |
title={this.props.translate('paymentsPage.allSet')} | |
onConfirm={PaymentMethods.cancelWalletTransfer} | |
isVisible={this.props.walletTransfer.completed} | |
prompt={this.props.translate('paymentsPage.transferConfirmText', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any selected account by default. userWallet.walletLinkedAccountID
does not have any value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
src/libs/actions/PaymentMethods.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
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); |
And not just when a user "confirms"?
App/src/pages/settings/Payments/PaymentsPage.js
Lines 176 to 180 in 11b8df1
<ConfirmModal | |
title={this.props.translate('paymentsPage.allSet')} | |
onConfirm={PaymentMethods.cancelWalletTransfer} | |
isVisible={this.props.walletTransfer.completed} | |
prompt={this.props.translate('paymentsPage.transferConfirmText', { |
PaymentMethods.saveWalletTransferAmountAndAccount( | ||
this.props.userWallet.currentBalance, | ||
selectedAccount ? selectedAccount.id : '', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
shouldShowConfirmModal
to 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
src/libs/actions/PaymentMethods.js
Outdated
* @param {String} selectedAccountID | ||
*/ | ||
function saveWalletTransferAccount(selectedAccountID) { | ||
Onyx.set(ONYXKEYS.WALLET_TRANSFER, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use a merge()
here instead of a set()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. It does not hurt. Sorry, forgot the conflict issues between set and merge.
src/libs/actions/PaymentMethods.js
Outdated
} | ||
|
||
/** | ||
* Remove all the data related to wallet transfer and close the ConfirmModal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Now, I am not worried about the storage so I am refactoring it.
src/libs/actions/PaymentMethods.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/libs/actions/PaymentMethods.js
Outdated
function saveWalletTransferTransferredAmount(transferAmount) { | ||
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think saveWalletTransferAmount
is fine descriptiveness-wise, and not as long 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Updated. I have not added logic to set the Default Account for two reasons:
|
PR updated @marcaaron @stitesExpensify |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, we're getting close!
src/libs/PaymentUtils.js
Outdated
function hasExpensifyPaymentMethod(cardList = [], bankAccountList = []) { | ||
return _.some(cardList, card => card) || _.some(bankAccountList, (bankAccountJSON) => { | ||
const bankAccount = new BankAccount(bankAccountJSON); | ||
return bankAccount.isDefaultCredit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc: @marcaaron
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/libs/actions/PaymentMethods.js
Outdated
function saveWalletTransferTransferredAmount(transferAmount) { | ||
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it is supposed to be transferAmount > 0
. Thanks for checking in.
Ready @marcaaron @stitesExpensify |
src/libs/actions/PaymentMethods.js
Outdated
* 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/libs/actions/PaymentMethods.js
Outdated
*/ | ||
function transferWalletBalance(paymentMethod) { | ||
const parameters = { | ||
[paymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT ? 'bankAccountID' : 'fundID']: paymentMethod.methodID, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the constants for 'bankAccountID'
and 'fundID'
or add them if they don't exist.
src/libs/actions/PaymentMethods.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
src/libs/actions/PaymentMethods.js
Outdated
|
||
/** | ||
* Close the ConfirmModal of Wallet balance transfer | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove please.
Future reference: avoid docs that don't add any additional information - https://github.com/Expensify/App/blob/main/STYLE.md#jsdocs
src/styles/utilities/spacing.js
Outdated
@@ -165,6 +165,10 @@ export default { | |||
marginBottom: -4, | |||
}, | |||
|
|||
mrn3: { | |||
marginRight: -12, | |||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove it please.
All comments addressed. Ready for final review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks great job!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there!
@stitesExpensify Awaiting final review from you and merge. Hope it answers your question #6878 (comment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
✋ 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