Razorpay Integration in Jetpack Compose: A Complete Guide
Why Choose Razorpay for Android Apps?
- Accept payments via UPI, cards (debit/credit), net-banking, and popular wallets—out of the box.
- Easy SDK integration: Comes with a dedicated Android SDK.
- Secure and reliable: PCI DSS compliant and provides end-to-end encryption.
- Developer-friendly: Clear documentation, sandbox mode for testing, and fast onboarding.
- Scalability: Suitable for startups as well as large-scale apps.
Prerequisites
- Android Studio Narwhal or above.
- Jetpack Compose already set up in your project.
- To get started, you’ll need a Razorpay account. The good news is that Razorpay provides a free sandbox (test) account where you can experiment with payments before moving to live transactions.
- Minimum Android SDK: 21+ (recommended).
How to use Razorpay in Android Jetpack Compose App?
Razorpay Payment Integration in Jetpack Compose using MVVM Architecture (2026 Guide)
Integrating payments is a critical part of many Android applications, especially in India where Razorpay is one of the most popular payment gateways. In this article, you will learn how to integrate Razorpay Checkout in a Jetpack Compose app using MVVM architecture.
This guide follows modern Android best practices:
- Jetpack Compose UI
- MVVM Architecture
- Single Responsibility Principle
- Clean separation of UI and business logic
Why Use MVVM for Payment Integration?
Payment logic should never be tightly coupled with UI code. MVVM helps us:
- Keep UI clean and reactive
- Handle payment states properly
- Easily test and maintain code
- Scale for subscriptions and multiple plans
Project Architecture Overview
com.example.paymentapp
│
├── ui
│ ├── PaymentScreen.kt
│
├── viewmodel
│ ├── PaymentViewModel.kt
│
├── payment
│ ├── RazorpayManager.kt
│
└── MainActivity.kt
Step 1: Add Razorpay Dependency
dependencies {
implementation "com.razorpay:checkout:1.6.33"
}
Step 2: Android Manifest Setup
<uses-permission android:name="android.permission.INTERNET" />
<application>
<activity
android:name="com.razorpay.CheckoutActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
Step 3: Payment State Model
We define a sealed class to represent payment states.
sealed class PaymentState {
object Idle : PaymentState()
object Loading : PaymentState()
data class Success(val paymentId: String) : PaymentState()
data class Error(val message: String) : PaymentState()
}
Step 4: Razorpay Manager (Business Logic Layer)
This class handles Razorpay checkout logic.
class RazorpayManager(
private val activity: Activity
) {
fun startPayment(
amount: Int,
onSuccess: (String) -> Unit,
onError: (String) -> Unit
) {
val checkout = Checkout()
checkout.setKeyID("rzp_test_xxxxxxx")
val options = JSONObject()
options.put("name", "Coding Bihar")
options.put("description", "Premium Course")
options.put("currency", "INR")
options.put("amount", amount)
val prefill = JSONObject()
prefill.put("email", "test@gmail.com")
prefill.put("contact", "9999999999")
options.put("prefill", prefill)
try {
checkout.open(activity, options)
} catch (e: Exception) {
onError(e.message ?: "Payment failed")
}
}
}
Step 5: Payment ViewModel
The ViewModel controls payment flow and exposes UI state.
class PaymentViewModel : ViewModel() {
private val _paymentState = mutableStateOf<PaymentState>(PaymentState.Idle)
val paymentState: State<PaymentState> = _paymentState
fun startPayment(
razorpayManager: RazorpayManager,
amount: Int
) {
_paymentState.value = PaymentState.Loading
razorpayManager.startPayment(
amount = amount,
onSuccess = {
_paymentState.value = PaymentState.Success(it)
},
onError = {
_paymentState.value = PaymentState.Error(it)
}
)
}
}
Step 6: Jetpack Compose UI
Compose UI reacts automatically to payment state changes.
@Composable
fun PaymentScreen(
viewModel: PaymentViewModel,
onPayClick: () -> Unit
) {
val state = viewModel.paymentState.value
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
when (state) {
is PaymentState.Loading -> {
CircularProgressIndicator()
}
is PaymentState.Success -> {
Text("Payment Successful")
Text("Payment ID: ${state.paymentId}")
}
is PaymentState.Error -> {
Text("Payment Failed")
Text(state.message)
}
else -> {
Button(onClick = onPayClick) {
Text("Pay ₹499")
}
}
}
}
}
Step 7: MainActivity Integration
class MainActivity : ComponentActivity(), PaymentResultListener {
private lateinit var razorpayManager: RazorpayManager
private val viewModel: PaymentViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Checkout.preload(applicationContext)
razorpayManager = RazorpayManager(this)
setContent {
PaymentScreen(
viewModel = viewModel,
onPayClick = {
viewModel.startPayment(
razorpayManager,
amount = 49900
)
}
)
}
}
override fun onPaymentSuccess(paymentId: String?) {
paymentId?.let {
viewModel.paymentState.value =
PaymentState.Success(it)
}
}
override fun onPaymentError(code: Int, response: String?) {
viewModel.paymentState.value =
PaymentState.Error(response ?: "Unknown error")
}
}
Security Best Practices
- Never store Razorpay Secret Key in app
- Create order ID from backend
- Verify payment signature on server
- Use HTTPS only
Conclusion
Using MVVM with Jetpack Compose makes Razorpay integration clean, scalable, and production-ready. This architecture is suitable for:
- Paid courses
- Subscriptions
- In-app purchases
- UPI-based payments
If you want advanced topics like server-side verification, subscriptions, or UPI-only checkout, you can extend this architecture easily.
Happy Coding π
Best Practices for Razorpay Integration
- It’s a good practice to let your backend server handle the process of creating and validating payment orders. Don’t expose sensitive keys in the app.
- Handle network failures gracefully and give retry options.
- Show clear UI messages so users know if their payment is processing.
- This ensures better security and prevents misuse of your API keys. While testing your integration, keep detailed logs enabled so you can track any errors easily. However, once your app goes live, make sure to turn off unnecessary logging to keep your production environment secure and clean.
- Test across devices to avoid unexpected crashes.
