feat: Add fiat payment confirmation flow with headless ramp integration placeholder#28152
Conversation
MetaMask#29906) ## **Description** Adds a disabled/loading state to all interactive components on the MMPay fiat confirmation screen while `startHeadlessBuy` is in progress. This prevents user interaction during order preparation and provides clear visual feedback via muted styling and a native loading spinner on the CTA button. Note that `isHeadlessBuyInProgress` is not currently used but it will be integrated on MetaMask#28152 ### Changes - **`ConfirmationContext`**: Added `isHeadlessBuyInProgress` boolean + setter to shared context so all target components can read the state directly. - **`ConfirmButton`**: Uses the design system `Button`'s built-in `isLoading` / `loadingText` props to show a spinner with "Preparing order" text. - **`CustomAmount`**: Disables the amount input keyboard when loading. - **`PayWithRow`**: Disables the token picker row; applies `TextColor.Muted` / `IconColor.Muted` for consistent dimming while preserving layout (arrow-down icon stays visible). - **`BridgeFeeRow`**: Disables the tooltip ⓘ button and applies muted text/icon colors. - **`TotalRow`**: Applies muted text colors to label and value. - **`Tooltip`**: Added `disabled` prop that guards `handlePress` and passes `disabled` to the underlying `ButtonIcon` for true non-interactivity. - **`InfoRow` / `AlertRow`**: Added `tooltipDisabled` prop forwarded to `Tooltip`; fixed `AlertRow` to only override label `variant` when an alert is active (respects caller's prop otherwise). - **Locale**: Added `confirm.preparing_order` string. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: MMPay fiat loading state Scenario: Confirmation screen components are disabled during headless buy Given I am on the MMPay confirmation screen with a fiat payment method selected When I tap the confirm/add funds button to trigger startHeadlessBuy Then the CTA button shows a loading spinner with "Preparing order" text And the amount input keyboard is non-interactive And the "Pay with" row is dimmed and non-tappable And the "Transaction fee" row tooltip icon is dimmed and non-tappable And the "Total" row text is dimmed And no layout shifts occur on any row ``` ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/7a227e71-562b-45a2-9ebd-76c3da45bf6c ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - [ ] I've tested with a power user scenario - [ ] I've instrumented key operations with Sentry traces for production performance metrics ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches confirmation-screen interaction/CTA enablement by introducing a new shared loading flag that disables inputs and shows a loading state; risk is mainly UX/flow regressions if the flag is set/cleared incorrectly. > > **Overview** > Adds a new `isHeadlessBuyInProgress` flag (and setter) to `ConfirmationContext` and threads it through MM Pay fiat confirmation components to **freeze user interaction during order preparation**. > > While this flag is true, the confirm CTA is disabled and shows a native loading spinner with new copy (`confirm.preparing_order`), and interactive rows/inputs (custom amount, pay-with picker, transaction-fee tooltip) are disabled with muted styling. Also fixes `AlertRow` to only override the `variant` color when an alert is present, and extends `Tooltip`/`InfoRow` with a `disabled`/`tooltipDisabled` path to prevent opening tooltips. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit cd87e35. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
| assetId, | ||
| amount: amountFiat, | ||
| paymentMethodId: fiatPayment?.selectedPaymentMethodId, | ||
| currency: 'USD', |
There was a problem hiding this comment.
Hardcoded 'USD' currency overrides user's actual fiat currency
High Severity
The currency parameter passed to startHeadlessBuy is hardcoded to 'USD'. The HeadlessBuyParams type documents that currency is optional and "when omitted, the Host falls back to the active user region's currency — same default BuildQuote uses." Since amountFiat comes from the fiat payment state and could reflect a non-USD currency (EUR, GBP, etc.), hardcoding 'USD' causes a currency mismatch — e.g., a European user's €50 amount would be interpreted as $50 USD. Omitting the field (or passing undefined) would let the Host use the correct fallback.
Reviewed by Cursor Bugbot for commit fc01f59. Configure here.
There was a problem hiding this comment.
Is it the case because we are only supporting USD, right?
There was a problem hiding this comment.
Yes exactly, we are hardcoding it to 'USD' in MMPay context regardless of the user locale currency
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7662e33. Configure here.
| supportAccountSelection && !accountOverride; | ||
|
|
||
| const { headlessBuyError } = useConfirmationContext(); | ||
|
|
There was a problem hiding this comment.
Alert styling inconsistent with headless buy error display
Low Severity
The hasAlert prop on CustomAmount is computed as Boolean(alertMessage) and does not account for headlessBuyError, but the AlertMessage component below receives alertMessage ?? headlessBuyError. When a headless buy error exists but no blocking alert is present, the error text renders but CustomAmount won't reflect the alert state visually (e.g. error-colored text), creating an inconsistent UI.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7662e33. Configure here.
| default: | ||
| return 'Pending'; | ||
| } | ||
| } |
There was a problem hiding this comment.
Status text hardcoded in English, not localized
Medium Severity
getStatusText() returns hardcoded English strings ("Completed", "Failed", "Cancelled", "Expired", "Pending") that are displayed to users in the subtitle. The sibling code in fiat-order-summary-line.tsx correctly uses the strings() i18n function for the title, so non-English users will see a mix of localized and non-localized text in the same UI element.
Reviewed by Cursor Bugbot for commit 7662e33. Configure here.
|




Description
Add fiat payment confirmation flow with headless ramp integration placeholder
Summary
startHeadlessBuycall is placeholder — will be uncommented once the Ramps team delivers the headless API)orderCodelands in state,useTransactionPayAutoFiatSubmissionauto-submits the transaction so theFiatStrategyinTransactionPayControllercan poll the order and relay fundsorderCodeand retries on failuregetStrategyin controller init now routes transactions with a selected fiat payment method toFiatStrategyinstead of the defaultRelayFiatStrategysubmit flow with order polling and relay execution core#8347 —@metamask/transaction-pay-controllerdep bump required before mergeChangelog
CHANGELOG entry:
Related issues
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Changes the transaction confirmation path to branch into a new fiat purchase kickoff and auto-submission flow, plus adds polling against
RampsController.getOrder; regressions could block confirmations or cause duplicate/failed submissions if edge cases are missed.Overview
Adds a fiat payment confirmation flow: when fiat is selected and no
orderIdexists,useTransactionConfirmnow triggersuseFiatConfirm(headless ramp start + persistorderId+ error/in-progress state) instead of immediately submitting the transaction, anduseTransactionPayAutoFiatSubmissionlater auto-callsonConfirm({ existingOrderId })once theorderIdappears (deduped per order, retry on failure).Surfaces fiat order UX and errors by extending
ConfirmationContextwithheadlessBuyError, adding a new blocking alert (AlertKeys.HeadlessBuyError) + metrics mapping + i18n strings, and wiring the error intoCustomAmountInfo’sAlertMessage.Adds fiat order status visibility in activity details via
FiatOrderSummaryLineand a newuseFiatOrderStatushook that pollsRampsController.getOrderuntil terminal states; also updates engine messenger action allowlists to include missingRampsController/pay-related actions and bumps@metamask/transaction-controllerand@metamask/transaction-pay-controllerdeps.Reviewed by Cursor Bugbot for commit 7662e33. Bugbot is set up for automated code reviews on this repo. Configure here.