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.