upi_intent 1.0.1
upi_intent: ^1.0.1 copied to clipboard
Launch UPI payment apps with a beautiful built-in app picker. Supports Android & iOS. NPCI spec compliant. The modern replacement for upi_pay.
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
