UPI
The Unified Payments Interface (UPI) is a real-time payment system that allows users to transfer funds between bank accounts using their mobile phones. UPI has revolutionized the way people make payments in India, making it faster, more efficient, and more convenient.
One of the key features of UPI is the intent mechanism, which enables seamless payment flows between different apps and services. With the intent mechanism, users can initiate a payment from within any app or service that supports UPI payments, without having to switch to a separate payments app.
To enable seamless payment flow using the UPI intent mechanism, developers need to integrate the UPI SDK into their apps or services. The UPI SDK provides a set of APIs that allow developers to initiate UPI payments from within their apps or services.
Prerequisites
- Business channels must accept UPI and be verified merchants by NPCI/banks.
- Ensure you have the details required to accept payments using UPI ID with your bank.
- Ensure that you have all of the required APIs from your bank to check the status of a payment.
- Note that every transaction should use a unique transaction ID.
Getting Started
The UPI intent mechanism works by using a deep link to launch the UPI payments app on the user's device.
Let's look into the intent.
The Intent.action
is simply ACTION_VIEW,
which is usually used to display data to the user.
And the data is just the URI of a specific format that is expected.
val upiPaymentUri = Uri.Builder()
.scheme("upi")
.authority("pay")
.appendQueryParameter("pa", "your-merchant-vpa@xxx")
.appendQueryParameter("pn", "your-merchant-name")
.appendQueryParameter("mc", "your-merchant-code")
.appendQueryParameter("tr", "your-transaction-ref-id")
.appendQueryParameter("tn", "your-transaction-note")
.appendQueryParameter("am", "your-order-amount")
.appendQueryParameter("cu", "INR")
.build()
[add copy]
The scheme and authority are used to link and map supported applications, reducing ambiguity.
The rest of the query parameters are as follows:
.png)
There are two ways to start UPI intent flow
- Implicit Intent flow
- Explicit Intent flow
Implicit Intent flow
In the Implicit Intent flow, your application will allow the user to choose from all the available apps that can handle UPI payments and are ready for UPI payments. It is also a mandatory option that your application should support as per NPCI.
To display all supported applications, create an Intent chooser.
val genericUpiPaymentIntent = Intent.createChooser(
Intent().apply {
action = Intent.ACTION_VIEW
data = upiPaymentUri
},
"Pay with"
)
[add copy]
Now, register for activity result
//Activity
val activityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val status = it.data?.getStringExtra("Status")
Toast.makeText(this, status, Toast.LENGTH_LONG).show()
}
[add copy]
//Compose
val activityResultLauncher =
rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
val status = it.data?.getStringExtra("Status")
Toast.makeText(context, status, Toast.LENGTH_LONG).show()
}
[add copy]
And, launch accordingly
Button(onClick = { activityResultLauncher.launch(genericUpiPaymentIntent) }) {
Text(text = "Pay with UPI")
}
[add copy]
Explicit Intent flow
In the Explicit Intent flow, your application will target a specific application instead of all available applications, and the operating system will launch that specific application directly. However, this method is not required, but it will greatly improve the user experience.
First, we need the package of the application that we need to target our intent to. For example, here is the list of packages of some of the applications that support UPI intent flow;
Google Pay - com.google.android.apps.nbu.paisa.user
Paytm - net.one97.paytm
PhonePe - com.phonepe.app
You can find the list of other supported apps here.
Now add this info to the intent
val gpayUPIPaymentIntent = Intent().apply {
action = Intent.ACTION_VIEW
data = upiPaymentUri
setPackage("com.google.android.apps.nbu.paisa.user")
}
[add copy]
And, launch accordingly
Button(onClick = { activityResultLauncher.launch(gpayUPIPaymentIntent) }) {
Text(text = "Pay with UPI")
}
[add copy]
What if the user does not have that specific application installed?
In that case OS will throw the following error
Error: android.content.ActivityNotFoundException: No Activity found to handle Intent
We can check whether that particular application is installed and is ready for UPI payments with the following methods:
//Determines whether the application is installed or not
fun Context.isAppInstalled(packageName: String): Boolean {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(
PackageManager.GET_ACTIVITIES.toLong()))
} else {
packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
}
return true
} catch (e: PackageManager.NameNotFoundException) {
//Log the error
}
return false
}
//Determines if the application is ready for UPI payments
fun Context.isAppUpiReady(packageName: String): Boolean {
val upiIntent = Intent(Intent.ACTION_VIEW, Uri.parse("upi://pay"))
val upiActivities: List<ResolveInfo> =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.queryIntentActivities(
upiIntent, PackageManager.ResolveInfoFlags.of(
PackageManager.MATCH_DEFAULT_ONLY.toLong()
)
)
} else {
packageManager.queryIntentActivities(upiIntent, PackageManager.MATCH_DEFAULT_ONLY)
}
for (activities in upiActivities) {
if (activities.activityInfo.packageName == packageName) return true
}
return false
}
[add copy]
The result of the above two methods can be used to display only the available explicit UPI payment option.

Here is how you can access the app icon and label with help of PackageManager
:
fun Context.getIconsAndLabelOfUPApplications(): Map<String, Bitmap> {
val upiIntent = Intent(Intent.ACTION_VIEW, Uri.parse("upi://pay"))
val upiActivities: List<ResolveInfo> =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.queryIntentActivities(
upiIntent, PackageManager.ResolveInfoFlags.of(
PackageManager.MATCH_DEFAULT_ONLY.toLong()
)
)
} else {
packageManager.queryIntentActivities(upiIntent, PackageManager.MATCH_DEFAULT_ONLY)
}
return upiActivities.associate {
val drawable = it.loadIcon(packageManager)
val appName = it.loadLabel(packageManager).toString()
appName to getBitmapFromImage(drawable)
}
}
private fun getBitmapFromImage(drawable: Drawable): Bitmap {
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
[add copy]
However you can directly access the icon and label of the application, and use them directly, but it is recommended to use the guidelines provided by each application.
For example here is the brand guideline for Google Pay.
Android 11 requirements
Merchants who target API level 30 or higher and run on Android 11 will only be able to view a limited number of apps.
To include the specific application app, include the package name in <package
elements inside the <queries
element.
<manifest>
.
.
.
<queries>
<package android:name="com.google.android.apps.nbu.paisa.user" />
<package android:name="net.one97.paytm" />
<package android:name="com.phonepe.app" />
</queries>
</manifest>
[add copy]
Note
At this moment payment receiver should be a valid merchant with all the required details with it, like merchant code, sign, etc., which will be provided to you by the provider.
In conclusion, the UPI intent mechanism is a powerful tool for enabling seamless payment flows between different apps and services. By integrating the UPI SDK into their apps or services, developers can provide a more convenient and efficient payment experience for their users, while also driving higher conversion rates for their businesses.
References: https://developers.google.com/pay/india/api/android