Commit dba2c63
authored
feat(ramp): surface headless buy errors as data (Phase 7) (#29612)
## **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](#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](#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](#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](#29340) (Phase
6 — headless order success callback + stack unwind).
[#29338](#29338) (Phase
5 — Headless Host + quote-first start).
## **Manual testing steps**
```gherkin
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.
https://github.com/user-attachments/assets/2ce3a5a7-7205-4490-9174-8be7672ae464
## **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
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I've applied the right labels on the PR
#### 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**
> 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.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7bdaa03. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: saustrie-consensys <270766059+saustrie-consensys@users.noreply.github.com>1 parent 3de0bcc commit dba2c63
11 files changed
Lines changed: 469 additions & 64 deletions
File tree
- app/components/UI/Ramp
- Views
- BuildQuote
- Checkout
- HeadlessHost
- headless
- hooks
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| |||
159 | 160 | | |
160 | 161 | | |
161 | 162 | | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
162 | 177 | | |
163 | 178 | | |
164 | 179 | | |
165 | | - | |
| 180 | + | |
166 | 181 | | |
167 | 182 | | |
168 | 183 | | |
| |||
627 | 642 | | |
628 | 643 | | |
629 | 644 | | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
630 | 648 | | |
631 | 649 | | |
632 | 650 | | |
| |||
642 | 660 | | |
643 | 661 | | |
644 | 662 | | |
| 663 | + | |
645 | 664 | | |
646 | 665 | | |
647 | 666 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
618 | 619 | | |
619 | 620 | | |
620 | 621 | | |
| 622 | + | |
| 623 | + | |
621 | 624 | | |
622 | 625 | | |
623 | 626 | | |
| |||
641 | 644 | | |
642 | 645 | | |
643 | 646 | | |
| 647 | + | |
644 | 648 | | |
645 | 649 | | |
646 | 650 | | |
| |||
740 | 744 | | |
741 | 745 | | |
742 | 746 | | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
743 | 793 | | |
744 | 794 | | |
745 | 795 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
31 | 35 | | |
32 | 36 | | |
33 | 37 | | |
| |||
120 | 124 | | |
121 | 125 | | |
122 | 126 | | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
123 | 139 | | |
124 | 140 | | |
125 | 141 | | |
| |||
234 | 250 | | |
235 | 251 | | |
236 | 252 | | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
237 | 256 | | |
238 | 257 | | |
239 | 258 | | |
| |||
248 | 267 | | |
249 | 268 | | |
250 | 269 | | |
| 270 | + | |
251 | 271 | | |
252 | 272 | | |
253 | 273 | | |
| |||
344 | 364 | | |
345 | 365 | | |
346 | 366 | | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
347 | 370 | | |
348 | 371 | | |
349 | 372 | | |
| |||
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
348 | 348 | | |
349 | 349 | | |
350 | 350 | | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
351 | 370 | | |
352 | 371 | | |
353 | 372 | | |
| |||
Lines changed: 11 additions & 48 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
| 135 | + | |
149 | 136 | | |
150 | | - | |
151 | 137 | | |
152 | | - | |
| 138 | + | |
| 139 | + | |
153 | 140 | | |
| 141 | + | |
154 | 142 | | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
155 | 146 | | |
156 | 147 | | |
157 | 148 | | |
| |||
202 | 193 | | |
203 | 194 | | |
204 | 195 | | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
211 | | - | |
212 | | - | |
213 | 196 | | |
214 | 197 | | |
215 | 198 | | |
216 | 199 | | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
| 200 | + | |
224 | 201 | | |
225 | 202 | | |
226 | 203 | | |
| |||
270 | 247 | | |
271 | 248 | | |
272 | 249 | | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
| 250 | + | |
| 251 | + | |
289 | 252 | | |
290 | 253 | | |
291 | 254 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
0 commit comments