Skip to content

Commit 528babf

Browse files
authored
fix: route fiat payment method through strategy selection to enable fiat strategy (#8720)
## Explanation The fiat strategy was never selected during quote retrieval because of two issues: 1. **`getStrategyOrder` never included `Fiat`** — The default strategy order only contained `Relay` and `Across`. The fiat payment method ID was not passed into the strategy routing function, so `TransactionPayStrategy.Fiat` was never part of the strategy list. 2. **`getQuotes` bailed early on empty requests** — When a fiat payment method is selected without a crypto payment token, `buildQuoteRequests` returns an empty array (no `paymentToken`). The guard `if (!requests?.length)` short-circuited before any strategy could run. The fiat strategy doesn't need these requests — it derives its own relay request from `amountFiat` internally. ### Changes - **`getStrategyOrder`** — Added optional `fiatPaymentMethodId` parameter. When provided, early returns `[TransactionPayStrategy.Fiat]` so only the fiat strategy is used. - **`#getStrategiesWithFallback`** — Now passes `transactionData.fiatPayment.selectedPaymentMethodId` through to `getStrategyOrder`. - **`getQuotes`** — Changed the empty-requests guard from `!requests?.length` to `!requests?.length && !fiatPaymentMethod` so the strategy loop runs when a fiat payment method is active. ## References - Related to CONF-1065 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes strategy routing and quote retrieval guard logic, which can affect which pay strategy executes and when quotes are fetched, but scope is limited to the transaction-pay controller and covered by new unit tests. > > **Overview** > Fixes fiat pay quote retrieval by threading `fiatPayment.selectedPaymentMethodId` into strategy selection and ensuring it can drive the `Fiat` strategy. > > `getStrategyOrder` now accepts an optional fiat payment method ID and short-circuits to **Fiat-only** strategy ordering when present, and `TransactionPayController` passes this value through during fallback strategy resolution. Quote retrieval no longer bails early on empty request sets when a fiat payment method is selected, allowing fiat quotes even without a crypto `paymentToken`. > > Adds targeted test coverage for the new routing behavior and updates the package changelog under **Fixed**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5e81276. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 024e07f commit 528babf

7 files changed

Lines changed: 136 additions & 3 deletions

File tree

packages/transaction-pay-controller/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Fix fiat strategy never being selected by routing fiat payment method through `getStrategyOrder` and allowing quote retrieval when no crypto payment token is set ([#8720](https://github.com/MetaMask/core/pull/8720))
13+
1014
## [21.1.0]
1115

1216
### Added

packages/transaction-pay-controller/src/TransactionPayController.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,48 @@ describe('TransactionPayController', () => {
540540
CHAIN_ID_MOCK,
541541
TOKEN_ADDRESS_MOCK,
542542
'perpsDeposit',
543+
undefined,
544+
);
545+
});
546+
547+
it('passes fiat payment method ID into getStrategyOrder', async () => {
548+
const controller = createController();
549+
550+
controller.updatePaymentToken({
551+
transactionId: TRANSACTION_ID_MOCK,
552+
tokenAddress: TOKEN_ADDRESS_MOCK,
553+
chainId: CHAIN_ID_MOCK,
554+
});
555+
556+
const { updateTransactionData } = updatePaymentTokenMock.mock.calls[0][1];
557+
558+
updateTransactionData(TRANSACTION_ID_MOCK, (data) => {
559+
data.paymentToken = {
560+
address: TOKEN_ADDRESS_MOCK,
561+
balanceFiat: '1',
562+
balanceHuman: '1',
563+
balanceRaw: '1',
564+
balanceUsd: '1',
565+
chainId: CHAIN_ID_MOCK,
566+
decimals: 6,
567+
symbol: 'USDC',
568+
};
569+
data.fiatPayment = { selectedPaymentMethodId: 'card-123' };
570+
});
571+
572+
const transactionMeta = {
573+
id: TRANSACTION_ID_MOCK,
574+
type: 'perpsDeposit',
575+
} as TransactionMeta;
576+
577+
messenger.call('TransactionPayController:getStrategy', transactionMeta);
578+
579+
expect(getStrategyOrderMock).toHaveBeenCalledWith(
580+
messenger,
581+
CHAIN_ID_MOCK,
582+
TOKEN_ADDRESS_MOCK,
583+
'perpsDeposit',
584+
'card-123',
543585
);
544586
});
545587
});

packages/transaction-pay-controller/src/TransactionPayController.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,14 +324,15 @@ export class TransactionPayController extends BaseController<
324324
return validStrategies;
325325
}
326326

327-
const paymentToken =
328-
this.state.transactionData[transaction.id]?.paymentToken;
327+
const transactionData = this.state.transactionData[transaction.id];
328+
const paymentToken = transactionData?.paymentToken;
329329

