Skip to content

Commit 9e56b9f

Browse files
authored
fix(ramps): match Build Quote payment pill to payment selection modal (#28392)
## Description Unified Buy v2 **Build Quote** on `main` shows the generic **card** icon in the payment pill even when a real method (e.g. **Apple Pay**) is selected, because the pill did not receive `paymentMethod`. This PR passes `selectedPaymentMethod` into **`PaymentMethodPill`**, which renders **`PaymentMethodIcon`** the same way as **`PaymentMethodListItem`** in the payment selection modal: `paymentMethodType` with the same cast, and **`colors.primary.default`** for the icon (parity with a **selected** row in the sheet). No extra parsing helpers and no Aggregator changes. ## Changelog CHANGELOG entry: Fixed payment method icon on Buy Build Quote to match the payment selection modal ## Related issues Refs: TRAM-3401 ## Manual testing steps ```gherkin Feature: Unified Buy Build Quote payment pill Scenario: Pill matches selected method in payment modal Given the user is on Build Quote with a payment method selected When they compare the pill leading icon to that method’s row in the payment selection modal Then the glyph and treatment match (same PaymentMethodIcon path as the list; primary color on the pill) Scenario: Pill shows card when no method Given the user has not selected a payment method When they view the pill Then the generic card icon is shown ``` ## Screenshots/Recordings ### Before (`main` branch) <img width="1206" height="2622" alt="image" src="https://github.com/user-attachments/assets/218cb58d-ae7a-40e6-8aa6-f36659a83847" /> ### After (this PR) ![Build Quote — Apple Pay selected, pill icon matches list](https://github.com/MetaMask/metamask-mobile/releases/download/pr-28392-screenshots/pr28392-this-branch.png) ## 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 - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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] > **Low Risk** > Low risk UI-only change: the Build Quote payment pill now renders a real payment-method icon when a payment type is available, with test/snapshot updates. No changes to quote fetching, routing, or controllers. > > **Overview** > **Build Quote’s payment pill now mirrors the payment selection modal iconography.** `BuildQuote` passes `selectedPaymentMethod` into `PaymentMethodPill`, enabling the pill to render `PaymentMethodIcon` (primary color) when a `paymentType` is present, and fall back to the generic card icon otherwise. > > Tests were updated to include `paymentType` in the mocked payment method, add coverage for rendering `PaymentMethodIcon` on any non-empty `paymentType`, and refresh affected snapshots. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f0bf9dd. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 5d16844 commit 9e56b9f

5 files changed

Lines changed: 157 additions & 47 deletions

File tree

app/components/UI/Ramp/Views/BuildQuote/BuildQuote.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ const SELECTED_PAYMENT_METHOD = {
301301
id: '/payments/debit-credit-card',
302302
name: 'Debit/Credit Card',
303303
isManualBankTransfer: false,
304+
paymentType: 'debit-credit-card',
305+
icons: [] as const,
304306
};
305307

306308
const USER_REGION = {

app/components/UI/Ramp/Views/BuildQuote/BuildQuote.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ function BuildQuote() {
923923
selectedPaymentMethod?.name ||
924924
strings('fiat_on_ramp.select_payment_method')
925925
}
926+
paymentMethod={selectedPaymentMethod}
926927
isLoading={paymentMethodsLoading}
927928
onPress={
928929
isTokenUnavailable ? undefined : handlePaymentPillPress

app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -334,20 +334,27 @@ exports[`BuildQuote handleNativeProviderContinue sets rampsError when quote is n
334334
testID="build-quote-payment-pill"
335335
>
336336
<View>
337-
<SvgMock
338-
fill="currentColor"
339-
name="Card"
337+
<Text
338+
allowFontScaling={false}
339+
selectable={false}
340340
style={
341341
[
342342
{
343-
"color": "#131416",
344-
"height": 16,
345-
"width": 16,
343+
"color": "#4459ff",
344+
"fontSize": 16,
346345
},
347346
undefined,
347+
{
348+
"fontFamily": "Material Icons",
349+
"fontStyle": "normal",
350+
"fontWeight": "normal",
351+
},
352+
{},
348353
]
349354
}
350-
/>
355+
>
356+
357+
</Text>
351358
</View>
352359
<Text
353360
accessibilityRole="text"
@@ -1033,20 +1040,27 @@ exports[`BuildQuote handleNativeProviderContinue sets rampsError when transakChe
10331040
testID="build-quote-payment-pill"
10341041
>
10351042
<View>
1036-
<SvgMock
1037-
fill="currentColor"
1038-
name="Card"
1043+
<Text
1044+
allowFontScaling={false}
1045+
selectable={false}
10391046
style={
10401047
[
10411048
{
1042-
"color": "#131416",
1043-
"height": 16,
1044-
"width": 16,
1049+
"color": "#4459ff",
1050+
"fontSize": 16,
10451051
},
10461052
undefined,
1053+
{
1054+
"fontFamily": "Material Icons",
1055+
"fontStyle": "normal",
1056+
"fontWeight": "normal",
1057+
},
1058+
{},
10471059
]
10481060
}
1049-
/>
1061+
>
1062+
1063+
</Text>
10501064
</View>
10511065
<Text
10521066
accessibilityRole="text"
@@ -1732,20 +1746,27 @@ exports[`BuildQuote handleNativeProviderContinue sets rampsError when transakRou
17321746
testID="build-quote-payment-pill"
17331747
>
17341748
<View>
1735-
<SvgMock
1736-
fill="currentColor"
1737-
name="Card"
1749+
<Text
1750+
allowFontScaling={false}
1751+
selectable={false}
17381752
style={
17391753
[
17401754
{
1741-
"color": "#131416",
1742-
"height": 16,
1743-
"width": 16,
1755+
"color": "#4459ff",
1756+
"fontSize": 16,
17441757
},
17451758
undefined,
1759+
{
1760+
"fontFamily": "Material Icons",
1761+
"fontStyle": "normal",
1762+
"fontWeight": "normal",
1763+
},
1764+
{},
17461765
]
17471766
}
1748-
/>
1767+
>
1768+
1769+
</Text>
17491770
</View>
17501771
<Text
17511772
accessibilityRole="text"
@@ -2431,20 +2452,27 @@ exports[`BuildQuote handleWidgetProviderContinue sets rampsError when getBuyWidg
24312452
testID="build-quote-payment-pill"
24322453
>
24332454
<View>
2434-
<SvgMock
2435-
fill="currentColor"
2436-
name="Card"
2455+
<Text
2456+
allowFontScaling={false}
2457+
selectable={false}
24372458
style={
24382459
[
24392460
{
2440-
"color": "#131416",
2441-
"height": 16,
2442-
"width": 16,
2461+
"color": "#4459ff",
2462+
"fontSize": 16,
24432463
},
24442464
undefined,
2465+
{
2466+
"fontFamily": "Material Icons",
2467+
"fontStyle": "normal",
2468+
"fontWeight": "normal",
2469+
},
2470+
{},
24452471
]
24462472
}
2447-
/>
2473+
>
2474+
2475+
</Text>
24482476
</View>
24492477
<Text
24502478
accessibilityRole="text"
@@ -3130,20 +3158,27 @@ exports[`BuildQuote handleWidgetProviderContinue sets rampsError when getBuyWidg
31303158
testID="build-quote-payment-pill"
31313159
>
31323160
<View>
3133-
<SvgMock
3134-
fill="currentColor"
3135-
name="Card"
3161+
<Text
3162+
allowFontScaling={false}
3163+
selectable={false}
31363164
style={
31373165
[
31383166
{
3139-
"color": "#131416",
3140-
"height": 16,
3141-
"width": 16,
3167+
"color": "#4459ff",
3168+
"fontSize": 16,
31423169
},
31433170
undefined,
3171+
{
3172+
"fontFamily": "Material Icons",
3173+
"fontStyle": "normal",
3174+
"fontWeight": "normal",
3175+
},
3176+
{},
31443177
]
31453178
}
3146-
/>
3179+
>
3180+
3181+
</Text>
31473182
</View>
31483183
<Text
31493184
accessibilityRole="text"
@@ -3829,20 +3864,27 @@ exports[`BuildQuote quoteFetchError tracks RAMPS_QUOTE_ERROR and shows BannerAle
38293864
testID="build-quote-payment-pill"
38303865
>
38313866
<View>
3832-
<SvgMock
3833-
fill="currentColor"
3834-
name="Card"
3867+
<Text
3868+
allowFontScaling={false}
3869+
selectable={false}
38353870
style={
38363871
[
38373872
{
3838-
"color": "#131416",
3839-
"height": 16,
3840-
"width": 16,
3873+
"color": "#4459ff",
3874+
"fontSize": 16,
38413875
},
38423876
undefined,
3877+
{
3878+
"fontFamily": "Material Icons",
3879+
"fontStyle": "normal",
3880+
"fontWeight": "normal",
3881+
},
3882+
{},
38433883
]
38443884
}
3845-
/>
3885+
>
3886+
3887+
</Text>
38463888
</View>
38473889
<Text
38483890
accessibilityRole="text"

app/components/UI/Ramp/components/PaymentMethodPill/PaymentMethodPill.test.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
import React from 'react';
22
import { fireEvent, render } from '@testing-library/react-native';
3+
import { PaymentType } from '@consensys/on-ramp-sdk';
34
import PaymentMethodPill from './PaymentMethodPill';
45
import { ThemeContext, mockTheme } from '../../../../../util/theme';
56

7+
jest.mock('../../Aggregator/components/PaymentMethodIcon', () => {
8+
const ReactActual = jest.requireActual('react');
9+
const { View, Text } = jest.requireActual('react-native');
10+
const Mock = (props: { paymentMethodType?: string }) =>
11+
ReactActual.createElement(
12+
View,
13+
{ testID: 'mock-payment-method-icon' },
14+
ReactActual.createElement(Text, null, props.paymentMethodType ?? ''),
15+
);
16+
return { __esModule: true, default: Mock };
17+
});
18+
619
const renderWithTheme = (component: React.ReactElement) =>
720
render(
821
<ThemeContext.Provider value={mockTheme}>
@@ -58,6 +71,35 @@ describe('PaymentMethodPill', () => {
5871
expect(toJSON()).toMatchSnapshot();
5972
});
6073

74+
it('renders PaymentMethodIcon for any non-empty paymentType like the payment list', () => {
75+
const { getByTestId, getByText } = renderWithTheme(
76+
<PaymentMethodPill
77+
label="Pay"
78+
paymentMethod={{
79+
paymentType: 'future-payment-method',
80+
}}
81+
/>,
82+
);
83+
84+
expect(getByTestId('mock-payment-method-icon')).toBeOnTheScreen();
85+
expect(getByText('future-payment-method')).toBeOnTheScreen();
86+
});
87+
88+
it('renders PaymentMethodIcon when paymentMethod has paymentType', () => {
89+
const { getByText, getByTestId } = renderWithTheme(
90+
<PaymentMethodPill
91+
label="Apple Pay"
92+
paymentMethod={{
93+
paymentType: PaymentType.ApplePay,
94+
}}
95+
/>,
96+
);
97+
98+
expect(getByText('Apple Pay')).toBeOnTheScreen();
99+
expect(getByTestId('mock-payment-method-icon')).toBeOnTheScreen();
100+
expect(getByText(PaymentType.ApplePay)).toBeOnTheScreen();
101+
});
102+
61103
describe('when isLoading is true', () => {
62104
it('renders a non-interactive View instead of TouchableOpacity', () => {
63105
const mockOnPress = jest.fn();

app/components/UI/Ramp/components/PaymentMethodPill/PaymentMethodPill.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22
import { TouchableOpacity, View } from 'react-native';
3+
import type { PaymentMethod } from '@metamask/ramps-controller';
4+
import type { PaymentType } from '@consensys/on-ramp-sdk';
35
import {
46
Icon,
57
IconName,
@@ -11,10 +13,16 @@ import {
1113
Spinner,
1214
} from '@metamask/design-system-react-native';
1315
import { useStyles } from '../../../../hooks/useStyles';
16+
import { useTheme } from '../../../../../util/theme';
1417

18+
import PaymentMethodIcon from '../../Aggregator/components/PaymentMethodIcon';
1519
import styleSheet from './PaymentMethodPill.styles';
1620
import { PAYMENT_METHOD_PILL_TEST_IDS } from './PaymentMethodPill.testIds';
1721

22+
const PAYMENT_METHOD_ICON_SIZE = 16;
23+
24+
export type PaymentMethodForPill = Pick<PaymentMethod, 'paymentType'>;
25+
1826
export interface PaymentMethodPillProps {
1927
/** Payment method label (e.g., "Debit card") */
2028
label: string;
@@ -24,15 +32,22 @@ export interface PaymentMethodPillProps {
2432
isLoading?: boolean;
2533
/** Test ID for testing */
2634
testID?: string;
35+
/** Selected method: same `PaymentMethodIcon` + `paymentType` pattern as the payment selection list (primary icon color). */
36+
paymentMethod?: PaymentMethodForPill | null;
2737
}
2838

2939
const PaymentMethodPill: React.FC<PaymentMethodPillProps> = ({
3040
label,
3141
onPress,
3242
isLoading = false,
3343
testID = PAYMENT_METHOD_PILL_TEST_IDS.CONTAINER,
44+
paymentMethod,
3445
}) => {
3546
const { styles } = useStyles(styleSheet, {});
47+
const { colors } = useTheme();
48+
49+
const paymentType = paymentMethod?.paymentType;
50+
const hasPaymentType = paymentType != null && String(paymentType).length > 0;
3651

3752
if (isLoading) {
3853
return (
@@ -53,11 +68,19 @@ const PaymentMethodPill: React.FC<PaymentMethodPillProps> = ({
5368
activeOpacity={0.7}
5469
>
5570
<View style={styles.iconWrapper}>
56-
<Icon
57-
name={IconName.Card}
58-
size={IconSize.Sm}
59-
color={IconColor.IconDefault}
60-
/>
71+
{hasPaymentType ? (
72+
<PaymentMethodIcon
73+
paymentMethodType={paymentType as PaymentType}
74+
size={PAYMENT_METHOD_ICON_SIZE}
75+
color={colors.primary.default}
76+
/>
77+
) : (
78+
<Icon
79+
name={IconName.Card}
80+
size={IconSize.Sm}
81+
color={IconColor.IconDefault}
82+
/>
83+
)}
6184
</View>
6285
<Text
6386
variant={TextVariant.BodyMd}

0 commit comments

Comments
 (0)