Auto commit (MindMachine) Fri Apr 10 12:01:02 PM CDT 2026
This commit is contained in:
159
app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt
Normal file
159
app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt
Normal file
@@ -0,0 +1,159 @@
|
||||
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<Boolean> = _hasNoAds.asStateFlow()
|
||||
|
||||
private val _productDetails = MutableStateFlow<Map<String, ProductDetails>>(emptyMap())
|
||||
val productDetails: StateFlow<Map<String, ProductDetails>> = _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<Purchase>) {
|
||||
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 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user