Skip to content

Commit 6bb27e9

Browse files
authored
refactor(ramp): remove checkout callback registry from unified Checkout (#28417)
## **Description** Removes the **`checkoutCallbackRegistry`** indirection from the unified Ramp [`Checkout`](app/components/UI/Ramp/Views/Checkout/Checkout.tsx) screen and [`useTransakRouting`](app/components/UI/Ramp/hooks/useTransakRouting.ts) (Transak buy flow). Handlers are passed as **`onNavigationStateChange`** on Checkout route params; [`Checkout`](app/components/UI/Ramp/Views/Checkout/Checkout.tsx) applies URL de-duplication before invoking that callback. The registry module and its unit tests are deleted. ## **Changelog** CHANGELOG entry: null ## **Related issues** Refs: ## **Manual testing steps** ```gherkin Feature: Transak buy payment webview Scenario: User opens Checkout after KYC approved Given user completes Transak flow until the unified Checkout WebView opens When the provider redirects through the expected success or callback URL Then order handling and navigation behave as before ``` ## **Screenshots/Recordings** Not applicable — wiring-only change. ### **Before** N/A ### **After** https://github.com/user-attachments/assets/e16f92bf-ed0e-459a-9005-90ff8dadc489 ## **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. ## **Labels note** This PR sets **CHANGELOG entry: null**. If your release or CI workflow expects it, add the `no-changelog` label on GitHub (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). `team-money-movement` may already be applied by automation. Add `team-ramps` if that better matches ownership for Ramp / `app/components/UI/Ramp` changes. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how the Checkout WebView callback is wired (route param function instead of registry) and adds URL de-duplication, which could affect provider redirect handling if any callers relied on the old callbackKey behavior. > > **Overview** > Removes the `checkoutCallbackRegistry` indirection and deletes its implementation/tests, switching Checkout callback wiring to pass an `onNavigationStateChange` handler directly via Checkout route params. > > Updates `Checkout` to invoke that param callback only after URL de-duplication (while keeping the existing callback flow for `providerCode`/`walletAddress`), and updates `useTransakRouting` and related tests to use the new param-based callback. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit abb4c3f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 86b6748 commit 6bb27e9

File tree

6 files changed

+22
-151
lines changed

6 files changed

+22
-151
lines changed

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

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import React from 'react';
33
import { fireEvent, act, waitFor } from '@testing-library/react-native';
44
import Checkout from './Checkout';
55
import renderWithProvider from '../../../../../util/test/renderWithProvider';
6-
import {
7-
registerCheckoutCallback,
8-
removeCheckoutCallback,
9-
} from '../../utils/checkoutCallbackRegistry';
106
import { MetaMetricsEvents } from '../../../../../core/Analytics';
117
import { callbackBaseUrl } from '../../Aggregator/sdk';
128

@@ -352,15 +348,14 @@ describe('Checkout', () => {
352348
});
353349
});
354350

