McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Verify Paystack Payments in Android with Kotlin
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

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.

In this article

  1. 1. ViewModel-Based Verification
  2. 2. Payment Result Activity
  3. 3. Learn More

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)
    }
}

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Learn More

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

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.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Accept Payments with Paystack in Android with Kotlin

Accept Paystack payments in a native Android app built with Kotlin. This guide covers initializing transactions from your backend, displaying the Paystack checkout in a WebView Activity, detecting the callback redirect, and verifying payment.

Read more

Handle Paystack Webhooks in Android with Kotlin

Paystack webhooks arrive at your server, not your Android app. This guide covers building the backend handler and delivering payment events to an Android Kotlin app via FCM push notifications or polling.

Read more

Transaction Not Found on Paystack Verify

Paystack returns "Transaction not found" when the reference you pass to the verify endpoint does not match any transaction in the current environment. The causes range from wrong reference strings to test/live key confusion to URL encoding problems. This guide covers every cause with specific fixes.

Read more

Idempotency in Paystack Payment Flows

Duplicate charges are the nightmare scenario for any payment integration. A customer clicks pay twice, a network timeout triggers a retry, or a webhook fires before the redirect callback. Idempotency is the engineering discipline that makes sure the same payment operation produces the same result no matter how many times it runs.

Read more

Paystack Callback URLs: Per-Transaction vs Dashboard Configuration

Paystack gives you two ways to set where customers land after paying: a default callback URL in your dashboard settings, or a per-transaction callback_url in the initialize request. Getting the priority order wrong, or not understanding how query parameters work, leads to lost customers and silent fulfillment failures.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.