package com.mindmachine.mvp.billing import android.app.Activity import android.content.Context import com.android.billingclient.api.AcknowledgePurchaseParams import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.BillingResult import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.ProductDetailsResponseListener import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesResponseListener import com.android.billingclient.api.QueryProductDetailsParams import com.android.billingclient.api.QueryPurchasesParams import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class BillingManager( context: Context, private val onNoAdsEntitlementChanged: (Boolean) -> Unit, ) { private val appContext = context.applicationContext private val _hasNoAds = MutableStateFlow(false) val hasNoAds: StateFlow = _hasNoAds.asStateFlow() private val _productDetails = MutableStateFlow>(emptyMap()) val productDetails: StateFlow> = _productDetails.asStateFlow() private val billingClient: BillingClient = BillingClient.newBuilder(appContext) .setListener { _, purchases -> if (purchases != null) { handlePurchases(purchases) } } .enablePendingPurchases() .build() fun start() { if (billingClient.isReady) { refresh() return } billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(result: BillingResult) { if (result.responseCode == BillingClient.BillingResponseCode.OK) { refresh() } } override fun onBillingServiceDisconnected() { // No-op, will retry on next call. } }) } fun refresh() { queryProductDetails() queryActivePurchases() } fun launchPurchase(activity: Activity, productId: String) { val details = _productDetails.value[productId] ?: return val productDetailsParams = when (details.productType) { BillingClient.ProductType.SUBS -> { val offerToken = details.subscriptionOfferDetails ?.firstOrNull() ?.offerToken ?: return BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(details) .setOfferToken(offerToken) .build() } BillingClient.ProductType.INAPP -> { BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(details) .build() } else -> return } val params = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .build() billingClient.launchBillingFlow(activity, params) } private fun queryProductDetails() { if (!billingClient.isReady) return val products = listOf( QueryProductDetailsParams.Product.newBuilder() .setProductId(BillingProducts.SUB_REMOVE_ADS_MONTHLY) .setProductType(BillingClient.ProductType.SUBS) .build(), QueryProductDetailsParams.Product.newBuilder() .setProductId(BillingProducts.INAPP_REMOVE_ADS_FOREVER) .setProductType(BillingClient.ProductType.INAPP) .build(), ) val params = QueryProductDetailsParams.newBuilder() .setProductList(products) .build() billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList -> if (result.responseCode == BillingClient.BillingResponseCode.OK) { _productDetails.value = detailsList.associateBy { it.productId } } }) } private fun queryActivePurchases() { if (!billingClient.isReady) return val listener = PurchasesResponseListener { result, purchases -> if (result.responseCode == BillingClient.BillingResponseCode.OK) { handlePurchases(purchases) } } billingClient.queryPurchasesAsync( QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build(), listener ) billingClient.queryPurchasesAsync( QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(), listener ) } private fun handlePurchases(purchases: List) { val active = purchases.any { p -> p.purchaseState == Purchase.PurchaseState.PURCHASED && p.products.any { it == BillingProducts.SUB_REMOVE_ADS_MONTHLY || it == BillingProducts.INAPP_REMOVE_ADS_FOREVER } } if (_hasNoAds.value != active) { _hasNoAds.value = active onNoAdsEntitlementChanged(active) } // Acknowledge where needed purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED && !it.isAcknowledged } .forEach { purchase -> val params = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) .build() billingClient.acknowledgePurchase(params) { /* no-op */ } } } }