Skip to content

Add wallet balance transfer Page #6878

Merged
stitesExpensify merged 33 commits into
Expensify:mainfrom
parasharrajat:trans-amount-scaffold
Jan 13, 2022
Merged

Add wallet balance transfer Page #6878
stitesExpensify merged 33 commits into
Expensify:mainfrom
parasharrajat:trans-amount-scaffold

Conversation

@parasharrajat

Copy link
Copy Markdown
Member

Please review @marcaaron

Details

Fixed Issues

$ #3922

Tests | QA Steps

  1. Open Payments page.
  2. Click on the wallet transfer.
  3. You should be taken to the Transfer balance page.
  4. If your wallet balance is greater than the fee and which account? has an account then you should be able to click the transfer button and it should be enabled.
  5. Observe if Which account? is a debit card then the Instant method is selected.
  6. Observe if Which account? is a bank account then the 1-3 business days method is selected.
  7. Paypal should not be shown as Which account?

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

Screenshots

Web | Mobile Web | Desktop

image

iOS

Android

@parasharrajat parasharrajat requested a review from a team as a code owner December 22, 2021 16:41
@MelvinBot MelvinBot requested review from stitesExpensify and removed request for a team December 22, 2021 16:41
Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment on lines +132 to +156
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);
}

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Contributor

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:

  1. We called the API to start a transfer and want to make a new request to cancel it
  2. 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:

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"?

<ConfirmModal
title={this.props.translate('paymentsPage.allSet')}
onConfirm={PaymentMethods.cancelWalletTransfer}
isVisible={this.props.walletTransfer.completed}
prompt={this.props.translate('paymentsPage.transferConfirmText', {

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Contributor

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.

Comment on lines +91 to +109
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;
}

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor

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..

@parasharrajat

Copy link
Copy Markdown
Member Author

Note: I can't test the wallet transfer, I would need your help to do that. Thanks.

Comment thread src/libs/actions/PaymentMethods.js
Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment on lines +132 to +156
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);
}

Copy link
Copy Markdown
Contributor

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:

  1. We called the API to start a transfer and want to make a new request to cancel it
  2. 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:

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"?

<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 : '',
);

Copy link
Copy Markdown
Contributor

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.

@parasharrajat parasharrajat Dec 22, 2021

Copy link
Copy Markdown
Member Author

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.

@parasharrajat parasharrajat Dec 29, 2021

Copy link
Copy Markdown
Member Author

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.

@marcaaron

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Member Author

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.

  1. 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.
  2. Confirm Modal is shown to user after API call is made and we need shouldShowConfirmModal to open this.
  3. 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.

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Member Author

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.

Comment thread src/pages/settings/Payments/TransferBalancePage.js Outdated
Comment thread src/pages/settings/Payments/TransferBalancePage.js Outdated
Comment thread src/pages/settings/Payments/TransferBalancePage.js Outdated
Comment thread src/pages/settings/Payments/TransferBalancePage.js Outdated
Comment thread src/pages/settings/Payments/paymentPropTypes.js Outdated
Comment thread src/pages/settings/Payments/paymentPropTypes.js Outdated
Comment thread src/pages/settings/Payments/paymentPropTypes.js Outdated
@parasharrajat

Copy link
Copy Markdown
Member Author

Updating the PR asap

Comment thread src/libs/PaymentUtils.js Outdated
Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment thread src/libs/actions/PaymentMethods.js Outdated
* @param {String} selectedAccountID
*/
function saveWalletTransferAccount(selectedAccountID) {
Onyx.set(ONYXKEYS.WALLET_TRANSFER, {

Copy link
Copy Markdown
Contributor

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()?

Copy link
Copy Markdown
Member Author

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.

Comment thread src/libs/actions/PaymentMethods.js Outdated
}

/**
* Remove all the data related to wallet transfer and close the ConfirmModal

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Member Author

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.

Comment thread src/pages/settings/Payments/TransferBalancePage.js Outdated
Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment on lines +132 to +156
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);
}

Copy link
Copy Markdown
Contributor

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.

Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment on lines +161 to +163
function saveWalletTransferTransferredAmount(transferAmount) {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount});
}

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Contributor

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 😄

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

😆

@parasharrajat

Copy link
Copy Markdown
Member Author

PR Updated.

I have not added logic to set the Default Account for two reasons:

  1. I don't know where to set it.
  2. I don't know How to set it 😉

@parasharrajat

parasharrajat commented Jan 11, 2022

Copy link
Copy Markdown
Member Author

PR updated @marcaaron @stitesExpensify

@stitesExpensify stitesExpensify left a comment

Copy link
Copy Markdown
Contributor

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!

Comment thread src/pages/settings/Payments/TransferBalancePage.js Outdated
Comment thread src/libs/PaymentUtils.js
* Check to see if user has either a debit card or personal bank account added
*
* @param {Array} [cardList]
* @param {Array} [bankAccountList]

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

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.

Comment thread src/libs/actions/PaymentMethods.js Outdated
Comment on lines +161 to +163
function saveWalletTransferTransferredAmount(transferAmount) {
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {transferAmount});
}

Copy link
Copy Markdown
Contributor

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 😄

Comment thread src/pages/settings/Payments/TransferBalancePage.js
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;

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Member Author

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.

@parasharrajat

Copy link
Copy Markdown
Member Author

Ready @marcaaron @stitesExpensify

Comment thread 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

Copy link
Copy Markdown
Contributor

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.

Comment thread src/libs/actions/PaymentMethods.js Outdated
*/
function transferWalletBalance(paymentMethod) {
const parameters = {
[paymentMethod.type === CONST.PAYMENT_METHODS.BANK_ACCOUNT ? 'bankAccountID' : 'fundID']: paymentMethod.methodID,

Copy link
Copy Markdown
Contributor

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.

Comment thread 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}`);

Copy link
Copy Markdown
Contributor

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?

Comment thread src/libs/actions/PaymentMethods.js Outdated

/**
* Close the ConfirmModal of Wallet balance transfer
*/

Copy link
Copy Markdown
Contributor

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

Comment thread src/styles/utilities/spacing.js Outdated

mrn3: {
marginRight: -12,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove it please.

@parasharrajat

Copy link
Copy Markdown
Member Author

All comments addressed. Ready for final review.

@marcaaron marcaaron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks great job!

@stitesExpensify stitesExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there!

Comment thread src/pages/settings/Payments/TransferBalancePage.js
@parasharrajat

Copy link
Copy Markdown
Member Author

@stitesExpensify Awaiting final review from you and merge. Hope it answers your question #6878 (comment)

@stitesExpensify stitesExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@stitesExpensify stitesExpensify merged commit 01757b2 into Expensify:main Jan 13, 2022
@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by @stitesExpensify in version: 1.1.29-6 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 failure ❌
🕸 web 🕸 success ✅

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by @roryabraham in version: 1.1.30-3 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants