<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Abdullahi Musa</title>
    <description>The latest articles on Forem by Abdullahi Musa (@mrammia).</description>
    <link>https://forem.com/mrammia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1018022%2Fd7bbda7c-723b-4938-92fe-b35c588470b1.png</url>
      <title>Forem: Abdullahi Musa</title>
      <link>https://forem.com/mrammia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mrammia"/>
    <language>en</language>
    <item>
      <title>Flutterwave Integration With Compose</title>
      <dc:creator>Abdullahi Musa</dc:creator>
      <pubDate>Mon, 25 Aug 2025 10:20:00 +0000</pubDate>
      <link>https://forem.com/mrammia/flutterwave-integration-with-compose-2n21</link>
      <guid>https://forem.com/mrammia/flutterwave-integration-with-compose-2n21</guid>
      <description>&lt;p&gt;Flutterwave remains one of the most reliable gateway for businesses across Africa and beyond. However, most existing resources focus on traditional XML-based UI and deprecated &lt;code&gt;onActivityResult()&lt;/code&gt; methods, leaving a gap for developers building apps with Jetpack Compose. In this tutorial, we’ll walk through the step-by-step process of integrating Flutterwave into a Compose project—from setup to handling transactions—so you can start accepting payments seamlessly in your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Requirements
&lt;/h2&gt;

&lt;p&gt;It is important to note that this tutorial will utilise Flutterwave's default Drop In UI thus, apart from the knowing the basis of Android Native Development, the only other requirements are&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A Flutterwave account with encryption and public keys (for testing purposes only, feel free to use the ones in this tutorial).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gradle dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manifest permission.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Setting Up Your Project
&lt;/h4&gt;

&lt;p&gt;Now create a new Android project with compose and use Kotlin DSL for your gradle. After it builds, add the following dependencies to the app-level gradle like in the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.code.gson:gson:2.11.0")
implementation("com.github.flutterwave.rave-android:rave_android:2.2.1")
implementation("org.parceler:parceler-api:1.1.13")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Side bar:&lt;br&gt;
Flutterwave requires AppCompactActivity thus we need to add it to our gradle (if not already present) and tweak our themes.xml to avoid crashes. Simply change the parent from &lt;code&gt;parent="android:Theme.Material.Light.NoActionBar"&lt;/code&gt; to &lt;code&gt;parent="Theme.AppCompat.Light.NoActionBar"&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration/blob/master/app/build.gradle.kts" rel="noopener noreferrer"&gt;build.gradle.kts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our settings.gradle.kts, in the &lt;code&gt;dependencyResolutionManagement&lt;/code&gt; section, replace :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the &lt;code&gt;repositories&lt;/code&gt; section just below, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maven { url = uri("https://jitpack.io") }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;dependencyResolutionManagement&lt;/code&gt; in settings.gradle.kts should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        maven { url = uri("https://jitpack.io") }
        mavenCentral()
        google()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration/blob/master/settings.gradle.kts" rel="noopener noreferrer"&gt;settings.gradle.kts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sync the changes.&lt;/p&gt;

&lt;p&gt;Add the following permission to our mainifest just before the application tag, and we're good to go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;uses-permission android:name="android.permission.INTERNET"/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration/blob/master/app/src/main/AndroidManifest.xml" rel="noopener noreferrer"&gt;AndroidManifest.xml&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The basic idea is represented by the diagram below. A user initiates the payment by pressing a button &lt;em&gt;[1]&lt;/em&gt; which sends a request to a secure backend server. The response contains public and encryption keys of our Flutterwave account as well as other required parameters to configure a payment request to Flutterwave &lt;em&gt;[2]&lt;/em&gt; . The configurations are then packaged as an intent &lt;em&gt;[3]&lt;/em&gt; launched using &lt;code&gt;rememberLauncherForActivityResult&lt;/code&gt; to start the Flutterwave default Drop UI &lt;em&gt;[4]&lt;/em&gt; . Flutterwave then processes the transaction and sends a response back &lt;em&gt;[5]&lt;/em&gt; .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwiay6swbp8y7oi152nm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwiay6swbp8y7oi152nm.png" alt="transaction flow image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up UI
&lt;/h2&gt;

