Requires Android API 26+ and Kotlin 1.9+. See Installation if you haven’t added the dependency yet.
Configuration
Configure the SDK early in your app’s lifecycle — typically in your Application.onCreate() or main Activity:
import com.zerosettle.sdk.ZeroSettle
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
ZeroSettle.configure(
context = this,
config = ZeroSettle.Configuration(
publishableKey = "your_live_key" // From your ZeroSettle dashboard
)
)
}
}
Configuration Options
| Parameter | Default | Description |
|---|
publishableKey | Required | Your publishable key from the dashboard |
syncPlayStoreTransactions | true | Set to false if using RevenueCat |
Sandbox vs Live Mode
Your publishable key prefix determines the mode:
- Sandbox (
zs_pk_test_): Use for development. No real charges.
- Live (
zs_pk_live_): Real payments processed.
Fetch Products
Fetch your product catalog from ZeroSettle. This also loads remote configuration (checkout type, jurisdiction settings, migration campaigns).
val catalog = ZeroSettle.fetchProducts(userId = currentUser.id)
for (product in catalog.products) {
println("${product.displayName}: ${product.webPrice.formatted}")
}
You can also use bootstrap() as a convenience — it fetches products and restores entitlements in one call:
val catalog = ZeroSettle.bootstrap(userId = currentUser.id)
fetchProducts() and bootstrap() are suspend functions — call them from a coroutine scope.
Make a Purchase
Present the payment sheet for a product. The user pays with a card via an embedded WebView or Custom Tab.
import com.zerosettle.sdk.ZeroSettle
import com.zerosettle.sdk.error.ZSError
try {
val transaction = ZeroSettle.purchase(
activity = this,
productId = product.id,
userId = currentUser.id,
)
println("Purchased: ${transaction.productId}")
// Unlock content
} catch (e: ZSError.Cancelled) {
// User cancelled — do nothing
} catch (e: ZSError.CheckoutFailed) {
println("Checkout failed: ${e.message}")
}
The checkout mode (WebView, Custom Tab, or external browser) is controlled by your remote config on the ZeroSettle dashboard.
Jetpack Compose
@Composable
fun PurchaseButton(product: ZSProduct) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
try {
val transaction = ZeroSettle.purchase(
activity = context as Activity,
productId = product.id,
userId = currentUser.id,
)
// Handle success
} catch (e: ZSError.Cancelled) {
// User cancelled
} catch (e: ZSError) {
// Handle error
}
}
}) {
Text("Buy ${product.displayName} — ${product.webPrice.formatted}")
}
}
Handle Deep Link Callback
For Custom Tab or browser-based checkout, the result returns via deep link. Handle it in your Activity:
class MainActivity : ComponentActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
ZeroSettle.handleDeepLink(uri)
}
}
}
AndroidManifest.xml
Register the deep link intent filter:
<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="api.zerosettle.io"
android:pathPrefix="/checkout/callback" />
</intent-filter>
</activity>
Deep link handling is only needed for Custom Tab and external browser checkout modes. The WebView mode (default) handles callbacks internally.
Listen for Events
Use the delegate to respond to checkout events:
class PurchaseManager : ZeroSettleDelegate {
init {
ZeroSettle.delegate = this
}
override fun zeroSettleCheckoutDidComplete(transaction: ZSTransaction) {
// Unlock content
unlockProduct(transaction.productId)
}
override fun zeroSettleCheckoutDidCancel(productId: String) {
// User cancelled checkout
}
override fun zeroSettleCheckoutDidFail(productId: String, error: Throwable) {
// Handle error
showError(error)
}
override fun zeroSettleEntitlementsDidUpdate(entitlements: List<Entitlement>) {
// Entitlements changed
updateUI(entitlements)
}
}
Kotlin Flows
You can also observe state reactively using Kotlin StateFlow:
// In a ViewModel or coroutine scope
ZeroSettle.entitlementUpdates.collect { entitlements ->
val hasPremium = entitlements.any {
it.productId == "premium_monthly" && it.isActive
}
_uiState.value = _uiState.value.copy(isPremium = hasPremium)
}
Check Entitlements
Restore and check entitlements on app launch:
val entitlements = ZeroSettle.restoreEntitlements(userId = currentUser.id)
val hasPremium = entitlements.any {
it.productId == "premium_monthly" && it.isActive
}
Complete Example
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.zerosettle.sdk.ZeroSettle
import com.zerosettle.sdk.error.ZSError
import com.zerosettle.sdk.model.ZSProduct
import kotlinx.coroutines.launch
class StoreActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ZeroSettle.configure(
context = this,
config = ZeroSettle.Configuration(
publishableKey = "your_live_key"
)
)
setContent { StoreScreen(activity = this) }
}
}
@Composable
fun StoreScreen(activity: ComponentActivity) {
var products by remember { mutableStateOf<List<ZSProduct>>(emptyList()) }
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
val catalog = ZeroSettle.bootstrap(userId = "current_user_id")
products = catalog.products
}
Scaffold(
topBar = { TopAppBar(title = { Text("Store") }) },
snackbarHost = { SnackbarHost(snackbarHostState) },
) { padding ->
LazyColumn(modifier = Modifier.padding(padding)) {
items(products) { product ->
ListItem(
headlineContent = { Text(product.displayName) },
supportingContent = { Text(product.webPrice.formatted) },
trailingContent = {
Button(onClick = {
scope.launch {
try {
val txn = ZeroSettle.purchase(
activity = activity,
productId = product.id,
userId = "current_user_id",
)
snackbarHostState.showSnackbar(
"Purchased ${txn.productId}"
)
} catch (e: ZSError.Cancelled) {
// User cancelled
} catch (e: Exception) {
snackbarHostState.showSnackbar(
"Error: ${e.message}"
)
}
}
}) {
Text("Buy")
}
},
)
}
}
}
}
Error Handling
The Android SDK uses sealed error types:
| Exception | When |
|---|
ZSError.NotConfigured | SDK not configured yet |
ZSError.Cancelled | User cancelled checkout |
ZSError.ProductNotFound | Product ID not found in catalog |
ZSError.CheckoutFailed | Payment failed |
ZSError.UserIdRequired | Method requires a userId |
ZSError.WebCheckoutDisabledForJurisdiction | Web checkout disabled for this region |
ZSError.ApiError | Network or API error |
Cancel Flow UI
Let users manage their subscriptions:
ZeroSettle.showManageSubscription(activity = this, userId = currentUser.id)
This automatically routes to the Stripe customer portal or Google Play subscription management based on the user’s entitlement sources.
Next Steps