Unified Payments Interface (UPI) processes billions of transactions every month, dominating digital commerce across India and expanding internationally. For Android developers targeting the Indian consumer base, seamless UPI Intent flow and reliable payment gateway integration (like Razorpay) are non-negotiable requirements. Here is how to engineer a secure payment pipeline.
1. The Two Integration Approaches
- Direct UPI Intent URI (Zero Gateway Fees): Fires an
android.intent.action.VIEWintent with aupi://payURI. Launches installed apps (GPay, PhonePe, BHIM) directly to pay your merchant VPA. - Payment Gateway SDK (Razorpay / Cashfree): Provides automated reconciliation, credit cards, debit cards, net banking, and automatic webhook verification.
2. Approach A: Direct UPI Intent in Kotlin
Construct a compliant NPCI UPI payment URI and launch the system app chooser:
import android.content.Intent
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
fun initiateUpiPayment(
vpa: String = "mohit@upi",
merchantName: String = "JMD WORLD",
txnId: String,
amount: Double,
note: String = "Software License Order"
) {
val uri = Uri.parse("upi://pay").buildUpon()
.appendQueryParameter("pa", vpa) // Payee VPA
.appendQueryParameter("pn", merchantName) // Payee Name
.appendQueryParameter("tr", txnId) // Transaction Ref ID
.appendQueryParameter("am", String.format("%.2f", amount)) // Amount
.appendQueryParameter("cu", "INR") // Currency
.appendQueryParameter("tn", note) // Note
.build()
val upiIntent = Intent(Intent.ACTION_VIEW, uri)
val chooser = Intent.createChooser(upiIntent, "Pay with UPI Application")
// Launch activity for result
}
3. Approach B: Razorpay Standard Checkout SDK
Add the dependency to your build.gradle.kts:
implementation("com.razorpay:checkout:1.6.36")
In your Checkout Activity:
import com.razorpay.Checkout
import com.razorpay.PaymentResultListener
import org.json.JSONObject
class PaymentActivity : AppCompatActivity(), PaymentResultListener {
fun startRazorpayPayment(orderId: String, amountInPaise: Int) {
val checkout = Checkout()
checkout.setKeyID("rzp_live_YourKeyHere")
val options = JSONObject().apply {
put("name", "JMD WORLD")
put("description", "Enterprise Software Services")
put("currency", "INR")
put("amount", amountInPaise) // e.g. 50000 = 500.00 INR
put("order_id", orderId)
put("prefill", JSONObject().apply {
put("email", "client@example.com")
put("contact", "8530008980")
})
}
checkout.open(this, options)
}
override fun onPaymentSuccess(razorpayPaymentId: String?) {
// Confirm with your PHP backend server
}
override fun onPaymentError(code: Int, response: String?) {
// Handle cancellation or payment failure
}
}
4. Server-Side Signature Verification in PHP
Never rely on client-side success callbacks alone! Attackers can forge onPaymentSuccess. Always verify the HMAC SHA-256 signature on your PHP server before fulfilling orders:
<?php
$orderId = $_POST['razorpay_order_id'];
$paymentId = $_POST['razorpay_payment_id'];
$signature = $_POST['razorpay_signature'];
$secret = 'YourRazorpayKeySecret';
$expectedSignature = hash_hmac('sha256', "$orderId|$paymentId", $secret);
if (hash_equals($expectedSignature, $signature)) {
// Payment authentic: Update database status to 'completed'
echo json_encode(['verified' => true]);
} else {
http_response_code(400);
echo json_encode(['verified' => false, 'error' => 'Invalid payment signature']);
}
?>
Conclusion
Combining the frictionless customer experience of UPI Intent on the front-end with rigorous HMAC signature validation on your backend produces an enterprise-grade fintech integration that protects both merchant revenue and buyer trust.
In-Article Sponsored Content
Suggested Engineering Guides
View All Guides (22+)
Recommended For You
Ads by Google
Comments (0)
No comments yet. Share your thoughts below!
Leave a Comment
Share your thoughts or questions. Your email address remains private.