Skip to content

Commit 46aa1dd

Browse files
committed
chore(runway): cherry-pick fix(ramp): cp-7.60.0 fix phone already registered error detection in BasicInfo form (#23299)
## **Description** Fixed error handling in BasicInfo component to correctly access error codes from Axios error responses. The code was previously trying to access `error.error.errorCode` but Axios errors have the structure `error.response.data.error.errorCode`, which prevented error code 2020 (phone already registered) from being detected correctly. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: #23302 ## **Manual testing steps** ```gherkin Feature: BasicInfo form error handling Scenario: user submits form with already registered phone number Given I am on the BasicInfo screen with valid form data When I submit the form with a phone number that is already registered Then I should see an error message indicating the phone is already registered And I should see a "Log in with email" button And clicking the logout button should navigate to the email entry screen ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <img width="333" height="720" alt="image" src="https://github.com/user-attachments/assets/0a01a7be-e4bb-496c-8396-e77d5785b668" /> ### **After** https://github.com/user-attachments/assets/e8acba92-1032-4f49-a5bd-84a605bda71f ## **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 - [x] 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. ## **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] > Fixes BasicInfo to read Axios error response for code 2020, format the email-based message, and expose a logout-to-email flow; updates tests to use AxiosError and cover these behaviors. > > - **UI (Deposit BasicInfo)**: > - Parse Axios errors via `response.data.error` to detect Transak `errorCode` `2020` and set `isPhoneRegisteredError`. > - Extract email from error message to show localized `phone_already_registered` banner text. > - Show "Log in with email" action on the error banner and navigate to `Routes.DEPOSIT.ENTER_EMAIL`; gracefully handle logout failures. > - Minor: add `AxiosError` typing and default fallback message. > - **Tests** (`BasicInfo.test.tsx`): > - Replace custom errors with `AxiosError` shaped responses; add/import Axios types. > - Verify formatted message, presence/absence of logout button by error type, navigation on logout, and error handling when logout fails. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0961bb6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3bdc120 commit 46aa1dd

File tree

2 files changed

+103
-46
lines changed

2 files changed

+103
-46
lines changed

app/components/UI/Ramp/Deposit/Views/BasicInfo/BasicInfo.test.tsx

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { createSsnInfoModalNavigationDetails } from '../Modals/SsnInfoModal';
99
import { BuyQuote } from '@consensys/native-ramps-sdk';
1010
import { endTrace } from '../../../../../../util/trace';
1111
import Logger from '../../../../../../util/Logger';
12+
import { AxiosError, type InternalAxiosRequestConfig } from 'axios';
1213
import {
1314
MOCK_REGIONS,
1415
MOCK_US_REGION,
@@ -392,14 +393,27 @@ describe('BasicInfo Component', () => {
392393
});
393394

394395
it('displays logout button when error has errorCode 2020', async () => {
395-
// Mock Transak API error structure: { error: { errorCode: 2020, message: "..." } }
396-
const error2020 = Object.assign(new Error('API Error'), {
397-
error: {
398-
errorCode: 2020,
399-
message:
400-
'This phone number is already registered. It has been used by an account created with k****@pedalsup.com. Login with this email to continue.',
396+
// Mock Transak API error structure: { response: { data: { error: { errorCode: 2020, message: "..." } } } }
397+
const errorMessage =
398+
'This phone number is already registered. It has been used by an account created with k****@pedalsup.com. Login with this email to continue.';
399+
const error2020 = new AxiosError(
400+
errorMessage,
401+
'ERR_BAD_REQUEST',
402+
undefined,
403+
undefined,
404+
{
405+
data: {
406+
error: {
407+
errorCode: 2020,
408+
message: errorMessage,
409+
},
410+
},
411+
status: 400,
412+
statusText: 'Bad Request',
413+
headers: {},
414+
config: {} as InternalAxiosRequestConfig,
401415
},
402-
});
416+
);
403417
mockPostKycForm.mockRejectedValueOnce(error2020);
404418

405419
render(BasicInfo);
@@ -435,13 +449,26 @@ describe('BasicInfo Component', () => {
435449

436450
it('displays formatted error message for errorCode 2020', async () => {
437451
// Mock Transak API error structure with email in message
438-
const error2020 = Object.assign(new Error('API Error'), {
439-
error: {
440-
errorCode: 2020,
441-
message:
442-
'This phone number is already registered. It has been used by an account created with k****@pedalsup.com. Login with this email to continue.',
452+
const errorMessage =
453+
'This phone number is already registered. It has been used by an account created with k****@pedalsup.com. Login with this email to continue.';
454+
const error2020 = new AxiosError(
455+
errorMessage,
456+
'ERR_BAD_REQUEST',
457+
undefined,
458+
undefined,
459+
{
460+
data: {
461+
error: {
462+
errorCode: 2020,
463+
message: errorMessage,
464+
},
465+
},
466+
status: 400,
467+
statusText: 'Bad Request',
468+
headers: {},
469+
config: {} as InternalAxiosRequestConfig,
443470
},
444-
});
471+
);
445472
mockPostKycForm.mockRejectedValueOnce(error2020);
446473

447474
render(BasicInfo);
@@ -505,13 +532,26 @@ describe('BasicInfo Component', () => {
505532
});
506533

507534
it('calls logoutFromProvider and navigates to EnterEmail on logout click', async () => {
508-
const error2020 = Object.assign(new Error('API Error'), {
509-
error: {
510-
errorCode: 2020,
511-
message:
512-
'This phone number is already registered. It has been used by an account created with test@gmail.com. Login with this email to continue.',
535+
const errorMessage =
536+
'This phone number is already registered. It has been used by an account created with test@gmail.com. Login with this email to continue.';
537+
const error2020 = new AxiosError(
538+
errorMessage,
539+
'ERR_BAD_REQUEST',
540+
undefined,
541+
undefined,
542+
{
543+
data: {
544+
error: {
545+
errorCode: 2020,
546+
message: errorMessage,
547+
},
548+
},
549+
status: 400,
550+
statusText: 'Bad Request',
551+
headers: {},
552+
config: {} as InternalAxiosRequestConfig,
513553
},
514-
});
554+
);
515555
mockPostKycForm.mockRejectedValueOnce(error2020);
516556

517557
render(BasicInfo);
@@ -553,13 +593,26 @@ describe('BasicInfo Component', () => {
553593
const logoutError = new Error('Logout failed');
554594
mockLogoutFromProvider.mockRejectedValueOnce(logoutError);
555595

556-
const error2020 = Object.assign(new Error('API Error'), {
557-
error: {
558-
errorCode: 2020,
559-
message:
560-
'This phone number is already registered. It has been used by an account created with d***@example.com. Login with this email to continue.',
596+
const errorMessage =
597+
'This phone number is already registered. It has been used by an account created with d***@example.com. Login with this email to continue.';
598+
const error2020 = new AxiosError(
599+
errorMessage,
600+
'ERR_BAD_REQUEST',
601+
undefined,
602+
undefined,
603+
{
604+
data: {
605+
error: {
606+
errorCode: 2020,
607+
message: errorMessage,
608+
},
609+
},
610+
status: 400,
611+
statusText: 'Bad Request',
612+
headers: {},
613+
config: {} as InternalAxiosRequestConfig,
561614
},
562-
});
615+
);
563616
mockPostKycForm.mockRejectedValueOnce(error2020);
564617

565618
render(BasicInfo);

app/components/UI/Ramp/Deposit/Views/BasicInfo/BasicInfo.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import Logger from '../../../../../../util/Logger';
4949
import BannerAlert from '../../../../../../component-library/components/Banners/Banner/variants/BannerAlert/BannerAlert';
5050
import { BannerAlertSeverity } from '../../../../../../component-library/components/Banners/Banner/variants/BannerAlert/BannerAlert.types';
5151
import { useRegions } from '../../hooks/useRegions';
52+
import type { AxiosError } from 'axios';
5253

5354
export interface BasicInfoParams {
5455
quote: BuyQuote;
@@ -230,31 +231,34 @@ const BasicInfo = (): JSX.Element => {
230231
}),
231232
);
232233
} catch (submissionError) {
233-
// Check for Transak error code 2020 (phone already registered)
234-
// API returns: { error: { errorCode: 2020, message: "..." } }
235-
const errorWithCode = submissionError as unknown as {
236-
error?: { errorCode?: number; message?: string };
237-
};
238-
const isPhoneError = errorWithCode?.error?.errorCode === 2020;
234+
const axiosError = submissionError as AxiosError;
235+
const apiError = (
236+
axiosError?.response?.data as {
237+
error?: { errorCode?: number; message?: string };
238+
}
239+
)?.error;
239240

241+
const isPhoneError = apiError?.errorCode === 2020;
240242
setIsPhoneRegisteredError(isPhoneError);
241243

242-
// For error code 2020, extract email from message and format it
243-
let errorMessage = '';
244-
if (isPhoneError && errorWithCode?.error?.message) {
245-
// Extract email from message like "...created with k****@pedalsup.com..."
246-
const emailMatch = errorWithCode.error.message.match(
247-
/[\w*]+@[\w*]+(?:\.[\w*]+)*/,
248-
);
244+
const errorMessageText =
245+
submissionError instanceof Error && submissionError.message
246+
? submissionError.message
247+
: strings('deposit.basic_info.unexpected_error');
248+
249+
let errorMessage = errorMessageText;
250+
if (isPhoneError && errorMessageText) {
251+
// Extract email from message for error code 2020 (phone already registered)
252+
const emailMatch = errorMessageText.match(/[\w*]+@[\w*]+(?:\.[\w*]+)*/);
249253
const email = emailMatch ? emailMatch[0] : '';
250-
errorMessage = email
251-
? strings('deposit.basic_info.phone_already_registered', { email })
252-
: errorWithCode.error.message;
253-
} else {
254-
errorMessage =
255-
submissionError instanceof Error && submissionError.message
256-
? submissionError.message
257-
: strings('deposit.basic_info.unexpected_error');
254+
if (email) {
255+
errorMessage = strings(
256+
'deposit.basic_info.phone_already_registered',
257+
{
258+
email,
259+
},
260+
);
261+
}
258262
}
259263

260264
setError(errorMessage);

0 commit comments

Comments
 (0)