# Kotlin and Google Play Billing

The JVM core uses the `dev.appvisionos:iap-client:0.5.0` coordinates. The optional Android AAR is `dev.appvisionos:iap-play-billing:0.5.0`, with a Google Play Billing 9.1 adapter. The Billing adapter currently requires Android API 24 or later. These are source/integration coordinates in the current preview, not a promise that a public Maven registry already serves them.

## Integrate the source packages

Use the approved repository checkout. A composite build can substitute the source modules in your application's `settings.gradle.kts`:

```kotlin
includeBuild("../cherrysub/sdk/kotlin") {
    dependencySubstitution {
        substitute(module("dev.appvisionos:iap-client")).using(project(":"))
        substitute(module("dev.appvisionos:iap-play-billing")).using(project(":play-billing"))
    }
}
```

Adjust the relative path to your checkout. Add the corresponding dependencies to your app module and configure Google/Maven repositories as required by your build. The source build uses JVM toolchain 17; follow its checked-in Gradle/Kotlin versions when validating compatibility.

## Configure the client

`sessionApi` below is your own authenticated [token broker client](/docs/authentication), not an SDK endpoint that can accept arbitrary user identities.

```kotlin
import dev.appvisionos.iap.*
import kotlin.time.Instant

val tokens = InMemoryCustomerTokenProvider(
    fetcher = CustomerTokenFetcher { appUserId ->
        val response = sessionApi.createIapToken(appUserId)
        CustomerToken(response.token, Instant.parse(response.expiresAt))
    },
)
val iap = IAPClient(
    baseUrl = "https://api-staging.cherrysub.com",
    publishableKey = "iap_pk_REPLACE_WITH_YOUR_KEY",
    customerTokenProvider = tokens,
)
```

The core owns its Ktor/OkHttp transport and can be used without BillingClient. Never distribute an app secret key in the APK.

## Present products and submit purchases

Read `iap.currentProductSet(user.id)`, select Google packages in returned order, and ask BillingClient for their product details and offers. Your app selects the purchase offer and starts the billing flow.

```kotlin
import com.android.billingclient.api.BillingClient
import dev.appvisionos.iap.playbilling.GooglePlayPurchaseCompletion
import dev.appvisionos.iap.playbilling.submitAndCompleteGooglePlayPurchase

iap.submitAndCompleteGooglePlayPurchase(
    appUserId = user.id,
    purchase = purchase,
    productType = BillingClient.ProductType.SUBS,
    completion = GooglePlayPurchaseCompletion { verifiedPurchase ->
        storefront.acknowledgeOrConsume(verifiedPurchase)
    },
)
val subscriber = iap.subscriber(user.id)
val hasPro = subscriber.accessLevels["pro"]?.active == true
```

`storefront.acknowledgeOrConsume` is your application-owned completion policy. Subscriptions and non-consumables generally require acknowledgement; consumable behavior is chosen by your app. The adapter invokes your policy only after backend acceptance.

Every state except `PurchaseState.PURCHASED` is rejected before network I/O. The adapter maps BillingClient's `subs`/`inapp` values to `subscription`/`one_time_product`. A raw purchase-token method remains available for custom integration.

## Recovery, lifecycle, and errors

Handle BillingClient updates and recovery queries, not only the initial UI callback. Leave pending purchases pending. Retry valid unfinished purchases after transient verification failure; do not acknowledge merely because the store UI returned.

The token cache is per user, refreshes early, coalesces requests, and is invalidated with `tokens.invalidate(user.id)`. A 401 triggers one forced refresh; other errors are not universally retried. On teardown, call `iap.close()` for the core client's resources.

## Test

Use the repository's Android sandbox app as a source-integrated example. Verify subscriptions and one-time products with their respective product types, a pending purchase, duplicate submission, offline recovery, refund/revocation, and a restore under a second application account. Confirm the same app/package and sandbox environment in CherrySub. [Google setup](/docs/google-play) · [Test matrix](/docs/testing).
