Skip to content

feat: standardize pay button for expenses and IOUs#66790

Merged
grgia merged 27 commits intoExpensify:mainfrom
getusha:feat-standardize-pay-button-expense-n-ious-2
Aug 1, 2025
Merged

feat: standardize pay button for expenses and IOUs#66790
grgia merged 27 commits intoExpensify:mainfrom
getusha:feat-standardize-pay-button-expense-n-ious-2

Conversation

@getusha
Copy link
Contributor

@getusha getusha commented Jul 21, 2025

Explanation of Change

Standardize pay button for IOUs and Expenses

Fixed Issues

$ #61744 #55874 #55875
PROPOSAL:

Tests

Paying IOU for the first time - preview

  1. Account B: submits an expense to Account A (a US based account)
  2. Account A: Navigate to the expense preview and press on Pay %amount% button
  3. Verify you are presented with 3 options Pay with personal account, Pay with business account, Mark as paid
  4. Press Pay with personal account
  5. Verify that you are prompted to setup a PBA
  6. Complete steps to add PBA
  7. Press on Pay %amount% button
  8. Verify Pay with business account is present
  9. Press Pay with business account
  10. Verify that a collect workspace is created and the IOU is moved to the new workspace chat
  11. Verify that there is a system message that clarifies the IOU is moved
  12. Navigate to the new workspace
  13. Create a new IOU
  14. Verify that there is Pay via %workspace name% option
  15. Press the option in step 14
  16. Verify that the IOU is moved to the existing workspace

Paid IOU before - preview

  1. Account B: Submit an expense to Account A
  2. Account A: Open the chat to Account B and pay the IOU using any payment method
  3. Verify that the recently used payment method is selected
  4. Account B: Submit another expense
  5. Account A: Tap on the default option on the new IOU
  6. Verify that the IOU is paid

Paying expense report for the first time - preview

  • Preconditions
  1. Have a workspace with a preferred currency of USD
    Steps
  2. Submit an expense to the workspace
  3. Login to the admin account and open the expense chat
  4. Verify that the settlement button matches the design below
  5. Press on the settlement button
  6. Verify that there is Pay with business account and Mark as paid.
  7. Verify the expense view has the same pattern

image

Paying expense report for the first time - held expense

  • Preconditions
  1. A workspace with an expense that has held amount
    Steps
  2. Press on the settlement button
  3. Verify that you're presented with a dropdown to select the payment method first
  4. Press on any payment method
  5. Verify you're presented with a modal to confirm whether you want to pay the full or not-held amount.

Paid expense report before - preview

  • Preconditions
  1. A workspace chat with previously paid expenses using a BBA
  2. A new expense report inside the workspace chat
    Steps
  3. Navigate to the workspace chat
  4. Verify the settlement button matches the pattern below

Test 65118:

  1. Go to workspace chat.
  2. Create an expense.
  3. Tap Submit and Approve.
  4. Go to Reports.
  5. Tap Pay.
  6. Select Mark as paid.
  7. Verify that you can pay the expense

Test 65118:

  1. Navigate to "Workspaces" and create a new workspace.
  2. Open "Workflows" and disable delayed submissions and approvals.
  3. Navigate to workspace chat.
  4. Submit a manual expense.
  5. Navigate to "Reports"
  6. Check the size of the "Pay" button.
  7. Return to chat and mark the expense as paid.
  8. Open the expense and cancel the payment.
  9. Return to "Reports"
  10. Verify that the "Pay" button has the same size as before.

Test 65125

  1. Go to staging.new.expensify.com
  2. [User A] Submit an expense to User B.
  3. [User B] Pay the expense.
  4. [User A] Submit another expense to User B.
  5. [User B] Go to Account > Preferences > Language.
  6. [User B] Change the language to Spanish or Dutch
  7. [User B] Go to Reports.
  8. Verify that you see the Pay button.

Test 65120

Precondition:
User B has paid an expense from User A at least once.

  1. Go to staging.new.expensify.com
  2. [User A] Submit an expense to User B.
  3. [User B] Go to Reports > Expenses.
  4. [User B] Go offline.
  5. [User B] Verify that Pay button is disabled.
  • Verify that no errors appear in the JS console

Offline tests

N/a

QA Steps

Same as tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

