Powered by Coinflow
Payments · Documentation
Operational

Android SDK

Overview

coinflow-card-form is a Jetpack Compose SDK that embeds Coinflow’s card tokenization form directly into Android apps. The user enters their card inside your app, the SDK returns a payment token, and your backend charges the card via the standard Coinflow checkout API.

Requirements

  • Android minSdk 24+
  • Kotlin 1.9+
  • Jetpack Compose

Installation

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

// app/build.gradle.kts
dependencies {
    implementation("cash.coinflow:coinflow-card-form:0.2.0")
}

Integration

Add the card form composable

Drop CoinflowCardFormView into your Compose layout and pass a CoinflowCardFormController you’ll use to trigger tokenization.

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cash.coinflow.cardform.*
import kotlinx.coroutines.launch

@Composable
fun PaymentScreen() {
    val controller = remember { CoinflowCardFormController() }
    val scope = rememberCoroutineScope()

    Column {
        CoinflowCardFormView(
            variant = CardFormVariant.CARD_FORM,
            merchantId = "your-merchant-id",
            env = CoinflowEnv.SANDBOX,
            controller = controller,
            modifier = Modifier.fillMaxWidth().height(52.dp)
        )

        Button(onClick = {
            scope.launch {
                try {
                    val response = controller.tokenize()
                    println("Token: ${response.token}")
                } catch (e: Exception) {
                    // surface error to user
                }
            }
        }) {
            Text("Pay")
        }
    }
}

your-merchant-id is an example placeholder. Use your actual merchant ID from the merchant dashboard, or contact the Coinflow integrations team. Typically read from a BuildConfig field, not hard-coded.

Configure the environment

val env = if (BuildConfig.DEBUG) CoinflowEnv.SANDBOX else CoinflowEnv.PROD
  • CoinflowEnv.SANDBOX — test cards, no real money
  • CoinflowEnv.PROD — live cards, real money

Charge the token server-side

controller.tokenize() is a suspend function. It returns a TokenizeResponse:

  • token: String — payment token to send to your backend
  • expMonth: String?, expYear: String? — populated only for variants that collect expiry

Send the token to your server and call Coinflow’s checkout API to charge it. See the Checkout API reference for the full request shape.

Variants

Variant Captures Use case
CardFormVariant.CARD_FORM Number, expiry, CVV Standard one-shot capture
CardFormVariant.CARD_NUMBER_FORM Number + expiry First step of a two-step flow
CardFormVariant.CVV_FORM CVV only Re-collecting CVV for a card-on-file token

Theming

MerchantTheme styles the rendered form. All fields optional.

val theme = MerchantTheme(
    primary = "#165DFB",
    background = "#ffffff",
    textColor = "#05092E",
    ctaColor = "#165DFB",
    font = "Red Hat Display",
    style = MerchantStyle.ROUNDED
)

CoinflowCardFormView(theme = theme, /* ... */)

All theme fields

Field Purpose
primary, ctaColor Accent / action colors (hex strings)
background, backgroundAccent, backgroundAccent2 Form background tones
textColor, textColorAccent, textColorAction Input and label text colors
font, fontSize, fontWeight Typography. font must be available on the device.
style Input shape: ROUNDED, SHARP, PILL
cardNumberPlaceholder, cvvPlaceholder, expirationPlaceholder Override input placeholder text
showCardIcon Toggle the card brand icon (Visa/Mastercard/Amex)

Dynamic height

The hosted form reflows responsively — at narrow widths the inputs wrap to multiple rows. To keep your Compose container fitted, listen for height changes via the onHeightChange callback:

var formHeight by remember { mutableStateOf(52.dp) }

CoinflowCardFormView(
    merchantId = "your-merchant-id",
    controller = controller,
    modifier = Modifier
        .fillMaxWidth()
        .height(formHeight),
    onHeightChange = { formHeight = it.dp }
)

The callback receives the rendered content height in CSS pixels (1:1 with dp at default WebView density). Without this wiring the form may be clipped if it wraps.

Resources