Skip to main content

Getting started

Add the Android Secure Fields SDK to your project and render a working card form.

Prerequisites

  • An Android project targeting SDK 26 or higher
  • A Purse account with a tenant ID (available in your Purse dashboard)

Step 1 — Add the dependency

The SDK is published to Maven Central — no repository configuration required. Add the dependency to your app module build.gradle.kts:

dependencies {
implementation("eu.purse:securefields-android:<latest-version>")
}

mavenCentral() is included in Android projects by default. No credentials or additional repository declarations are needed.

Enable GPG signature verification

Gradle verifies checksums automatically, but GPG signature verification confirms the artifact was signed by Purse and guards against supply chain tampering. For a payment integration this is strongly recommended. Run once in your project:

./gradlew --write-verification-metadata pgp,sha256 help

Commit the generated gradle/verification-metadata.xml. Subsequent builds will reject any artifact whose signature does not match.

Step 2 — Add the Internet permission

Add the permission to your app's AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

The SDK makes two network calls: a BIN lookup during card entry and a tokenization request on submit. Both require this permission.

Step 3 — Initialize the SDK

Choose the approach that matches your UI framework:

Call rememberSecureFields() inside your composable. It creates the SDK instance and automatically calls destroy() when the composable leaves the composition.

import eu.purse.securefields.config.SecureFieldsConfig
import eu.purse.securefields.config.SecureFieldsFieldsConfig
import eu.purse.securefields.config.VaultEnvironment
import eu.purse.securefields.config.VaultFieldConfig
import eu.purse.securefields.model.Brand
import eu.purse.securefields.rememberSecureFields

@Composable
fun CheckoutScreen() {
val secureFields = rememberSecureFields(
config = SecureFieldsConfig(
brands = listOf(Brand.VISA, Brand.MASTERCARD),
fields = SecureFieldsFieldsConfig(
cardNumber = VaultFieldConfig(placeholder = "Card number"),
expDate = VaultFieldConfig(placeholder = "MM / YY"),
cvv = VaultFieldConfig(placeholder = "CVV"),
)
),
environment = VaultEnvironment.SANDBOX,
tenantId = "${TENANT_ID}",
)
// Continue below — render fields and add a submit button
}
Environment

Use VaultEnvironment.SANDBOX during development. Switch to VaultEnvironment.PRODUCTION for live traffic. Drive this from your build configuration:

environment = if (BuildConfig.DEBUG) VaultEnvironment.SANDBOX else VaultEnvironment.PRODUCTION
ValueEndpoint
VaultEnvironment.SANDBOXhttps://api.vault.purse-sandbox.com
VaultEnvironment.PRODUCTIONhttps://api.vault.purse-secure.com
Production requirement: FLAG_SECURE

Before going live, set FLAG_SECURE on your checkout Activity to prevent the card form from being captured by the Android task switcher or screen recording apps. The SDK cannot set this for you — it only warns via onSecurityWarning() if you forget. See Security and compliance — Enable FLAG_SECURE.

Step 4 — Render the card form

Use VaultForm to render all configured fields in a pre-built layout. Give it an explicit height that fits your active fields (e.g. 3 × 48dp + 2 × 12dp gaps ≈ 168dp).

import eu.purse.securefields.VaultForm

@Composable
fun CheckoutScreen() {
val secureFields = rememberSecureFields(config = ...)

LaunchedEffect(secureFields) {
secureFields.render()
}

VaultForm(
sdk = secureFields,
modifier = Modifier.fillMaxWidth()
)
}
Custom field arrangement

VaultForm renders the whole card form as a single component. If you need to lay out individual fields in your own arrangement, render each one with the VaultField composable instead — pass it a field controller from the SDK instance (secureFields.cardNumber, .expDate, .cvv, .holderName):

import eu.purse.securefields.VaultField

@Composable
fun CheckoutScreen() {
val secureFields = rememberSecureFields(config = ...)

LaunchedEffect(secureFields) {
secureFields.render()
}

Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
VaultField(secureFields.cardNumber, Modifier.fillMaxWidth().height(48.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
VaultField(secureFields.expDate, Modifier.weight(1f).height(48.dp))
VaultField(secureFields.cvv, Modifier.weight(1f).height(48.dp))
}
}
}

The controllers are populated by render(), so call it before the fields display anything. Set an explicit height on each VaultField — it does not auto-size. Don't render the same field with both VaultForm and VaultField; a native field view can only be attached to one parent.

Step 5 — Submit and handle the result

Call submit() when the user taps Pay. It validates the form and tokenizes the card data. submit() is a suspend function — call it from a coroutine.

@Composable
fun CheckoutScreen() {
val scope = rememberCoroutineScope()
val secureFields = rememberSecureFields(config = ...)
var formValid by remember { mutableStateOf(false) }

LaunchedEffect(secureFields) {
secureFields.render()
secureFields.addListener(object : SecureFieldsListener {
override fun onFormValid(payload: FormValidPayload) {
formValid = !payload.hasErrors
}
})
}

Button(
enabled = formValid,
onClick = {
scope.launch {
when (val result = secureFields.submit()) {
is SubmitResult.Success -> onTokenReceived(result.vaultFormToken)
is SubmitResult.Error -> showError(result.message)
}
}
}
) { Text("Pay") }

VaultForm(sdk = secureFields, modifier = Modifier.fillMaxWidth())
}
Send the token to your backend

vaultFormToken is an opaque server-side token. Pass it to your backend and use it to create a payment via the Payment API. Do not attempt to decode or store the raw token client-side.

Next steps