MacOS: Chrome / Safari

Paying IOU for the first time - preview

Screenshot 2025-04-22 at 6 05 19 in the evening

Paid IOU before - preview

Screenshot 2025-04-22 at 6 05 40 in the evening

Paying expense report for the first time - preview

Screenshot 2025-04-22 at 6 06 00 in the evening

Paying expense report for the first time - held expense

Screen.Recording.2025-04-22.at.6.11.01.in.the.evening.mov

Paid expense report before - preview

Screenshot 2025-04-22 at 6 11 46 in the evening

@getusha getusha changed the title revert the revert and apply fixes feat: standardize pay button for expenses and ious Jul 21, 2025
@getusha getusha changed the title feat: standardize pay button for expenses and ious feat: standardize pay button for expenses and IOUs Jul 21, 2025
@getusha getusha marked this pull request as ready for review July 21, 2025 13:12
@getusha getusha requested review from a team as code owners July 21, 2025 13:12
@melvin-bot melvin-bot bot requested review from rojiphil and removed request for a team July 21, 2025 13:12
@melvin-bot
Copy link

melvin-bot bot commented Jul 21, 2025

@rojiphil Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@getusha
Copy link
Contributor Author

getusha commented Jul 21, 2025

@hungvu193 will review this

@joekaufmanexpensify joekaufmanexpensify requested review from hungvu193 and removed request for rojiphil July 21, 2025 13:18
@github-actions
Copy link
Contributor

🚧 @joekaufmanexpensify has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

🚧 @joekaufmanexpensify has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions
Copy link
Contributor

🧪🧪 Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! 🧪🧪
Built from App PR #66790.

Android 🤖 iOS 🍎
https://ad-hoc-expensify-cash.s3.amazonaws.com/android/66790/index.html https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/66790/index.html
Android iOS
Desktop 💻 Web 🕸️
https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/66790/NewExpensify.dmg https://66790.pr-testing.expensify.com
Desktop Web

👀 View the workflow run that generated this build 👀

@hungvu193
Copy link
Contributor

Still under reviewing 👀

Comment on lines +24 to +29
let lastUsedPaymentMethods: OnyxEntry<LastPaymentMethod>;
Onyx.connect({
key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
callback: (value) => (lastUsedPaymentMethods = value),
});

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe we avoid using Onyx.connect from now on and will remove it completely soon.

FYI: https://expensify.slack.com/archives/C05LX9D6E07/p1748457444774569

I also recently reviewed a PR related to this kind of issue (the function is not PURE function, so when Onyx.connect update the data, the component doesn't re-render).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lets test it thoroughly this required a lot of change :/

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes we stopped using Onyx.connect. We should avoid adding a new call to it in this PR

@grgia grgia self-requested a review July 22, 2025 13:01
@joekaufmanexpensify
Copy link
Contributor

joekaufmanexpensify commented Jul 22, 2025

@joekaufmanexpensify
Copy link
Contributor

Reported another bug here

@hungvu193
Copy link
Contributor

Thanks @joekaufmanexpensify I'll check it today

Comment on lines +2235 to +2239
name: policyID,
},
lastUsed: {
name: policyID,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is correct. Do we need this update at all?

Copy link
Contributor

Choose a reason for hiding this comment

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

We can pay with Policy, as I remember. that's why we saved it here

Copy link
Contributor

Choose a reason for hiding this comment

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

but the name key @hungvu193

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@grgia could you elaborate? please

Copy link
Contributor

Choose a reason for hiding this comment

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

@getusha, shouldn't the name key be a type, not the policyID? ie. CONST.IOU.PAYMENT_TYPE

],
};

if (personalPolicy?.id && !lastUsedPaymentMethod) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We're not making BE updates for the Bank account creation or policy creation.

I opened a BE issue here https://github.com/Expensify/Expensify/issues/528452

This is the list of affected commands

  • ConnectBankAccountWithPlaid
  • AddPersonalBankAccount
  • RestartBankAccountSetUp
  • DeletePaymentBankAccount
  • DeleteWorkspace
  • MoveIOUReportToPolicyAndInviteSubmitter
  • MoveIOUReportToPolicy

Copy link
Contributor

@jnowakow jnowakow Jul 24, 2025

Choose a reason for hiding this comment

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

