Verify Paystack Payments in Android with Kotlin
Extract the transaction reference from the WebView callback URL. Send it to your backend verify endpoint using Retrofit (or OkHttp) with Kotlin coroutines. Your backend calls the Paystack verify API and returns the result. Update your Android UI based on the verified flag and amount returned.
ViewModel-Based Verification
// viewmodel/PaymentViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
sealed class VerifyState {
object Loading : VerifyState()
data class Success(val amount: Double, val currency: String, val reference: String) : VerifyState()
data class Failure(val message: String) : VerifyState()
}
class PaymentViewModel : ViewModel() {
private val _verifyState = MutableStateFlow<VerifyState>(VerifyState.Loading)
val verifyState: StateFlow<VerifyState> = _verifyState
fun verifyPayment(reference: String) {
viewModelScope.launch {
_verifyState.value = VerifyState.Loading
try {
val result = RetrofitClient.paymentApi.verify(reference)
if (result.verified) {
_verifyState.value = VerifyState.Success(
amount = result.amount ?: 0.0,
currency = result.currency ?: "NGN",
reference = result.reference ?: reference
)
} else {
_verifyState.value = VerifyState.Failure("Payment not confirmed")
}
} catch (e: Exception) {
_verifyState.value = VerifyState.Failure(
e.message ?: "Network error. Check your connection."
)
}
}
}
}
Payment Result Activity
// PaymentResultActivity.kt
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class PaymentResultActivity : AppCompatActivity() {
private val viewModel: PaymentViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_payment_result)
val reference = intent.getStringExtra("reference") ?: return finish()
val progressBar = findViewById<ProgressBar>(R.id.progressBar)
val statusText = findViewById<TextView>(R.id.statusText)
val retryButton = findViewById<Button>(R.id.retryButton)
retryButton.setOnClickListener { viewModel.verifyPayment(reference) }
lifecycleScope.launch {
viewModel.verifyState.collect { state ->
when (state) {
is VerifyState.Loading -> {
progressBar.visibility = View.VISIBLE
statusText.text = "Verifying payment..."
retryButton.visibility = View.GONE
}
is VerifyState.Success -> {
progressBar.visibility = View.GONE
statusText.text = "Payment confirmed!
${state.currency} ${state.amount}"
retryButton.visibility = View.GONE
}
is VerifyState.Failure -> {
progressBar.visibility = View.GONE
statusText.text = state.message
retryButton.visibility = View.VISIBLE
}
}
}
}
viewModel.verifyPayment(reference)
}
}
Learn More
This guide is part of the Paystack integration guides by language and framework series.
Key Takeaways
- ✓Verification happens on your backend, not in Android. Never call Paystack API with a secret key from an APK.
- ✓Use Retrofit with coroutines to call your backend verify endpoint from a ViewModel.
- ✓Check both the verified flag and amount in the response. Do not trust amounts from the WebView.
- ✓Handle network errors gracefully. Mobile networks fail. Show a retry button with the reference.
- ✓Use LiveData or StateFlow to update the UI from verification results in the ViewModel.
- ✓Store the reference in SharedPreferences before checkout so verification can resume if the app restarts.
Frequently Asked Questions
- Should I verify payment in an Activity or ViewModel in Android?
- Use a ViewModel. Activities can be recreated (on rotation, etc.), but ViewModel survives configuration changes. Put the Retrofit call in viewModelScope.launch inside the ViewModel. Observe the result in the Activity using LiveData or StateFlow.
- What if the user leaves the app during verification?
- The coroutine in viewModelScope continues running as long as the ViewModel is alive. If the Activity is destroyed and recreated, it reattaches to the same ViewModel and collects the result. Store the reference in SharedPreferences so you can re-verify on app restart if needed.
- How do I test Paystack verification on Android without making real payments?
- Use Paystack test mode and test cards. Initialize test transactions from your backend with test API keys. The test card numbers are documented in the Paystack dashboard. Your Android app works exactly the same way with test keys.
- What HTTP client should I use in Android for calling my backend?
- Retrofit with OkHttp is the standard for Android. It supports coroutines via suspend functions and handles JSON deserialization with converters. Alternatively, use Ktor client for a Kotlin Multiplatform compatible solution.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon