feat: adds support for custom actions such as paypal#26949
feat: adds support for custom actions such as paypal#26949georgeweiler wants to merge 22 commits intomainfrom
Conversation
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
app/components/UI/Ramp/Views/Modals/PaymentSelectionModal/PaymentSelectionModal.tsx
Show resolved
Hide resolved
…l external browser flow The external browser flow (PayPal) was using an HTTPS fake-callback URL for InAppBrowser.openAuth, which cannot redirect back to the app. This replaces it with a metamask:// deep link matching the legacy aggregator pattern. After the browser flow completes, the user is now navigated to the Order Details screen via navigation.reset() so they can see the pre-created order's polling status. Made-with: Cursor
The buyURL baked into quotes at fetch-time uses the HTTPS fake-callback as the redirectUrl. External browser providers (e.g. PayPal) need a metamask:// deep link instead so they redirect back to the app after payment. This replaces the redirectUrl on the buyURL before calling getBuyWidgetData, matching the legacy SDK BuyAction.createWidget behaviour. Made-with: Cursor
When the user manually closes the PayPal browser without completing payment, InAppBrowser.openAuth resolves with type 'cancel'. We now check the result and return early, staying on the BuildQuote screen instead of navigating to an empty Order Details screen. This matches the legacy aggregator's useInAppBrowser behaviour. Made-with: Cursor
The previous fix conditionally overrode redirectUrl based on needsDeepLink, which checked isCustomAction and the inline buyWidget.browser. However, the browser type is only known AFTER the API fetch (chicken-and-egg problem). If neither isCustomAction nor the inline buyWidget were set on the quote, the override was skipped, and PayPal received the HTTPS fake-callback URL instead of the metamask:// deep link. Now we unconditionally set the deep link redirectUrl on the buyURL before every getBuyWidgetData call. This is safe because in-app browser providers ignore the redirectUrl parameter, and it matches how the legacy SDK always passed the deep link to createWidget(). Made-with: Cursor
Logs buyURL override, buyWidget API response, and InAppBrowser result to help diagnose the redirect issue in testing. All log lines are marked with TODO for removal after verification. Made-with: Cursor
|
I have read the CLA Document and I hereby sign the CLA |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
| return match ?? null; | ||
| } | ||
| return null; | ||
| const [quote] = quotesResponse.success; |
There was a problem hiding this comment.
Simplifies extremely defensive quote context checking that is not needed. We only ever fetch 1 quote on BuildQuote. A simple check for provider matching is enough.
| createEventBuilder, | ||
| ]); | ||
|
|
||
| const handleContinuePress = useCallback(async () => { |
There was a problem hiding this comment.
handleContinuePress was managing 2 separate branches for Native, Aggregator and now would have to handle custom actions. I've split the logic into separate functions for readability, testability, etc..
if (isNativeProvider(selectedQuote)) {
await handleNativeProviderContinue();
} else if (isCustomAction(selectedQuote)) {
await handleCustomActionContinue();
} else {
await handleAggregatorContinue();
}
| ); | ||
| const useExternalBrowser = buyWidget.browser === 'IN_APP_OS_BROWSER'; | ||
|
|
||
| if (useExternalBrowser) { |
There was a problem hiding this comment.
This shouldn't be necessary as all aggregator orders should be opening in the widget. However the mobile app has no way to know this, so we have to handle the case when API returns in-app browser for a non-custom-action.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |
| selectedQuote !== null && | ||
| quoteMatchesAmount && | ||
| quoteMatchesCurrentContext; | ||
| hasAmount && !selectedQuoteLoading && selectedQuote !== null; |
There was a problem hiding this comment.
Removed quote validation allows stale/mismatched quote submission
High Severity
The old code validated that the selected quote's amountIn matched the current amountAsNumber, the provider matched, and the payment method matched before enabling the continue button and before proceeding in handleContinuePress. All three guards were removed. Now canContinue only checks hasAmount && !selectedQuoteLoading && selectedQuote !== null, and handleContinuePress has no amount/payment-method validation. Because quote fetching is debounced (500ms), a user can change the amount or payment method and immediately tap Continue, submitting a stale quote for the wrong amount or payment method to the provider. The old quoteMatchesAmount and quoteMatchesCurrentContext guards explicitly prevented this race.
Additional Locations (1)
There was a problem hiding this comment.
This is true, but it's an edgecase issue. I've moved several lines of defensive code because quote amounts are not contractual anyway and can be updated when the user gets to the provider widget.
| } | ||
| return null; | ||
| const [quote] = quotesResponse.success; | ||
| return quote?.provider === selectedProvider.id ? quote : null; |
There was a problem hiding this comment.
selectedQuote ignores payment method when multiple quotes returned
Medium Severity
The selectedQuote memo was simplified to const [quote] = quotesResponse.success; return quote?.provider === selectedProvider.id ? quote : null. The old logic handled multi-quote responses by finding a quote matching both the provider and selectedPaymentMethod.id. The new code blindly takes the first array element, ignoring selectedPaymentMethod even though it's still in the dependency array. If the API returns multiple quotes for different payment methods, the wrong quote (for a different payment method) can be selected and submitted.
There was a problem hiding this comment.
not an issue. build quote only ever fetches 1 quote
|
|
✅ E2E Fixture Validation — Schema is up to date |
- Pass providerCode and walletAddress from BuildQuote to OrderDetails so it can fetch the order from the API when not yet in state - Add hydration effect in OrderDetails that calls refreshOrder when order is missing but fallback params are available - Show loading spinner and error states during hydration - Remove all debug instrumentation (rampsDebugLog) from BuildQuote, OrderDetails, and Checkout Made-with: Cursor
52d67a0 to
479f678
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
| ======= | ||
| versionName "7.70.0" | ||
| versionCode 3607 | ||
| >>>>>>> 1242530285d27aa5c0f2a7b7b53cc417231b3d58 |
There was a problem hiding this comment.
Unresolved Git Merge Conflict in Build File
High Severity
The file contains raw git merge conflict markers (<<<<<<< HEAD, =======, >>>>>>>) for the versionName and versionCode fields. This is a Gradle syntax error that will cause every Android build to fail immediately. The conflict between version 7.69.0/code 3892 (HEAD) and 7.70.0/code 3607 (the other branch) was never resolved before committing.




Description
Integrates new ramps-controller API for custom-action flows (e.g., PayPal) removes Redux-based custom ID tracking.
Simplifies ramps-controller state hydration system into a single
initcall on app start up.What changed:
getBuyWidgetDatainstead ofgetWidgetUrl. ForisCustomActionquotes, callsaddPrecreatedOrderbefore opening external browser (InAppBrowser orLinking.openURL) and passesorderIdto Checkout for WebView flows.addPrecreatedOrderinstead of ReduxaddFiatCustomIdData. Supports bothorderIdandcustomOrderIdfor backwards compatibility.useRampsOrdersexposesaddPrecreatedOrderandAddPrecreatedOrderParams;useRampsQuotesusesgetBuyWidgetDatareturningPromise<BuyWidget | null>;useRampsControllerexposes both.PaymentSelectionModalandProviderSelectionfilter outisCustomActionquotes where appropriate.What stays untouched: Non-custom-action flows (standard WebView checkout) unchanged. Existing ramps navigation and token selection preserved.
Dependencies: Requires MetaMask/core#8100 for the new controller API.
Changelog
CHANGELOG entry: Fixes Paypal order tracking
Related issues
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist
Note
High Risk
Changes the on-ramp checkout flow to support external-browser/deeplink providers (e.g., PayPal) and introduces new order hydration/tracking paths, which can break purchase flows if edge cases aren’t handled. Additionally,
android/app/build.gradlecontains unresolved merge-conflict markers inversionName/versionCode, which is build-breaking.Overview
Adds support for custom-action on-ramp providers (e.g., PayPal) by switching from
getWidgetUrlto the newgetBuyWidgetDataAPI (url + orderId + browser) and usingaddPrecreatedOrderto track orders before redirecting out of the app.Updates
BuildQuoteto overrideredirectUrlto ametamask://deep link, choose between WebView checkout vs external browser (Linking/InAppBrowser.openAuth), and navigate directly toRAMPS_ORDER_DETAILSfor external flows while passingorderIdinto Checkout for WebView flows.Checkoutnow registers precreated orders fromorderId/legacycustomOrderIdand removes the prior Redux custom-id tracking.Provider/payment selection UI now filters out
isCustomActionquotes, order list merging hidesPrecreated/IdExpiredV2 orders, and multiple modules normalize provider codes vianormalizeProviderCode. Build/version numbers were bumped in CI/iOS configs, but Androidbuild.gradlecurrently includes conflict markers that must be resolved.Written by Cursor Bugbot for commit 479f678. This will update automatically on new commits. Configure here.