upi_intent πΈ
A modern, production-ready Flutter plugin for UPI payments β with a beautiful built-in app picker, NPCI-compliant URL construction, typed response parsing, and active maintenance.
π The only UPI package with a built-in Material 3 app picker. Replaces outdated packages like
upi_pay(abandoned 2+ years ago).
β¨ Features
| Feature | Details |
|---|---|
| π¨ Beautiful App Picker | Built-in Material 3 bottom sheet β no extra code needed |
| π NPCI-Compliant URLs | Correct upi://pay format per official NPCI spec |
| β VPA Validator | Client-side format validation before initiating payment |
| π± Android + iOS | Full cross-platform support |
| π€ Android 11+ Ready | Required <queries> manifest block included |
| π Auto Dark Mode | App picker adapts to system theme automatically |
| π§ͺ Null-safe & Typed | Full null-safety with typed UpiResponse model |
| π¦ Zero bloat | No unnecessary dependencies |
πΈ Screenshots
π Installation
Step 1 β Add dependency
# pubspec.yaml
dependencies:
upi_intent: ^1.0.0
Then run:
flutter pub get
Step 2 β Android Setup β οΈ Required!
Open your app's android/app/src/main/AndroidManifest.xml and add the <queries> block:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- β
Required for Android 11+ (API 30+) to detect UPI apps -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="upi" />
</intent>
</queries>
<application
android:label="your_app"
...>
...
</application>
</manifest>
β Without this block, zero UPI apps will be detected on Android 11 and above!
Step 3 β iOS Setup (Optional)
Open ios/Runner/Info.plist and add URL scheme whitelist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>gpay</string>
<string>phonepe</string>
<string>paytmmp</string>
<string>bhim</string>
<string>upi</string>
</array>
βΉοΈ iOS Note: Due to platform restrictions, iOS cannot return detailed transaction data. Always verify payment on your backend server.
π‘ Usage
Basic Payment (with Built-in App Picker)
import 'package:upi_intent/upi_intent.dart';
// Call inside an async function that has access to BuildContext
Future<void> makePayment(BuildContext context) async {
try {
final UpiResponse? response = await UpiIntent.pay(
context: context,
payment: UpiPayment(
payeeVpa: 'merchant@upi', // β Payee's UPI ID (required)
payeeName: 'My Online Store', // β Payee name (required)
amount: 299.00, // β Amount in INR (optional)
transactionNote: 'Order #1234', // β Payment note (optional)
transactionRefId: 'TXN_001', // β Your reference ID (optional)
),
);
if (response == null) {
// User dismissed the app picker without selecting
print('User cancelled');
return;
}
if (response.isSuccess) {
// β
Payment marked successful by UPI app
print('Payment successful!');
print('Transaction ID: ${response.transactionId}');
// β οΈ IMPORTANT: Always verify on backend before confirming order!
await verifyOnBackend(response.transactionId!);
} else {
// β Payment failed or pending
print('Payment status: ${response.status}');
}
} on UpiException catch (e) {
// Plugin-level errors (invalid VPA, no UPI apps found, etc.)
print('UPI Error: ${e.message}');
}
}
Pay with a Specific App (Skip the Picker)
// Get all installed UPI apps
final List<UpiApp> apps = await UpiIntent.getInstalledApps();
// Find a specific app
final googlePay = apps.firstWhereOrNull(
(app) => app.packageName == 'com.google.android.apps.nbu.paisa.user',
);
if (googlePay == null) {
print('Google Pay is not installed');
return;
}
// Pay directly with that app
final response = await UpiIntent.payWithApp(
payment: UpiPayment(
payeeVpa: 'merchant@upi',
payeeName: 'My Shop',
amount: 99.00,
),
app: googlePay,
);
Validate a VPA Before Payment
// Validate format before calling pay()
final String vpa = 'user@okicici';
if (!UpiValidator.isValidVpa(vpa)) {
showDialog(context: context, builder: (_) => AlertDialog(
title: const Text('Invalid VPA'),
content: const Text('Please enter a valid UPI ID (e.g. name@upi)'),
));
return;
}
// Now safe to proceed with payment
await UpiIntent.pay(context: context, payment: UpiPayment(payeeVpa: vpa, ...));
Get List of Installed UPI Apps
final List<UpiApp> apps = await UpiIntent.getInstalledApps();
for (final app in apps) {
print('${app.name} β ${app.packageName}');
}
// Example output:
// Google Pay β com.google.android.apps.nbu.paisa.user
// PhonePe β com.phonepe.app
// Amazon Pay β in.amazon.mShop.android.shopping
Build UPI URL (for QR Codes)
final String upiUrl = UpiIntent.buildUpiUrl(
UpiPayment(
payeeVpa: 'merchant@upi',
payeeName: 'My Shop',
amount: 199.00,
transactionNote: 'Online order',
),
);
// Result: upi://pay?pa=merchant@upi&pn=My+Shop&am=199.00&cu=INR&tn=Online+order
print(upiUrl); // Use this URL in a QR code widget
π API Reference
UpiPayment β Payment Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payeeVpa |
String |
β | Payee's UPI Virtual Payment Address (e.g. name@upi) |
payeeName |
String |
β | Name of the payee / merchant |
amount |
double? |
β | Amount in INR. Leave null to let user enter amount |
transactionNote |
String? |
β | Short description shown in UPI app |
transactionRefId |
String? |
β | Your order/transaction reference ID |
merchantCode |
String? |
β | Merchant Category Code (MCC) for business payments |
UpiResponse β Payment Response
| Property | Type | Description |
|---|---|---|
status |
UpiTransactionStatus |
Outcome of the transaction |
isSuccess |
bool |
true only when status is success |
transactionId |
String? |
UPI network transaction ID (txnId) |
approvalRefNo |
String? |
Bank approval reference number |
responseCode |
String? |
Raw response code from bank |
UpiTransactionStatus Enum
| Value | Meaning | What to do |
|---|---|---|
success |
UPI app reported success | β
Verify transactionId on backend |
failure |
Payment failed | β Show error, let user retry |
submitted |
Submitted to bank, pending | β³ Check backend after a few seconds |
unknown |
Status unclear | π Always verify via backend |
UpiApp β Installed App Info
| Property | Type | Description |
|---|---|---|
name |
String |
Display name (e.g. "Google Pay") |
packageName |
String |
Android package name |
icon |
List<int>? |
App icon as raw bytes (for custom UI) |
UpiValidator β Static Helpers
// Check if a VPA has valid format (user@handle)
bool UpiValidator.isValidVpa(String vpa)
// Check if amount is within NPCI limits (βΉ1 β βΉ1,00,000)
bool UpiValidator.isValidAmount(double amount)
π¦ Supported UPI Apps
| App | Android | iOS |
|---|---|---|
| Google Pay | β | β |
| PhonePe | β | β |
| Paytm | β | β |
| Amazon Pay | β | β |
| WhatsApp Pay | β | β |
| BHIM | β | β |
| FreeCharge | β | β |
| MobiKwik | β | β |
| Airtel Thanks | β | β |
| YONO SBI | β | β |
| iMobile ICICI | β | β |
| Any other UPI app | β | β οΈ |
iOS Note: Only apps with registered URL schemes can be detected on iOS.
β οΈ Security β Important!
Never trust client-side UPI responses alone.
A malicious user could fake a success response. Always verify the transactionId on your server:
// β WRONG β Do NOT do this
if (response.isSuccess) {
confirmOrder(); // Dangerous!
}
// β
CORRECT β Always verify on backend
if (response.isSuccess && response.transactionId != null) {
final verified = await myBackend.verifyUpiTransaction(
txnId: response.transactionId!,
amount: 299.00,
vpa: 'merchant@upi',
);
if (verified) confirmOrder();
}
π οΈ Troubleshooting
β No UPI apps detected on Android 11+?
β Add the <queries> block to your AndroidManifest.xml β see Android Setup.
β UpiException: Invalid UPI VPA thrown?
β Validate the VPA first using UpiValidator.isValidVpa(vpa).
β Payment works but response is null?
β User dismissed the app picker. Handle the null case gracefully.
β Works on physical device but not emulator? β Expected behavior. UPI apps are not available on emulators. Test on a real device.
β iOS showing submitted status always?
β iOS cannot return detailed transaction data due to platform restrictions. Verify on backend.
β Lost connection to device during testing?
β Normal! The Flutter app goes to background when UPI app opens. Debug connection drops. Payment still works β press the back button and check result.
π Changelog
See CHANGELOG.md for version history.
π License
MIT License β Copyright (c) 2025 Yash Dodani
See LICENSE for full details.
π Contributing
PRs are welcome! Please open an issue first to discuss what you'd like to change.
- Fork the repo
- Create a feature branch:
git checkout -b feature/my-feature - Commit changes:
git commit -m 'Add my feature' - Push:
git push origin feature/my-feature - Open a Pull Request
Made with β€οΈ by Yash Dodani