330330
return getStrategyOrder(
331331
this.messenger,
332332
paymentToken?.chainId,
333333
paymentToken?.address,
334334
transaction.type,
335+
transactionData?.fiatPayment?.selectedPaymentMethodId,
335336
);
336337
}
337338
}

packages/transaction-pay-controller/src/utils/feature-flags.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,49 @@ describe('Feature Flags Utils', () => {
809809
TransactionPayStrategy.Relay,
810810
]);
811811
});
812+
813+
it('returns only Fiat strategy when fiatPaymentMethodId is provided', () => {
814+
const strategyOrder = getStrategyOrder(
815+
messenger,
816+
undefined,
817+
undefined,
818+
undefined,
819+
'card-123',
820+
);
821+
822+
expect(strategyOrder).toStrictEqual([TransactionPayStrategy.Fiat]);
823+
});
824+
825+
it('returns only Fiat strategy regardless of other routing config when fiatPaymentMethodId is provided', () => {
826+
getRemoteFeatureFlagControllerStateMock.mockReturnValue({
827+
...getDefaultRemoteFeatureFlagControllerState(),
828+
remoteFeatureFlags: {
829+
confirmations_pay: {
830+
strategyOrder: [
831+
TransactionPayStrategy.Relay,
832+
TransactionPayStrategy.Across,
833+
],
834+
strategyOverrides: {
835+
default: {
836+
chains: {
837+
[CHAIN_ID_MOCK]: [TransactionPayStrategy.Bridge],
838+
},
839+
},
840+
},
841+
},
842+
},
843+
});
844+
845+
const strategyOrder = getStrategyOrder(
846+
messenger,
847+
CHAIN_ID_MOCK,
848+
TOKEN_ADDRESS_MOCK,
849+
'perpsDeposit',
850+
'/payments/debit-credit-card',
851+
);
852+
853+
expect(strategyOrder).toStrictEqual([TransactionPayStrategy.Fiat]);
854+
});
812855
});
813856

814857
describe('getStrategyOrder route-aware resolution', () => {

packages/transaction-pay-controller/src/utils/feature-flags.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,14 +312,21 @@ function getDefaultOverrideStrategies(
312312
* @param tokenAddress - Optional token address used to match route overrides.
313313
* @param transactionType - Optional transaction type used to match route
314314
* overrides.
315+
* @param fiatPaymentMethodId - Optional fiat payment method ID used to match route overrides.
315316
* @returns Ordered strategy list.
316317
*/
317318
export function getStrategyOrder(
318319
messenger: TransactionPayControllerMessenger,
319320
chainId?: Hex,
320321
tokenAddress?: Hex,
321322
transactionType?: string,
323+
fiatPaymentMethodId?: string,
322324
): StrategyOrder {
325+
// If fiat payment method is selected, use Fiat strategy only
326+
if (fiatPaymentMethodId) {
327+
return [TransactionPayStrategy.Fiat];
328+
}
329+
323330
const routingConfig = getStrategyRoutingConfig(messenger);
324331
const normalizedChainId = normalizeHex(chainId);
325332
const normalizedTokenAddress = normalizeHex(tokenAddress);

packages/transaction-pay-controller/src/utils/quotes.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,42 @@ describe('Quotes Utils', () => {
589589
});
590590
});
591591

592+
it('still invokes strategies when no payment token but fiat payment method is set', async () => {
593+
await run({
594+
transactionData: {
595+
...TRANSACTION_DATA_MOCK,
596+
paymentToken: undefined,
597+
fiatPayment: { selectedPaymentMethodId: 'card-123' },
598+
},
599+
});
600+
601+
expect(getQuotesMock).toHaveBeenCalled();
602+
});
603+
604+
it('does not invoke strategies when no payment token and no fiat payment method', async () => {
605+
await run({
606+
transactionData: {
607+
...TRANSACTION_DATA_MOCK,
608+
paymentToken: undefined,
609+
fiatPayment: {},
610+
},
611+
});
612+
613+
const transactionDataMock = {
614+
quotes: [QUOTE_MOCK],
615+
quotesLastUpdated: undefined,
616+
};
617+
618+
updateTransactionDataMock.mock.calls.map((call) =>
619+
call[1](transactionDataMock),
620+
);
621+
622+
expect(transactionDataMock).toMatchObject({
623+
quotes: [],
624+
quotesLastUpdated: expect.any(Number),
625+
});
626+
});
627+
592628
it('gets quotes from strategy', async () => {
593629
await run();
594630

packages/transaction-pay-controller/src/utils/quotes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ async function getQuotes(
571571
},
572572
);
573573

574-
if (!requests?.length) {
574+
if (!requests?.length && !fiatPaymentMethod) {
575575
return {
576576
batchTransactions: [],
577577
quotes: [],

0 commit comments

Comments
 (0)