UPI Payment Flow on Android: From URI Construction to Result Verification
How to implement secure and modern UPI integration with Kotlin
1Importance of UPI on Android
UPI has become the default payment mechanism for Android apps in India. Most apps use standard intent-based flows where payments execute in external UPI apps and results return to the caller. This approach eliminates direct handling of user credentials or PINs by the app, enhancing security. The system relies on interoperable UPI apps that users already trust for financial transactions.
2UPI URI Structure
UPI payment requests use the 'upi://pay' scheme. Key parameters include payee's virtual address (pa), payee name (pn), amount (am), currency (cu), and transaction reference (tr). Using Uri.Builder in Kotlin ensures proper encoding and URI construction. Example: 'upi://pay?pa=test@upi&pn=Test%20Merchant&am=100.00&cu=INR&tr=TXN123'. Always validate parameters before URI generation to prevent malformed requests.
3Launching UPI with Modern APIs
Instead of the deprecated onActivityResult(), Android now recommends lifecycle-aware Activity Result APIs. Using registerForActivityResult provides safer intent launching and result handling. This modern approach better manages payment states (success, failure, cancellation) and prevents common edge cases like activity recreation during payment flows.
4Understanding UPI Responses
UPI apps return responses as key-value strings, typically like 'Status=SUCCESS&txnId=123456&ApprovalRefNo=987654'. However, field names may vary across apps, parameter order isn't guaranteed, and some fields might be missing. Developers must parse responses defensively, avoiding assumptions about specific field presence or formatting.
5Response Validation Logic
A UPI response should be considered valid only if: the string isn't null/blank, contains a Status field, the status is recognizable (SUCCESS/FAILURE/PENDING), and at least one transaction identifier exists (txnId/ApprovalRefNo/txnRef). Missing any condition means treating the response as cancelled/invalid. This strict validation prevents acting on incomplete or interrupted payment flows.
6Handling Payment States
Upon valid response, route based on Status: SUCCESS triggers payment confirmation, FAILURE shows error details, others indicate pending states. For UX, display a neutral 'Processing' state initially, then resolve UI after parsing. Never assume instant success - network delays or user interruptions might require graceful handling of indeterminate states.
7QR-Based UPI Initiation
UPI QR flows encode standard UPI URIs into QR codes. Using QRCodeWriter generates scannable bitmaps that any UPI app can process. This method excels for physical payment points. The QR content must follow UPI URI specifications exactly. Consider adding error correction for reliability in suboptimal scanning conditions.
8Testing Recommendations and Summary
Thoroughly test with: multiple UPI apps, all flow outcomes (success/failure/cancellation), invalid/zero amounts, and app backgrounding during payments. Prefer real devices over emulators for accurate payment app behavior. Reliable UPI flows require: correct URI construction, modern intent handling, defensive parsing, and explicit validation - creating predictable, resilient payment experiences that users trust.