Android SDK

Add in-app purchases and paywalls to your Android app with Kotlin and Play Billing.

The CashSDK Android SDK wraps Play Billing in idiomatic Kotlin. Its suspend functions plug straight into coroutines, so fetching offerings, running purchases, and reading entitlements are all ordinary await-style calls.

Install#

Add Maven Central, then the dependency:

kotlin
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
  }
}
kotlin
android {
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
  }
}

kotlin {
  jvmToolchain(17)
}

dependencies {
  implementation("com.cashsdk:cashsdk-android:1.2.0")
  implementation("com.android.billingclient:billing-ktx:9.1.0")
}

Requires minSdk 24, compileSdk 35, Kotlin 2.3 and a Java 17 toolchain.

Prefer to vendor the source instead? Clone the repo and include it as a module:

bash
git clone https://github.com/cashsdk/cashsdk-android.git
kotlin
include(":cashsdk-android")
project(":cashsdk-android").projectDir = file("/absolute/path/to/cashsdk-android")
kotlin
dependencies {
  implementation(project(":cashsdk-android"))
}

Configure#

Initialize CashSDK once in your Application.onCreate with your publishable key. This guarantees it is ready before any activity runs.

kotlin
import android.app.Application
import com.cashsdk.CashSDK

class MyApp : Application() {
  override fun onCreate() {
    super.onCreate()
    CashSDK.configure(context = this, publishableKey = "csk_pk_…")
  }
}

Register the class in your manifest:

xml
<application
  android:name=".MyApp"
  ... >

Ship only publishable keys (csk_pk_… / csk_pk_…) in your app. Secret keys (csk_sk_…) belong on your backend.

Purchase#

purchase is a suspend function that takes an Activity, because Play Billing launches its flow from one. CashSDK drives the billing flow, verifies the purchase token server-side, settles it (consuming a consumable, acknowledging everything else), and returns the customer's updated entitlements.

kotlin
lifecycleScope.launch {
  val entitlements = CashSDK.shared.purchase(activity, "pro_monthly")

  if (entitlements.isActive("pro")) {
    unlockPro()
  }
}

Do not call acknowledgePurchase or consumePurchase yourself. The SDK decides which is correct from the verified product type. Getting it wrong means Google auto-refunds the purchase after 3 days, or a consumable SKU becomes permanently unbuyable.

Check entitlements#

entitlements is a synchronous, offline-valid snapshot; entitlementUpdates is a Flow carrying renewals, refunds and cross-device changes.

kotlin
if (CashSDK.shared.entitlements.isActive("pro")) {
  // Show premium content
} else {
  // Show the paywall
}

lifecycleScope.launch {
  CashSDK.shared.entitlementUpdates.collect { ents -> render(ents) }
}

Restore purchases#

Let customers recover access on a new device or after reinstalling.

kotlin
CashSDK.shared.restore()

if (CashSDK.shared.entitlements.isActive("pro")) {
  unlockPro()
}

Identify the customer#

Link the SDK to your own user ID so entitlements sync across devices and platforms. Call identify on every launch once the session is known, not only at sign-in. The userToken is a short-lived token minted by your backend, and is the only identity production trusts.

kotlin
CashSDK.shared.identify(userId = "8841", userToken = tokenFromYourBackend)

// On sign-out:
CashSDK.shared.logout()

Pass un-padded numeric IDs. Attribution derives from the value of a numeric ID, so "7", "07" and "007" produce the same token, so a zero-padded backend will merge two customers onto one account.

Testing, and the error you will probably hit first#

Android has no offline test mode: a purchase must come from Google Play. Use a licence tester account with the app published to a track (internal testing counts). Those purchases are real, Google-signed, and free, so they exercise the entire path including verification and webhooks.

Before that is set up, the SDK will throw CashSDKError.Billing with response code 4. It now explains itself rather than handing you the number:

That product is not available to this account. This is almost always setup, not code: the product must be ACTIVE in Play Console, the app must be published to a track (internal testing counts), the installed build must be signed with the same key as the uploaded one, and the signed-in Google account must be a licence tester. It can also take a few hours after a first upload for products to become purchasable. (Play Billing code 4)

responseCode and debug are still on the error, so when (e.responseCode) keeps working.

ProGuard / R8#

The published artifact ships with consumer ProGuard rules, so entitlement and result models survive R8 shrinking without configuration. If you run an aggressive custom setup, keep the SDK's public types:

proguard
-keep class com.cashsdk.** { *; }

Next steps#