Quick question: lastUsedPaymentMethod is object with several options. For example, if lastUsedPaymentMethod = {expense: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, don’t we want to update iou in this case?

Copy link
Contributor

@jnowakow jnowakow Jul 24, 2025

Choose a reason for hiding this comment

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

So shouldn't it look like this?

    if (personalPolicy?.id && !lastUsedPaymentMethod?.iou) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @jnowakow!
@grgia any changes required here?

@joekaufmanexpensify
Copy link
Contributor

Found one more bug where deleting a workspace is broken. Reported here.

@getusha getusha requested a review from grgia August 1, 2025 16:48
Copy link
Contributor

@grgia grgia left a comment

Choose a reason for hiding this comment

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

🤞

@grgia grgia removed the request for review from marcaaron August 1, 2025 16:49
@grgia grgia merged commit 4515ef6 into Expensify:main Aug 1, 2025
19 checks passed
@Julesssss
Copy link
Contributor

I think this PR introduced the broken test check on main.

@getusha
Copy link
Contributor Author

getusha commented Aug 1, 2025

@Julesssss PR to fix it is up

@github-actions
Copy link
Contributor

github-actions bot commented Aug 4, 2025

🚀 Deployed to staging by https://github.com/grgia in version: 9.1.89-1 🚀

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

@grgia
Copy link
Contributor

grgia commented Aug 6, 2025

Note follow up: #68027
There is bug on production that is unrelated to this PR that we are looking into

@github-actions
Copy link
Contributor

github-actions bot commented Aug 6, 2025

🚀 Deployed to production by https://github.com/jasperhuangg in version: 9.1.89-21 🚀

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

savePreferredPaymentMethod(policyIDKey, option.value);
}}
options={paymentButtonOptions}
onOptionSelected={(option) => handlePaymentSelection(undefined, option.value, triggerKYCFlow)}
Copy link
Contributor

Choose a reason for hiding this comment

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

This change caused

We should only call handlePaymentSelection if paymentButtonOptions.length === 1

return {
text: title ?? '',
description: description ?? '',
icon: typeof icon === 'number' ? Bank : icon,
Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @getusha, why do we need this change? Is SVG icon broken on native?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes strangely icon has a number value on android Native. Tried to investigate but i wasn't able to get far with the RCA.

<View style={[styles.alignItemsCenter, styles.flexColumn, styles.flexShrink1]}>
{primaryText}
<Text style={[isLoading && styles.opacity0, styles.pointerEventsNone, styles.fontWeightNormal, styles.textDoubleDecker]}>{secondLineText}</Text>
<Text
Copy link
Contributor

Choose a reason for hiding this comment

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

We neglected to allow truncation here, so we fixed it in #70837

};

const approveButtonOption = {
text: translate('iou.approve', {formattedAmount}),
Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @getusha @hungvu193 , do you know if there are any steps to follow to access this button on the UI? We’re working on a PR and need to remove the amount from the approve buttons when necessary. If we can see where this button appears in the app, it’ll be easier to determine whether we want to remove it or not.

Comment on lines +241 to +245
buttonOptions.push({
text: latestBankItem.at(0)?.text ?? '',
icon: latestBankItem.at(0)?.icon,
value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT,
description: latestBankItem.at(0)?.description,
Copy link
Contributor

Choose a reason for hiding this comment

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

{BZ Checklist #71298 } We don't pass the iconStyles and iconSize here, so Bank account icon has the wrong style in the settlement button

if (!policyExpenseChatReportID) {
const {policyExpenseChatReportID: newPolicyExpenseChatReportID} = moveIOUReportToPolicyAndInviteSubmitter(iouReport.reportID, policy.id, formatPhoneNumber) ?? {};
savePreferredPaymentMethod(iouReport.policyID, policy.id, CONST.LAST_PAYMENT_METHOD.IOU, lastPaymentMethod?.[policy.id]);
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(newPolicyExpenseChatReportID));
Copy link
Contributor

Choose a reason for hiding this comment

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

{BZ CHECKLIST} there was a regression, the navigation occurs too soon—before the optimistic report from moveIOUReportToPolicyAndInviteSubmitter is fully merged into Onyx, this was fixed using setNavigationActionToMicrotaskQueue

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.