&lt;p&gt;For the UI, a simple &lt;code&gt;TextField&lt;/code&gt; and a &lt;code&gt;Button&lt;/code&gt; should do. The &lt;code&gt;TextField&lt;/code&gt; is wired to accept only decimals with two decimal places&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import android.app.Activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.flutterwavecompseintegration.ui.theme.FlutterwaveCompseIntegrationTheme

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            FlutterwaveCompseIntegrationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -&amp;gt;
                    ScreenContent(this,innerPadding)
                }
            }
        }
    }
}


@Composable
fun ScreenContent(activity:Activity, innerPadding: PaddingValues) {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(innerPadding),
        contentAlignment = Alignment.Center
    ) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
            modifier = Modifier.fillMaxWidth()
        ) {
            var amount by remember { mutableStateOf("") }

            TextField(
                value = amount,
                onValueChange = { input -&amp;gt;
                    // Regex: optional digits, optional decimal with up to 2 digits
                    val regex = Regex("^(0|[1-9]\\d*)(\\.\\d{0,2})?$")

                    if (input.isEmpty()) {
                        amount = ""
                    } else if (regex.matches(input)) {
                        amount = input
                    }
                },
                placeholder = { Text("Enter Amount") },
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Decimal,
                    imeAction = ImeAction.Done
                ),
                modifier = Modifier
                    .fillMaxWidth(0.8f) // 80% of screen width
            )

            Spacer(modifier = Modifier.height(16.dp))

            Button(
                onClick = { /* Handle click */ },
                modifier = Modifier.fillMaxWidth(0.5f) // 50% of screen width
            ) {
                Text("Pay")
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ScreenContentPreview() {
    FlutterwaveCompseIntegrationTheme {
        ScreenContent(PaddingValues())
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8zc7p4l269aqienrso5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8zc7p4l269aqienrso5j.png" alt="UI all set up" width="800" height="1777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration/blob/master/app/src/main/java/com/example/flutterwavecomposeintegration/MainActivity.kt" rel="noopener noreferrer"&gt;MainActivity.kt&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Flutterwave
&lt;/h2&gt;

&lt;p&gt;Let's create a file in our project directory to hold our Flutterwave utilities called &lt;code&gt;flutterwave_utils&lt;/code&gt; (or whatever that works for you).&lt;/p&gt;

&lt;p&gt;Ideally, the encryption and public key should come from a secure backend; it should never be hardcoded in any file as I am about to do here. &lt;br&gt;
In addition, if you do get it from a backend, do not log it nor cache it to a room Db or shared preferences. &lt;/p&gt;

&lt;p&gt;Once a user initiates a payment, it should send a request to your backend which then sends a response containing the keys, narration and other useful information like additional Metadata. &lt;br&gt;
A class that shows what a response could look like is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import com.google.gson.annotations.SerializedName


data class FlutterwavePaymentRequest(
    @SerializedName("email")
    val email: String? = null,
    @SerializedName("country")
    val country: String? = null,
    @SerializedName("currency")
    val currency: String? = null,
    @SerializedName("amount")
    val amount: Double? = null,
    @SerializedName("first_name")
    val firstName: String? = null,
    @SerializedName("last_name")
    val lastName: String? = null,
    @SerializedName("narration")
    val narration: String? = null,
    @SerializedName("reference")
    val reference: String? = null,
    @SerializedName("public_key")
    val publicKey: String? = null,
    @SerializedName("encryption_key")
    val encryptionKey: String? = null,
    @SerializedName("is_staging")
    val isStaging: Boolean? = null,
    @SerializedName("metadata") val metadata: Map&amp;lt;String, Any&amp;gt;? = null
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration/blob/master/app/src/main/java/com/example/flutterwavecomposeintegration/flutterwave_utils.kt" rel="noopener noreferrer"&gt;flutterwave_utils.kt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this tutorial, an hardcoded instance would be used &lt;br&gt;
Public and Encryption keys provided by &lt;a href="https://github.com/Gideonjon/Flutterwave_Integration_Kotlin/blob/main/app/src/main/java/com/example/flutter_test/MainActivity.kt" rel="noopener noreferrer"&gt;GideonJon&lt;/a&gt;. Thanks&lt;/p&gt;

&lt;p&gt;in our Button's onClick&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onClick = {
    val amountToPay = amount.toDoubleOrNull() ?: return@Button
    val fee = 209.09
    val paymentRequest = FlutterwavePaymentRequest(
        email = "tester@email.com",
        country = "NG",
        currency = "NGN",
        amount = amountToPay,
        firstName = "first name",
        lastName = "last name",
        narration = "payment for item",
        reference = "txt_ref123456",
        isStaging = true,
        publicKey = "FLWPUBK_TEST-06fe0b1c5d0e3af287d0ec5c99dec6f0-X",
        encryptionKey = "FLWSECK_TESTf5dca3a1293a",
        metadata = mapOf(
            "paymentUserId" to "user_id_1234",
            "fee" to fee,
            "total" to amountToPay + fee,
            "totalInKobo" to (amountToPay + fee) * 100
        )
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the hardcoded &lt;code&gt;FlutterwavePaymentRequest&lt;/code&gt; instance, we will create an instance of &lt;code&gt;RavePayInitializer&lt;/code&gt; from Flutterwave rather than start the &lt;code&gt;RaveUiManager&lt;/code&gt;(Flutterwave's default Drop In UI activity). One of the cool things about Flutterwave is its extensive payment options. From card and USSD to Mpessa, UgMobileMoney and many more so here's where we can customise it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun createRavePayParams(
    paymentRequest: FlutterwavePaymentRequest,
    acceptAccountPayments: Boolean = true,
    acceptUssdPayments: Boolean = true,
    acceptBankTransferPayments: Boolean = true,
    acceptMpesaPayments: Boolean = false,
    acceptUkPayments: Boolean = false,
    acceptUgMobileMoneyPayments: Boolean = false,
    acceptZmMobileMoneyPayments: Boolean = false,
    acceptRwfMobileMoneyPayments: Boolean = false,
    acceptAchPayments: Boolean = false,
    acceptGHMobileMoneyPayments: Boolean = false,
    acceptSaBankPayments: Boolean = false,
    acceptFrancMobileMoneyPayments: Boolean = false,
    theme: Int = com.flutterwave.raveandroid.R.style.DefaultTheme
): RavePayInitializer {
    val paymentMethods = arrayListOf(RaveConstants.PAYMENT_TYPE_CARD)
    if (acceptAccountPayments) {
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_ACCOUNT)
    }
    if (acceptUssdPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_USSD)
    }
    if (acceptBankTransferPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_BANK_TRANSFER)
    }
    if (acceptMpesaPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_MPESA)
    }
    if (acceptUkPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_UK)
    }
    if (acceptUgMobileMoneyPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_UG_MOBILE_MONEY)
    }
    if (acceptZmMobileMoneyPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_ZM_MOBILE_MONEY)
    }
    if (acceptRwfMobileMoneyPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_RW_MOBILE_MONEY)
    }
    if (acceptAchPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_ACH)
    }
    if (acceptGHMobileMoneyPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_GH_MOBILE_MONEY)
    }
    if (acceptSaBankPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_SA_BANK_ACCOUNT)
    }
    if (acceptFrancMobileMoneyPayments){
        paymentMethods.add(RaveConstants.PAYMENT_TYPE_FRANCO_MOBILE_MONEY)
    }
    with(paymentRequest) {
        return RavePayInitializer(
            email,
            amount ?: 0.0,
            publicKey,
            encryptionKey,
            reference,
            narration,
            currency,
            country,
            "NG",
            firstName,
            lastName,
            theme,
            "",
            true,
            true,
            true,
            false,
            0,
            0,
            isStaging ?: true,
            stringifyMeta(metadata?.map { Meta(it.key, it.value.toString()) }),
            "",
            null,
            false,
            true,
            true,
            paymentMethods
        )
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;createRavePayParams()&lt;/code&gt; function takes the request, different payment methods(itemised in the function arguments) as well as theme. We start by creating a list of preferred payment methods then using the request instance, fill in the remaining values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle the response
&lt;/h2&gt;

&lt;p&gt;From official resources, a typical response for every transaction looks like &lt;a href="https://gist.github.com/bolaware/305ef5a6df7744694d9c35787580a2d2" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;br&gt;
The response has been mapped to a data class or Plain Old Java Object(POJO) named &lt;code&gt;FlutterwaveTransactionResponse&lt;/code&gt; . This lets us view transaction status, perform transaction verification with your reference or access the meta and to display the response message to users. This would be added to &lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration/blob/master/app/src/main/java/com/example/flutterwavecomposeintegration/flutterwave_utils.kt" rel="noopener noreferrer"&gt;flutterwave_utils.kt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The response is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class FlutterwaveTransactionResponse(
    @SerializedName("status")
    val status: String? = null,

    @SerializedName("message")
    val message: String? = null,

    @SerializedName("data")
    val data: FlutterwaveTransactionData? = null
)


