Skip to content

feat: Add fiat payment confirmation flow with headless ramp integration placeholder#28152

Merged
OGPoyraz merged 34 commits into
mainfrom
ogp/CONF-1065
May 14, 2026
Merged

feat: Add fiat payment confirmation flow with headless ramp integration placeholder#28152
OGPoyraz merged 34 commits into
mainfrom
ogp/CONF-1065

Conversation

@OGPoyraz

@OGPoyraz OGPoyraz commented Mar 31, 2026

Copy link
Copy Markdown
Member

Description

Add fiat payment confirmation flow with headless ramp integration placeholder

Summary

  • When a user confirms a transaction with fiat payment selected and no order yet, the confirm button now triggers the ramp purchase flow instead of submitting the transaction directly (startHeadlessBuy call is placeholder — will be uncommented once the Ramps team delivers the headless API)
  • After ramp purchase completes and orderCode lands in state, useTransactionPayAutoFiatSubmission auto-submits the transaction so the FiatStrategy in TransactionPayController can poll the order and relay funds
  • Auto-submission is deduplicated per orderCode and retries on failure
  • getStrategy in controller init now routes transactions with a selected fiat payment method to FiatStrategy instead of the default Relay
  • Non-fiat confirmation flow is completely unchanged
  • Depends on feat: Implement FiatStrategy submit flow with order polling and relay execution core#8347@metamask/transaction-pay-controller dep bump required before merge

Changelog

CHANGELOG entry:

Related issues

Fixes:

Manual testing steps

Feature: my feature name

  Scenario: user [verb for user action]
    Given [describe expected initial app state]

    When user [verb for user action]
    Then [describe expected outcome]

Screenshots/Recordings

Before

After

Pre-merge author checklist

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.

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 orderId exists, useTransactionConfirm now triggers useFiatConfirm (headless ramp start + persist orderId + error/in-progress state) instead of immediately submitting the transaction, and useTransactionPayAutoFiatSubmission later auto-calls onConfirm({ existingOrderId }) once the orderId appears (deduped per order, retry on failure).

Surfaces fiat order UX and errors by extending ConfirmationContext with headlessBuyError, adding a new blocking alert (AlertKeys.HeadlessBuyError) + metrics mapping + i18n strings, and wiring the error into CustomAmountInfo’s AlertMessage.

Adds fiat order status visibility in activity details via FiatOrderSummaryLine and a new useFiatOrderStatus hook that polls RampsController.getOrder until terminal states; also updates engine messenger action allowlists to include missing RampsController/pay-related actions and bumps @metamask/transaction-controller and @metamask/transaction-pay-controller deps.

Reviewed by Cursor Bugbot for commit 7662e33. Bugbot is set up for automated code reviews on this repo. Configure here.

@metamaskbot metamaskbot added the team-confirmations Push issues to confirmations team label Mar 31, 2026
@github-actions github-actions Bot added size-XL and removed size-M labels May 11, 2026
pull Bot pushed a commit to Reality2byte/metamask-mobile that referenced this pull request May 11, 2026
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 -->
@OGPoyraz OGPoyraz marked this pull request as ready for review May 11, 2026 12:27
@OGPoyraz OGPoyraz requested review from a team as code owners May 11, 2026 12:27
@OGPoyraz OGPoyraz added the no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed label May 12, 2026
Comment thread tests/component-view/render.tsx Outdated
Comment thread app/components/Views/confirmations/hooks/transactions/useTransactionConfirm.ts Outdated
@OGPoyraz OGPoyraz added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label May 13, 2026
Comment thread tests/component-view/render.tsx
@socket-security

socket-security Bot commented May 14, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​metamask/​controller-utils@​12.0.0 ⏵ 12.1.097 +110077 +196 +2100
Updatednpm/​@​metamask/​account-tree-controller@​7.3.0 ⏵ 7.4.098 +110078 +198 +1100
Updatednpm/​@​metamask/​transaction-pay-controller@​22.3.0 ⏵ 22.4.09710081 +198 +1100

View full report

@github-actions

Copy link
Copy Markdown
Contributor

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',

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc01f59. Configure here.

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 the case because we are only supporting USD, right?

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.

Yes exactly, we are hardcoding it to 'USD' in MMPay context regardless of the user locale currency

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeIdentity, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeSwap, SmokeStake, SmokeWalletPlatform, SmokeMoney, SmokePerps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, SmokeBrowser, SmokeSnaps
  • Selected Performance tags: @PerformanceAccountList, @PerformanceOnboarding, @PerformanceLogin, @PerformanceSwaps, @PerformanceLaunch, @PerformanceAssetLoading, @PerformancePredict, @PerformancePreps
  • Risk Level: high
  • AI Confidence: 100%
click to see 🤖 AI reasoning details

E2E Test Selection:
Hard rule (controller-version-update): @MetaMask controller package version updated in package.json: @metamask/transaction-controller, @metamask/transaction-pay-controller. Running all tests.

Performance Test Selection:
Hard rule (controller-version-update): @MetaMask controller package version updated in package.json: @metamask/transaction-controller, @metamask/transaction-pay-controller. Running all tests.

View GitHub Actions results

@OGPoyraz OGPoyraz enabled auto-merge May 14, 2026 11:09

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ 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();

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.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7662e33. Configure here.

default:
return 'Pending';
}
}

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7662e33. Configure here.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
4.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@OGPoyraz OGPoyraz added this pull request to the merge queue May 14, 2026
Merged via the queue into main with commit a75f9df May 14, 2026
112 of 116 checks passed
@OGPoyraz OGPoyraz deleted the ogp/CONF-1065 branch May 14, 2026 12:39
@github-actions github-actions Bot locked and limited conversation to collaborators May 14, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.78.0 Issue or pull request that will be included in release 7.78.0 label May 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed release-7.78.0 Issue or pull request that will be included in release 7.78.0 size-XL skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-confirmations Push issues to confirmations team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants