Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 44 additions & 18 deletions payments/wallets/webview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,14 @@ export function PayWebView({gatewayUrl, appDeepLink, onSuccess, onFailure}: PayW
Linking.openURL(request.url).catch(console.warn);
return false;
}
return true;
// Allow https (the checkout) and about:; block any other scheme so the
// page can't drive the OS into arbitrary native schemes (tel:, sms:, …).
try {
const scheme = new URL(request.url).protocol;
return scheme === 'https:' || scheme === 'about:';
} catch {
return false;
}
},
[],
);
Expand Down Expand Up @@ -208,6 +215,7 @@ const styles = StyleSheet.create({webview: {flex: 1}});

```kotlin
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Handler
Expand All @@ -232,6 +240,24 @@ fun isWalletDeeplink(url: String): Boolean = try {
Uri.parse(url).getQueryParameter("uri")?.startsWith("wc:") == true
} catch (_: Exception) { false }

// Shared by both shouldOverrideUrlLoading overloads. Returns true = handled (don't load
// in the WebView), false = let the WebView load it. Forward wc: wallet deeplinks to the OS;
// allow https (the checkout) and about: to load; block any other scheme (tel:, sms:,
// intent:, …) so the page can't drive the OS into arbitrary native apps.
private fun handleWalletDeeplink(context: Context, url: String?): Boolean {
if (url == null) return false
if (isWalletDeeplink(url)) {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} catch (_: ActivityNotFoundException) {
// No app can handle the wallet deeplink (wallet not installed).
}
return true
}
val scheme = Uri.parse(url).scheme?.lowercase()
return scheme != "https" && scheme != "about"
}

@Composable
fun PayWebView(
gatewayUrl: String,
Expand Down Expand Up @@ -274,22 +300,16 @@ fun PayWebView(
"ReactNativeWebView"
)
webViewClient = object : WebViewClient() {
// This WebResourceRequest overload is API 24+. If you support minSdk < 24,
// also override the deprecated shouldOverrideUrlLoading(view, url: String) and
// run the same isWalletDeeplink check — otherwise deeplink interception
// silently no-ops on API 23.
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val reqUrl = request?.url?.toString() ?: return false
if (isWalletDeeplink(reqUrl)) {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(reqUrl)))
} catch (_: ActivityNotFoundException) {
// No app can handle the wallet deeplink (wallet not installed).
}
return true
}
return false
}
// API 24+ overload.
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean =
handleWalletDeeplink(context, request?.url?.toString())

// Deprecated pre-API-24 overload — still the only one called on API 23, so
// without it deeplink interception silently no-ops there and wc: links would
// load inside the WebView instead of opening the wallet.
@Suppress("DEPRECATION")
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean =
handleWalletDeeplink(context, url)
}
loadUrl(buildPayUrl(gatewayUrl, appDeepLink))
}
Expand Down Expand Up @@ -458,7 +478,13 @@ class _PayWebViewState extends State<PayWebView> {
}
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
// Allow https (the checkout) and about:; block any other scheme so the
// page can't drive the OS into arbitrary native schemes (tel:, sms:, …).
final scheme = Uri.tryParse(request.url)?.scheme.toLowerCase();
if (scheme == 'https' || scheme == 'about') {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
))
..addJavaScriptChannel('ReactNativeWebView', onMessageReceived: (msg) {
Expand Down