Skip to content

feat(ramp): surface headless buy errors as data (Phase 7)#29612

Merged
saustrie-consensys merged 3 commits into
mainfrom
saustrie-consensys/headless-buy-phase-7
May 6, 2026
Merged

feat(ramp): surface headless buy errors as data (Phase 7)#29612
saustrie-consensys merged 3 commits into
mainfrom
saustrie-consensys/headless-buy-phase-7

Conversation

@saustrie-consensys
Copy link
Copy Markdown
Contributor

@saustrie-consensys saustrie-consensys commented May 1, 2026

Description

This PR closes Phase 7 of the incremental Unified Buy (v2) headless buy plan (app/components/UI/Ramp/headless/PLAN.md): headless consumers now receive structured HeadlessBuyError data for hard failures instead of depending on Ramp UI surfaces like banners, ErrorViews, or order toasts.

Reason

  • Phase 6 (#29340) fired onOrderCreated and bypassed the order-details redirect on success, but several failure paths were still UI-coupled. Limit failures could be wrapped into generic display errors, Checkout/WebView failures rendered local UI, and one Transak success path could still show a toast before a headless consumer regained control.

What changed

  • HeadlessBuyErrorCode + failSession — centralizes error normalization in sessionRegistry, preserving explicit error codes/details and closing the session with failed terminal semantics after onError fires.
  • HeadlessHost — uses failSession for auth errors, malformed asset ids, and continueWithQuote rejections so the consumer receives one structured onError callback and one terminal close.
  • useTransakRouting — preserves LimitExceededError as LIMIT_EXCEEDED, forwards checkout-processing failures through onError, and suppresses the manual-bank-transfer toast path when a live headless session owns the flow.
  • Checkout — routes callback-processing failures and primary WebView HTTP errors through onError for headless sessions, then unwinds the ramp stack instead of rendering the checkout ErrorView.
  • BuildQuote — keeps legacy headless params from falling back to banner-only error handling if they are encountered.
  • PLAN.md — marks Phase 7 complete.

References

  • Stacked on Phase 6: #29340 (poc/headless-buy-phase-6). This PR's base is poc/headless-buy-phase-6 so the diff is Phase 7-only.
  • Continues from Phase 5: #29338 (Headless Host + quote-first start).

Tests

  • yarn eslint app/components/UI/Ramp/headless/types.ts app/components/UI/Ramp/headless/sessionRegistry.ts app/components/UI/Ramp/headless/sessionRegistry.test.ts app/components/UI/Ramp/Views/HeadlessHost/HeadlessHost.tsx app/components/UI/Ramp/Views/HeadlessHost/HeadlessHost.test.tsx app/components/UI/Ramp/hooks/useTransakRouting.ts app/components/UI/Ramp/hooks/useTransakRouting.test.ts app/components/UI/Ramp/Views/Checkout/Checkout.tsx app/components/UI/Ramp/Views/Checkout/Checkout.test.tsx app/components/UI/Ramp/Views/BuildQuote/BuildQuote.tsx
  • yarn jest --watchman=false app/components/UI/Ramp/headless/sessionRegistry.test.ts app/components/UI/Ramp/Views/HeadlessHost/HeadlessHost.test.tsx app/components/UI/Ramp/hooks/useTransakRouting.test.ts app/components/UI/Ramp/Views/Checkout/Checkout.test.tsx app/components/UI/Ramp/Views/BuildQuote/BuildQuote.test.tsx
  • yarn lint:tsc was attempted, but the local run is blocked by unrelated existing type errors in SocialLeaderboard tests and controller messenger types.

Changelog

CHANGELOG entry: null

Related issues

Fixes: No GitHub issue — incremental POC on branch saustrie-consensys/headless-buy-phase-7.

Continuity: #29340 (Phase 6 — headless order success callback + stack unwind). #29338 (Phase 5 — Headless Host + quote-first start).

Manual testing steps

Feature: Headless Buy Phase 7 (structured errors)

  Scenario: Native Transak limit failure surfaces as data
    Given the app is an internal build and I am signed in
    And I open Settings → Fiat on-ramp → Headless Buy playground
    When I start a headless native quote that exceeds the user's Transak limit
    Then the playground event log should show `onError({ code: "LIMIT_EXCEEDED" })`
    And the session should close without showing a Ramp-only toast or order-details redirect

  Scenario: Aggregator Checkout failure surfaces as data
    Given I start a headless aggregator quote from the playground
    When the Checkout callback or primary WebView request fails
    Then the consumer should receive `onError({ code: "UNKNOWN", message })`
    And the app should unwind out of the Ramp stack instead of rendering the Checkout ErrorView

  Scenario: Non-headless Buy flow is unchanged
    Given I open Wallet → Buy through the regular flow
    When a quote, checkout, or limit error occurs
    Then the existing Ramp UI surfaces should render as before

Screenshots/Recordings

Before

N/A — Phase 7 changes error/callback plumbing only.

After

N/A — no user-facing UI changes, but here's a video anyways.

Screen.Recording.2026-05-06.at.4.02.27.PM.mov

Pre-merge author checklist

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.

Note

Medium Risk
Changes error handling and navigation unwinding for headless Unified Buy flows across HeadlessHost, Checkout, and useTransakRouting, which could affect session lifecycle and user recovery paths if misrouted. Scope is contained to headless-mode branches with added test coverage, but touches core buy/checkout flow control.

Overview
Headless buy errors are now surfaced as structured data instead of Ramp UI. A new failSession helper in headless/sessionRegistry normalizes thrown/native errors into HeadlessBuyError (including LIMIT_EXCEEDED mapping and optional details), fires onError, and closes the session with failed terminal semantics.

Headless flows now consistently use this failure path: HeadlessHost forwards auth/asset/continue failures via failSession, Checkout sends callback-processing and primary WebView HTTP errors through onError and pops the ramp stack instead of rendering an ErrorView, and useTransakRouting preserves LimitExceededError details, suppresses toasts when a live headless session is present, and routes post-checkout processing failures through failSession + stack unwind. Tests are updated/added to cover these headless-specific error paths and regression guards.

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

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.

@metamaskbotv2 metamaskbotv2 Bot added the team-money-movement issues related to Money Movement features label May 1, 2026
@github-actions github-actions Bot added the size-L label May 1, 2026
Base automatically changed from poc/headless-buy-phase-6 to main May 5, 2026 13:54
…adless-buy-phase-7

# Conflicts:
#	app/components/UI/Ramp/Views/Checkout/Checkout.test.tsx
#	app/components/UI/Ramp/Views/Checkout/Checkout.tsx
#	app/components/UI/Ramp/headless/PLAN.md
#	app/components/UI/Ramp/hooks/useTransakRouting.test.ts
#	app/components/UI/Ramp/hooks/useTransakRouting.ts
@saustrie-consensys saustrie-consensys marked this pull request as ready for review May 6, 2026 13:47
@saustrie-consensys saustrie-consensys requested a review from a team as a code owner May 6, 2026 13:47
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeMoney
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
All 11 changed files are within the Ramp/fiat on-ramp feature area (app/components/UI/Ramp/). The changes implement Phase 7 of the headless buy feature: routing errors through onError as typed HeadlessBuyError objects.

Key changes:

  1. sessionRegistry.ts: New failSession() function and toHeadlessBuyError() helper that standardize error handling across headless buy sessions. New HeadlessBuyErrorCode type extracted from inline union.
  2. BuildQuote.tsx: Integrates failSession() for auth failures (nativeFlowError) and quote errors in headless sessions.
  3. Checkout.tsx: New failHeadlessCheckout() callback using failSession() for WebView HTTP errors and callback processing failures, with parent navigation pop.
  4. HeadlessHost.tsx: Refactored from manual onError + closeSession calls to unified failSession() usage.
  5. useTransakRouting.ts: Enhanced LimitExceededError with headlessBuyErrorCode property and structured details, added headless-specific order processing path, integrated failSession() for post-checkout errors.
  6. types.ts: Minor refactor extracting HeadlessBuyErrorCode as standalone type.
  7. PLAN.md: Documentation update.
  8. Test files: Unit tests for the above changes.

These changes affect the fiat on-ramp buy flow error handling paths, which are tested by SmokeMoney. The changes could affect: buy flow error states, limit exceeded handling, auth failure propagation, and WebView error handling. No other feature areas (swap confirmations, wallet platform, network, accounts) are touched. SmokeMoney's description covers 'ramps: unified buy, sell/off-ramp, region-aware flows' which directly covers these code paths.

Per SmokeMoney tag description: 'When selecting SmokeMoney for Card Add Funds or similar flows that execute swaps, also select SmokeSwap and SmokeConfirmations.' However, these changes are specifically about error handling in the headless buy session registry, not about swap execution or confirmation flows. The error paths being modified are failure/cancellation paths, not the happy path that triggers confirmations. Therefore, SmokeConfirmations is not required here.

Performance Test Selection:
The changes are focused on error handling logic in the headless buy session registry and related Ramp views/hooks. No UI rendering performance, list rendering, animations, data loading, or app startup code is modified. The changes are purely about error propagation paths (failSession, toHeadlessBuyError) and do not affect any performance-sensitive code paths measured by the available performance test tags.

View GitHub Actions results

@saustrie-consensys saustrie-consensys self-assigned this May 6, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 6, 2026

@saustrie-consensys saustrie-consensys added this pull request to the merge queue May 6, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 6, 2026
@saustrie-consensys saustrie-consensys added this pull request to the merge queue May 6, 2026
Merged via the queue into main with commit dba2c63 May 6, 2026
96 of 97 checks passed
@saustrie-consensys saustrie-consensys deleted the saustrie-consensys/headless-buy-phase-7 branch May 6, 2026 17:07
@github-actions github-actions Bot locked and limited conversation to collaborators May 6, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.77.0 Issue or pull request that will be included in release 7.77.0 label May 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.77.0 Issue or pull request that will be included in release 7.77.0 size-L team-money-movement issues related to Money Movement features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants