package com.example.myapp.billing
import android.app.Activity
import android.content.Context
import com.android.billingclient.api.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
class BillingManager(
private val context: Context
) : PurchasesUpdatedListener {
private var billingClient: BillingClient? = null
private val _purchaseState = MutableStateFlow<PurchaseState>(PurchaseState.Idle)
val purchaseState: StateFlow<PurchaseState> = _purchaseState
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
purchases?.forEach { purchase ->
handlePurchase(purchase)
}
}
BillingClient.BillingResponseCode.USER_CANCELED -> {
_purchaseState.value = PurchaseState.Canceled
}
else -> {
_purchaseState.value = PurchaseState.Error(billingResult.debugMessage)
}
}
}
fun initialize() {
billingClient = BillingClient.newBuilder(context)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient?.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
queryPurchases()
}
}
override fun onBillingServiceDisconnected() {
// Try to restart connection
}
})
}
suspend fun queryProducts(productIds: List<String>): List<ProductDetails> =
suspendCancellableCoroutine { continuation ->
val productList = productIds.map { productId ->
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(BillingClient.ProductType.INAPP)
.build()
}
val params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
.build()
billingClient?.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
continuation.resume(productDetailsList)
} else {
continuation.resume(emptyList())
}
}
}
fun launchPurchaseFlow(activity: Activity, productDetails: ProductDetails) {
val productDetailsParamsList = listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build()
)
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
billingClient?.launchBillingFlow(activity, billingFlowParams)
}
private fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
acknowledgePurchase(purchase)
}
_purchaseState.value = PurchaseState.Purchased(purchase)
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
_purchaseState.value = PurchaseState.Pending
}
}
private fun acknowledgePurchase(purchase: Purchase) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient?.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// Purchase acknowledged
}
}
}
private fun queryPurchases() {
billingClient?.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
) { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
purchases.forEach { purchase ->
handlePurchase(purchase)
}
}
}
}
fun endConnection() {
billingClient?.endConnection()
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
purchasesUpdatedListener.onPurchasesUpdated(billingResult, purchases)
}
}
sealed class PurchaseState {
object Idle : PurchaseState()
object Pending : PurchaseState()
object Canceled : PurchaseState()
data class Purchased(val purchase: Purchase) : PurchaseState()
data class Error(val message: String) : PurchaseState()
}
Google Play Billing enables selling digital content and subscriptions. I integrate the Play Billing Library and initialize BillingClient with PurchasesUpdatedListener. Query available products with queryProductDetailsAsync(). Launch purchase flow with launchBillingFlow(). Handle purchases in the listener—verify, acknowledge, and grant entitlements. Subscriptions require acknowledgePurchase() or consumeAsync() for consumables. Security validates purchases server-side with Google APIs. Pending purchases complete asynchronously. Query purchase history with queryPurchasesAsync(). Billing supports one-time products, subscriptions, and rewarded products. Proper implementation prevents piracy and ensures users receive purchased content reliably across devices.
Related snips
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
Dependency injection with Hilt
package com.example.myapp.ui
import androidx.lifecycle.*
import kotlinx.coroutines.launch
class UserViewModel(
LiveData transformations and mediators
package com.example.myapp.utils
import android.os.Build
import android.os.StrictMode
import android.os.Trace
import timber.log.Timber
Performance optimization and profiling
package com.example.myapp.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
App widgets for home screen
package com.example.myapp.data.repository
import com.example.myapp.data.local.PostDao
import com.example.myapp.data.local.PostEntity
import com.example.myapp.data.remote.ApiService
import com.example.myapp.models.Post
Unit testing with JUnit and MockK
package com.example.myapp.utils
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.app.PendingIntent
Notification channels and categories
Share this code
Here's the card — post it anywhere.