Handling UPI Intent in WebView
Introduction
UPI intent is a functionality which allows the customer to make a UPI payment without entering their UPI ID. The user is instead shown a list of UPI PSPs (Payment Service Providers) which are installed on their device and has to select the UPI PSP from where they wish to complete the payment.
Applicability
This document is applicable for merchants who have integrated the Neo - Full Redirect or Ace - SDK in a WebView for any of the following workflows:
- One-time Payments
- Payment + Mandate
- Only Mandate Setup
The document contains the steps to handle UPI intent in WebView on following platforms/frameworks:
Note for UPI intent on iOS:
BillDesk supports the following UPI PSPs for UPI intent transactions on iOS and these apps appear on the Neo - Full Redirect or Ace - SDK irrespective of the installation status of these apps on the customers' device:
- BHIM (only for One-time Payments)
- Google Pay
- Paytm
- PhonePe
Sample Code
Native Android
Step 1: Add the below given code in the AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
...
...
</application>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="upi" android:host="pay"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="upi" android:host="mandate" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="nb" android:host="pay" />
</intent>
</queries>
</manifest>Step 2: Add WebView to your Android app
Include a WebView in your app's layout XML file:
Xml
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />Step 3: Setup WebViewClient
Implement a WebViewClient to handle URL requests:
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.net.URISyntaxException;
public class MyWebViewClient extends WebViewClient {
private Activity activity;
public MyWebViewClient(Activity activity) {
this.activity = activity;
}
@Override
public Boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return false;
}
}import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import java.net.URISyntaxException
class MyWebViewClient(private val activity: Activity): WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView ? , request : WebResourceRequest ? ):
Boolean {
request ? .let {
return false
}
}
}Step 4: Set WebViewClient in MainActivity
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = findViewById(R.id.webview);
webView.setWebViewClient(new MyWebViewClient(MainActivity.this));
}
}import android.app.Activity
import android.os.Bundle
import android.webkit.WebView
class MainActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle ? ) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView: WebView = findViewById(R.id.webview)
webView.webViewClient = MyWebViewClient(this)
}
}Step 5: Handle Deep Links
Handle deep links within shouldOverrideUrlLoading() method of WebViewClient to
trigger the UPI payment process:
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("upi://pay") || url.startsWith("bhim://upi") || url.startsWith("upi://mandate") || url.startsWith("nb://pay") ) {
try {
Context context = view.getContext();
Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
if (intent != null) {
PackageManager packageManager = context.getPackageManager();
ResolveInfo info = packageManager.resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (info != null) {
context.startActivity(intent);
} else {
// Fallback URL for other UPI apps
String fallbackUrl =
intent.getStringExtra("browser_fallback_url");
if (fallbackUrl != null && !fallbackUrl.isEmpty()) {
view.loadUrl(fallbackUrl);
} else {
Log.e(TAG, "Fallback URL is null or empty");
}
}
return true;
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
return false;
}
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
String url = uri.toString();
if (url.startsWith("upi://pay") || url.startsWith("bhim://upi") || url.startsWith("upi://mandate") || url.startsWith("nb://pay") ) {
try {
Context context = view.getContext();
Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
if (intent != null) {
PackageManager packageManager = context.getPackageManager();
ResolveInfo info = packageManager.resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (info != null) {
context.startActivity(intent);
} else {
// Fallback URL for other UPI apps
String fallbackUrl =
intent.getStringExtra("browser_fallback_url");
if (fallbackUrl != null && !fallbackUrl.isEmpty()) {
view.loadUrl(fallbackUrl);
} else {
Log.e("WebViewClient", "Fallback URL is null or
empty ");
}
}
return true;
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
return false;
}
import android.content.Intent
import android.content.pm.PackageManager
import android.util.Log
import android.webkit.WebView
import java.net.URISyntaxException
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (url.startsWith("upi://pay") || url.startsWith("bhim://upi")
|| url.startsWith("upi://mandate") || url.startsWith("nb://pay") ) {
try {
val context = view.context
val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
if (intent != null) {
val packageManager = context.packageManager
val info = packageManager.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY)
if (info != null) {
context.startActivity(intent)
} else {
// Fallback URL for other UPI apps
val fallbackUrl =
intent.getStringExtra("browser_fallback_url")
if (fallbackUrl != null && !fallbackUrl.isEmpty()) {
view.loadUrl(fallbackUrl)
} else {
Log.e(TAG, "Fallback URL is null or empty")
}
}
return true
}
} catch (e: URISyntaxException) {
e.printStackTrace()
}
}
return false
}
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val uri = request.url
val url = uri.toString()
if (url.startsWith("upi://pay") ||
url.startsWith("bhim://upi") ||
url.startsWith("upi://mandate") ||
url.startsWith("nb://pay")
) {
try {
val context = view.context
val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
val packageManager = context.packageManager
val info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
if (info != null) {
context.startActivity(intent)
} else {
// Fallback URL for other UPI apps
val fallbackUrl = intent.getStringExtra("browser_fallback_url")
if (!fallbackUrl.isNullOrEmpty()) {
view.loadUrl(fallbackUrl)
} else {
Log.e("WebViewClient", "Fallback URL is null or empty")
}
}
return true
} catch (e: URISyntaxException) {
e.printStackTrace()
}
}
return false
}
Native iOS
Step 1: Handle deep links in your app
Delegate, override the application(_:open:options:) method to handle deep link URLs:
Swift
func application(
_ app: UIApplication, open url: URL,
options:
[UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool { // Handle deep link URL here return
true
}Step 2: Setup WKNavigationDelegate for WKWebView
Set up the WKNavigationDelegate for your WKWebView to handle navigation events:
Swift
import UIKit
import WebKit
class BilldeskViewController: UIViewController { var checkoutUrl: String? @IBOutlet weak var
wkWebView: WKWebView! override func viewDidLoad() { super.viewDidLoad()
wkWebView.navigationDelegate = self loadWebPage() } func loadWebPage() { guard let
urlString = checkoutUrl, let url = URL(string: urlString) else { return }
wkWebView.load(URLRequest(url: url)) } }Step 3: Implement WKNavigationDelegate
Implement WKNavigationDelegate methods to handle navigation events in your WKWebView:
Swift
import UIKit
import WebKit
extension BilldeskViewController: WKNavigationDelegate { func webView(_ webView:
WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler:
@escaping (WKNavigationActionPolicy) -> Void) { guard let url = navigationAction.request.url
else { decisionHandler(.cancel) return } let regex = try! NSRegularExpression(pattern:
"(upi|intent)", options: []) if (regex.firstMatch(in: url.absoluteString, options: [], range:
NSRange(location: 0, length: url.absoluteString.count)) != nil) &&
!url.absoluteString.hasPrefix("http://") && !url.absoluteString.hasPrefix("https://") {
UIApplication.shared.open(url) decisionHandler(.cancel) } else { decisionHandler(.allow) }
print("navigationAction load: \(String(describing: navigationAction.request.url))") } }React Native
Step 1: Add a WebView
Include a WebView in your app's layout view file:
<View style={{ flex: 1 }}>
<WebView
originWhitelist={['*']}
javaScriptEnabled={true}
domStorageEnabled={true}
scalesPageToFit={true}
allowFileAccess={true}
allowFileAccessFromFileURLs={true}
source={{ html: htmlContent }}
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
onMessage={(event) => {
console.log(event.nativeEvent.data);
}}
/>
</View>import { StyleSheet, Text, View, Linking } from 'react-native'; // Import Linking
import { WebView } from 'react-native-webview';
import { useEffect, useState } from 'react';
import { Asset } from 'expo-asset';
import { readAsStringAsync } from 'expo-file-system';
export default function App() {
const [html, setHtml] = useState('');
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
const files = await Asset.loadAsync(require('./src/index.html'));
const fileContents = await readAsStringAsync(files[0].localUri);
setHtml(fileContents);
})();
}, []);
const handleShouldStartLoadWithRequest = (event) => {
const url = event.url;
const regex = new RegExp('(upi|intent)');
if (Platform.OS === 'android' || Platform.OS === 'ios') {
if (regex.test(url)) {
Linking.openURL(url);
return false;
}
}
return true;
};
return (
<View style={{ flex: 1 }}>
<WebView
originWhitelist={['*']}
javaScriptEnabled={true}
domStorageEnabled={true}
scalesPageToFit={true}
allowFileAccess={true}
allowFileAccessFromFileURLs={true}
source={{ html }}
onShouldStartLoadWithRequest={handleShouldStartLoadWithRequest}
onMessage={(event) => {
console.log(event.nativeEvent.data);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});Step 2: Handle intent links
const handleShouldStartLoadWithRequest = (event: any) => {
const url = event.url;
const regex = new RegExp('(upi|intent)');
if ((Platform.OS === 'android' || Platform.OS === 'ios') && regex.test(url)) {
Linking.openURL(url);
return false;
}
return true;
};Flutter
Step 1: Add below dependencies in pubspec.yaml file
...
...
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.0.20
flutter_inappwebview: ^5.7.2+3
...
...Step 2: Import necessary packages
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher/url_launcher.dart';Step 3: Set up InAppWebView with UPI launch capability
InAppWebView(
….
shouldOverrideUrlLoading: (controller, navigationAction) async {
var uri = navigationAction.request.url!;
if (!["http", "https", "file", "chrome", "data", "javascript", "about"].contains(uri.scheme)) {
// Check if it's a UPI URL
bool isUpiUrl = RegExp(r"(upi)", caseSensitive: false).hasMatch(uri.toString());
if (isUpiUrl) {
if (await canLaunchUrl(uri)) {
// Launch the UPI app
await launchUrl(uri, mode: LaunchMode.externalApplication);
// Cancel the request in WebView
return NavigationActionPolicy.CANCEL;
}
}
}
return NavigationActionPolicy.ALLOW;
},
// ... other InAppWebView parameters ...
)Updated 2 months ago