355-
describe('checkout callback registry (297-302)', () => {
356-
it('invokes registered callback when callbackKey is set and WebView navigates to new URL', async () => {
351+
describe('onNavigationStateChange with URL deduplication', () => {
352+
it('invokes param callback when WebView navigates to new URL', async () => {
357353
const mockCallback = jest.fn();
358-
const callbackKey = registerCheckoutCallback(mockCallback);
359354

360355
mockUseParams.mockReturnValue({
361356
url: 'https://provider.example.com',
362357
providerName: 'Test',
363-
callbackKey,
358+
onNavigationStateChange: mockCallback,
364359
});
365360

366361
const { getByTestId } = renderWithProvider(<Checkout />, {}, true, false);
@@ -374,18 +369,15 @@ describe('Checkout', () => {
374369
url: 'https://custom-dedup-url.example.com',
375370
}),
376371
);
377-
378-
removeCheckoutCallback(callbackKey);
379372
});
380373

381374
it('does not invoke callback on second navigation to same URL (dedup)', async () => {
382375
const mockCallback = jest.fn();
383-
const callbackKey = registerCheckoutCallback(mockCallback);
384376

385377
mockUseParams.mockReturnValue({
386378
url: 'https://provider.example.com',
387379
providerName: 'Test',
388-
callbackKey,
380+
onNavigationStateChange: mockCallback,
389381
});
390382

391383
const { getByTestId } = renderWithProvider(<Checkout />, {}, true, false);
@@ -396,8 +388,6 @@ describe('Checkout', () => {
396388
});
397389

398390
expect(mockCallback).toHaveBeenCalledTimes(1);
399-
400-
removeCheckoutCallback(callbackKey);
401391
});
402392
});
403393

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

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ import { useStyles } from '../../../../../component-library/hooks';
3232
import styleSheet from './Checkout.styles';
3333
import Device from '../../../../../util/device';
3434
import { shouldStartLoadWithRequest } from '../../../../../util/browser';
35-
import {
36-
getCheckoutCallback,
37-
removeCheckoutCallback,
38-
} from '../../utils/checkoutCallbackRegistry';
3935
import { CHECKOUT_TEST_IDS } from './Checkout.testIds';
4036

4137
interface CheckoutParams {
@@ -59,12 +55,7 @@ interface CheckoutParams {
5955
cryptocurrency?: string;
6056
/** V2: the Redux provider type for this order. Defaults to AGGREGATOR. */
6157
providerType?: FIAT_ORDER_PROVIDERS;
62-
/**
63-
* Key into the checkout callback registry. Used by Transak/Deposit flows.
64-
* The actual callback lives outside navigation state so that route params stay serializable.
65-
*/
66-
callbackKey?: string;
67-
/** Optional callback invoked on every navigation state change (e.g. to intercept redirect URLs). */
58+
/** Optional callback invoked on navigation state changes after URL de-duplication (e.g. redirect URLs). */
6859
onNavigationStateChange?: (navState: { url: string }) => void;
6960
}
7061

@@ -99,24 +90,13 @@ const Checkout = () => {
9990
network,
10091
userAgent,
10192
onNavigationStateChange,
102-
callbackKey,
10393
} = params ?? {};
10494
const effectiveOrderId = (orderIdParam ?? customOrderId)?.trim() || null;
10595

10696
const initialUriRef = useRef(uri);
107-
const callbackKeyRef = useRef(callbackKey);
10897
const registeredOrderIdsRef = useRef<Set<string>>(new Set());
10998
const hasCallbackFlow = Boolean(providerCode && walletAddress);
11099

111-
useEffect(() => {
112-
callbackKeyRef.current = callbackKey;
113-
return () => {
114-
if (callbackKey) {
115-
removeCheckoutCallback(callbackKey);
116-
}
117-
};
118-
}, [callbackKey]);
119-
120100
useEffect(() => {
121101
navigation.setOptions(
122102
getDepositNavbarOptions(
@@ -291,12 +271,10 @@ const Checkout = () => {
291271
(navState: { url: string }) => {
292272
if (navState.url !== previousUrlRef.current) {
293273
previousUrlRef.current = navState.url;
294-
if (callbackKeyRef.current) {
295-
getCheckoutCallback(callbackKeyRef.current)?.(navState);
296-
}
274+
onNavigationStateChange?.(navState);
297275
}
298276
},
299-
[],
277+
[onNavigationStateChange],
300278
);
301279

302280
const handleShouldStartLoadWithRequest = useCallback(
@@ -380,9 +358,9 @@ const Checkout = () => {
380358
onNavigationStateChange={
381359
hasCallbackFlow
382360
? handleNavigationStateChange
383-
: callbackKey
361+
: onNavigationStateChange
384362
? handleNavigationStateChangeWithDedup
385-
: onNavigationStateChange
363+
: undefined
386364
}
387365
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
388366
testID={CHECKOUT_TEST_IDS.WEBVIEW}

app/components/UI/Ramp/hooks/useTransakRouting.test.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,28 +135,23 @@ jest.mock('../Deposit/utils', () => ({
135135
generateThemeParameters: jest.fn(() => ({ theme: 'light' })),
136136
}));
137137

138+
let capturedHandleNavigationStateChange:
139+
| ((nav: { url: string }) => void)
140+
| null = null;
141+
138142
jest.mock('../Views/Checkout', () => ({
139143
createCheckoutNavDetails: jest.fn(
140144
({
141145
url,
142146
providerName,
143-
callbackKey,
147+
onNavigationStateChange,
144148
}: {
145149
url: string;
146150
providerName: string;
147-
callbackKey?: string;
148-
}) => ['Checkout', { url, providerName, callbackKey }],
149-
),
150-
}));
151-
152-
let capturedHandleNavigationStateChange:
153-
| ((nav: { url: string }) => void)
154-
| null = null;
155-
jest.mock('../utils/checkoutCallbackRegistry', () => ({
156-
registerCheckoutCallback: jest.fn(
157-
(callback: (nav: { url: string }) => void) => {
158-
capturedHandleNavigationStateChange = callback;
159-
return 'mock-callback-key';
151+
onNavigationStateChange?: (nav: { url: string }) => void;
152+
}) => {
153+
capturedHandleNavigationStateChange = onNavigationStateChange ?? null;
154+
return ['Checkout', { url, providerName, onNavigationStateChange }];
160155
},
161156
),
162157
}));
@@ -188,6 +183,7 @@ const mockQuote = {
188183
describe('useTransakRouting', () => {
189184
beforeEach(() => {
190185
jest.clearAllMocks();
186+
capturedHandleNavigationStateChange = null;
191187
mockUserRegion = {
192188
country: { currency: 'USD', isoCode: 'US' },
193189
regionCode: 'us-ca',
@@ -299,6 +295,7 @@ describe('useTransakRouting', () => {
299295
params: expect.objectContaining({
300296
url: 'https://payment.example.com',
301297
providerName: 'Transak',
298+
onNavigationStateChange: expect.any(Function),
302299
}),
303300
}),
304301
],

app/components/UI/Ramp/hooks/useTransakRouting.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { generateThemeParameters } from '../Deposit/utils';
1414
import { BasicInfoFormData } from '../Deposit/Views/BasicInfo/BasicInfo';
1515
import { AddressFormData } from '../Deposit/Views/EnterAddress/EnterAddress';
1616
import { createCheckoutNavDetails } from '../Views/Checkout';
17-
import { registerCheckoutCallback } from '../utils/checkoutCallbackRegistry';
1817
import useAnalytics from './useAnalytics';
1918
import { showV2OrderToast } from '../utils/v2OrderToast';
2019
import Logger from '../../../../util/Logger';
@@ -49,7 +48,7 @@ interface RampStackParamList {
4948
url: string;
5049
providerName: string;
5150
userAgent?: string;
52-
callbackKey?: string;
51+
onNavigationStateChange?: (navState: { url: string }) => void;
5352
};
5453
[key: string]: object | undefined;
5554
}
@@ -357,11 +356,10 @@ export const useTransakRouting = (_config?: UseTransakRoutingConfig) => {
357356

358357
const navigateToWebviewModalCallback = useCallback(
359358
({ paymentUrl, amount }: { paymentUrl: string; amount?: number }) => {
360-
const callbackKey = registerCheckoutCallback(handleNavigationStateChange);
361359
const [routeName, routeParams] = createCheckoutNavDetails({
362360
url: paymentUrl,
363361
providerName: 'Transak',
364-
callbackKey,
362+
onNavigationStateChange: handleNavigationStateChange,
365363
});
366364
navigation.reset({
367365
index: 1,

app/components/UI/Ramp/utils/checkoutCallbackRegistry.test.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

app/components/UI/Ramp/utils/checkoutCallbackRegistry.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)