data class FlutterwaveTransactionData(
    @SerializedName("id") val id: Int? = null,
    @SerializedName("txRef") val txRef: String? = null,
    @SerializedName("flwRef") val flwRef: String? = null,
    @SerializedName("orderRef") val orderRef: String? = null,
    @SerializedName("redirectUrl") val redirectUrl: String? = null,
    @SerializedName("device_fingerprint") val deviceFingerprint: String? = null,
    @SerializedName("cycle") val cycle: String? = null,
    @SerializedName("amount") val amount: Double? = null,
    @SerializedName("charged_amount") val chargedAmount: Double? = null,
    @SerializedName("appfee") val appFee: Double? = null,
    @SerializedName("merchantfee") val merchantFee: Double? = null,
    @SerializedName("merchantbearsfee") val merchantBearsFee: Int? = null,
    @SerializedName("chargeResponseCode") val chargeResponseCode: String? = null,
    @SerializedName("raveRef") val raveRef: String? = null,
    @SerializedName("chargeResponseMessage") val chargeResponseMessage: String? = null,
    @SerializedName("currency") val currency: String? = null,
    @SerializedName("IP") val ip: String? = null,
    @SerializedName("narration") val narration: String? = null,
    @SerializedName("status") val status: String? = null,
    @SerializedName("modalauditid") val modalAuditId: String? = null,
    @SerializedName("chargeRequestData") val chargeRequestData: String? = null,
    @SerializedName("chargeResponseData") val chargeResponseData: String? = null,
    @SerializedName("retry_attempt") val retryAttempt: String? = null,
    @SerializedName("getpaidBatchId") val getPaidBatchId: String? = null,
    @SerializedName("createdAt") val createdAt: String? = null,
    @SerializedName("updatedAt") val updatedAt: String? = null,
    @SerializedName("deletedAt") val deletedAt: String? = null,
    @SerializedName("customerId") val customerId: Int? = null,
    @SerializedName("AccountId") val accountId: Int? = null,
    @SerializedName("customer.id") val customerIdAlt: Int? = null,
    @SerializedName("customer.phone") val customerPhone: String? = null,
    @SerializedName("customer.fullName") val customerFullName: String? = null,
    @SerializedName("customer.customertoken") val customerToken: String? = null,
    @SerializedName("customer.email") val customerEmail: String? = null,
    @SerializedName("customer.createdAt") val customerCreatedAt: String? = null,
    @SerializedName("customer.updatedAt") val customerUpdatedAt: String? = null,
    @SerializedName("customer.deletedAt") val customerDeletedAt: String? = null,
    @SerializedName("customer.AccountId") val customerAccountId: Int? = null,
    @SerializedName("meta") val meta: List&amp;lt;Any&amp;gt;? = null,
    @SerializedName("flwMeta") val flwMeta: FlwMeta? = null
)

data class FlwMeta(
    @SerializedName("chargeResponse") val chargeResponse: String? = null,
    @SerializedName("chargeResponseMessage") val chargeResponseMessage: String? = null
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Launch the intent
&lt;/h2&gt;

&lt;p&gt;Now create a &lt;code&gt;rememberLauncherForActivityResult&lt;/code&gt; in &lt;code&gt;MainActivity&lt;/code&gt; to handle results without using the deprecated &lt;code&gt;onActivityResult()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        // Launcher to start RavePayActivity and get result
        val paymentLauncher = rememberLauncherForActivityResult(
            contract = ActivityResultContracts.StartActivityForResult()
        ) { result -&amp;gt;
            val response = result.data?.getStringExtra("response")
            val gson = Gson()
            val jsonType = object : TypeToken&amp;lt;FlutterwaveTransactionResponse&amp;gt;() {}.type
            val transactionResponse = if (!response.isNullOrEmpty()) gson.fromJson&amp;lt;FlutterwaveTransactionResponse&amp;gt;(
                response,
                jsonType
            ) else null
            when (result.resultCode) {
                RavePayActivity.RESULT_SUCCESS -&amp;gt; {
                    if (transactionResponse != null) {
                        Toast.makeText(
                            activity,
                            transactionResponse.data?.chargeResponseMessage ?: "Success",
                            Toast.LENGTH_SHORT
                        ).show()
                    } else {
                        Toast.makeText(activity, "SUCCESS", Toast.LENGTH_SHORT).show()
                    }
                }

                RavePayActivity.RESULT_ERROR -&amp;gt; {
                    if (transactionResponse != null) {
                        Toast.makeText(
                            activity,
                            transactionResponse.data?.chargeResponseMessage ?: "Error",
                            Toast.LENGTH_SHORT
                        ).show()
                    } else {
                        Toast.makeText(activity, "ERROR", Toast.LENGTH_SHORT).show()
                    }
                }

                RavePayActivity.RESULT_CANCELLED -&amp;gt; {
                    Toast.makeText(activity, "CANCELLED", Toast.LENGTH_SHORT).show()
                }
            }
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting it all together now, the &lt;code&gt;Button&lt;/code&gt; click can now launch the intent&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val intent = Intent(activity, RavePayActivity::class.java)
intent.putExtra(
    RaveConstants.RAVE_PARAMS,
    Parcels.wrap(createRavePayParams(paymentRequest))
)
paymentLauncher.launch(intent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev9fbjztx7zy0c7529nb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev9fbjztx7zy0c7529nb.png" alt="Flutterwave activity launched" width="800" height="1777"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkz4e4cf2heygrlvz43q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkz4e4cf2heygrlvz43q3.png" alt="amount confirmation" width="800" height="1777"&gt;&lt;/a&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3egsoxban3l63xymlbs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3egsoxban3l63xymlbs.png" alt="final payment stage" width="800" height="1777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Important
&lt;/h2&gt;

&lt;p&gt;Please please please do &lt;strong&gt;NOT&lt;/strong&gt; test the card payment with a &lt;strong&gt;REAL CARD&lt;/strong&gt; unless you are ready to lodge a reversal complaint at the bank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I really hope you find this useful. Do let me know in the comment section if you would like a tutorial on doing this without the default UI, or help customise the style.&lt;/p&gt;

&lt;p&gt;For more information, check out the official documentation &lt;a href="https://github.com/Flutterwave/AndroidSDK/blob/v3/README.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
The project can be viewed on GitHub &lt;a href="https://github.com/MrAmmia/flutterwave-compose-integration" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;cover photo credit: &lt;a href="https://devathon.com/top-10-best-payment-gateways-nigeria/" rel="noopener noreferrer"&gt;devathon&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutterwave</category>
      <category>tutorial</category>
      <category>androiddev</category>
      <category>jetpackcompse</category>
    </item>
  </channel>
</rss>
