Created
August 31, 2020 12:55
-
-
Save risalfajar/8609a2b027b09e628a31f42e877ea88e to your computer and use it in GitHub Desktop.
Billing Repository Full
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BillingRepository private constructor(private val application: Application) : | |
PurchasesUpdatedListener, BillingClientStateListener { | |
private lateinit var playStoreBillingClient: BillingClient | |
val coinsSkuDetailsListLiveData: MutableLiveData<List<SkuDetails>> = MutableLiveData() | |
val subsSkuDetailsListLiveData: MutableLiveData<List<SkuDetails>> = MutableLiveData() | |
companion object { | |
@Volatile | |
private var INSTANCE: BillingRepository? = null | |
fun getInstance(application: Application): BillingRepository = | |
INSTANCE ?: synchronized(this) { | |
INSTANCE ?: BillingRepository(application).also { | |
INSTANCE = it | |
} | |
} | |
} | |
fun startDataSourceConnections() { | |
Timber.d("startDataSourceConnections") | |
instantiateAndConnectToPlayBillingService() | |
} | |
fun endDataSourceConnections() { | |
playStoreBillingClient.endConnection() | |
} | |
private fun instantiateAndConnectToPlayBillingService() { | |
playStoreBillingClient = BillingClient.newBuilder(application.applicationContext) | |
.setListener(this) | |
.enablePendingPurchases() | |
.build() | |
connectToPlayBillingService() | |
} | |
private fun connectToPlayBillingService() { | |
Timber.d("connectToPlayBillingService") | |
if (!playStoreBillingClient.isReady) { | |
playStoreBillingClient.startConnection(this) | |
} | |
} | |
override fun onBillingSetupFinished(billingResult: BillingResult) { | |
when (billingResult.responseCode) { | |
BillingClient.BillingResponseCode.OK -> { | |
Timber.d("onBillingSetupFinished successfully") | |
querySkuDetailsAsync(BillingClient.SkuType.INAPP, SkuKeys.INAPP_SKUS) | |
querySkuDetailsAsync(BillingClient.SkuType.SUBS, SkuKeys.SUBS_SKUS) | |
queryPurchasesAsync() | |
} | |
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> Timber.d(billingResult.debugMessage) | |
else -> Timber.d(billingResult.debugMessage) | |
} | |
} | |
override fun onBillingServiceDisconnected() { | |
connectToPlayBillingService() | |
} | |
private fun queryPurchasesAsync() { | |
Timber.d("queryPurchasesAsync called") | |
val purchasesResult = HashSet<Purchase>() | |
var result = playStoreBillingClient.queryPurchases(BillingClient.SkuType.INAPP) | |
Timber.d("queryPurchasesAsync INAPP results: ${result?.purchasesList?.size}") | |
result?.purchasesList?.apply { purchasesResult.addAll(this) } | |
if (isSubScriptionSupported()) { | |
result = playStoreBillingClient.queryPurchases(BillingClient.SkuType.SUBS) | |
result?.purchasesList?.apply { purchasesResult.addAll(this) } | |
Timber.d("queryPurchasesAsync SUBS results: ${result?.purchasesList?.size}") | |
} | |
processPurchases(purchasesResult) | |
} | |
private fun processPurchases(purchasesResult: Set<Purchase>) { | |
Timber.d("processPurchases called") | |
val validPurchases = HashSet<Purchase>(purchasesResult.size) | |
Timber.d("processPurchases newBatch content $purchasesResult") | |
purchasesResult.forEach { purchase -> | |
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) { | |
// TODO: You should verify valid signature before marking the purchase as valid | |
validPurchases.add(purchase) | |
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) { | |
// TODO: Handle pending purchase | |
} | |
} | |
val (consumables, subscriptions) = validPurchases.partition { | |
SkuKeys.CONSUMABLE_SKUS.contains(it.sku) | |
} | |
Timber.d("processPurchases consumables content: $consumables") | |
Timber.d("processPurchases subscription content: $subscriptions") | |
handleConsumablePurchasesAsync(consumables) | |
handleSubscriptionPurchasesAsync(subscriptions) | |
} | |
private fun handleConsumablePurchasesAsync(consumables: List<Purchase>) { | |
Timber.d("handleConsumablePurchasesAsync called") | |
consumables.forEach { purchase -> | |
Timber.d("handleConsumablePurchasesAsync foreach it is $purchase") | |
val params = ConsumeParams | |
.newBuilder() | |
.setPurchaseToken(purchase.purchaseToken) | |
.build() | |
playStoreBillingClient.consumeAsync(params) { billingResult, purchaseToken -> | |
when (billingResult.responseCode) { | |
BillingClient.BillingResponseCode.OK -> { | |
purchaseToken.apply { disburseConsumableEntitlements(purchase) } | |
} | |
else -> Timber.w(billingResult.debugMessage) | |
} | |
} | |
} | |
} | |
private fun disburseConsumableEntitlements(purchase: Purchase) { | |
when (purchase.sku) { | |
SkuKeys.COINS_5K -> addCoin(5000) | |
SkuKeys.COINS_10K -> addCoin(10000) | |
SkuKeys.COINS_100K -> addCoin(100000) | |
SkuKeys.COINS_500K -> addCoin(500000) | |
SkuKeys.COINS_1M -> addCoin(1000000) | |
SkuKeys.COINS_2M -> addCoin(2000000) | |
} | |
//NOTE: you can add more logic here to handle failed entitlement | |
} | |
private fun handleSubscriptionPurchasesAsync(subscriptions: List<Purchase>) { | |
Timber.d("acknowledgeNonConsumablePurchasesAsync called") | |
if(subscriptions.isNullOrEmpty()){ | |
setUserAsVip(false) | |
}else{ | |
subscriptions.forEach { purchase -> | |
if(!purchase.isAcknowledged){ | |
acknowledgeSubscription(purchase) | |
}else{ | |
setUserAsVip(true) | |
} | |
} | |
} | |
} | |
private fun acknowledgeSubscription(purchase: Purchase){ | |
val params = AcknowledgePurchaseParams | |
.newBuilder() | |
.setPurchaseToken(purchase.purchaseToken) | |
.build() | |
playStoreBillingClient.acknowledgePurchase(params) { billingResult -> | |
when (billingResult.responseCode) { | |
BillingClient.BillingResponseCode.OK -> setUserAsVip(true) | |
else -> Timber.d("acknowledgeNonConsumablePurchasesAsync response is ${billingResult.debugMessage}") | |
} | |
} | |
} | |
private fun setUserAsVip(vip : Boolean){ | |
FirestoreRepository.setUserVip(vip) | |
} | |
private fun querySkuDetailsAsync( | |
@BillingClient.SkuType skuType: String, | |
skuList: List<String> | |
) { | |
Timber.d("querySkuDetailsAsync for $skuType") | |
val params = SkuDetailsParams | |
.newBuilder() | |
.setSkusList(skuList) | |
.setType(skuType) | |
.build() | |
playStoreBillingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList -> | |
when (billingResult.responseCode) { | |
BillingClient.BillingResponseCode.OK -> { | |
Timber.d("querySkuDetailsAsync result: $skuDetailsList") | |
if (skuDetailsList.orEmpty().isNotEmpty()) { | |
if (SkuKeys.INAPP_SKUS.contains(skuList[0])) | |
coinsSkuDetailsListLiveData.value = skuDetailsList.apply { | |
this?.sortByDescending { it.priceAmountMicros } | |
} | |
else | |
subsSkuDetailsListLiveData.value = skuDetailsList.apply { | |
this?.sortByDescending { it.priceAmountMicros } | |
} | |
} | |
} | |
} | |
} | |
} | |
private fun addCoin(amount: Long) = | |
FirestoreRepository.incrementCoins(FirebaseAuthRepository.currentUserId, amount) | |
override fun onPurchasesUpdated( | |
billingResult: BillingResult, | |
purchases: MutableList<Purchase>? | |
) { | |
when (billingResult.responseCode) { | |
BillingClient.BillingResponseCode.OK -> purchases?.apply { processPurchases(this.toSet()) } | |
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> { | |
//call queryPurchasesAsync to verify and process all such items | |
Timber.d("ITEM_ALREADY_OWNED: ${billingResult.debugMessage}") | |
queryPurchasesAsync() | |
} | |
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> connectToPlayBillingService() | |
else -> Timber.i(billingResult.debugMessage) | |
} | |
} | |
private fun isSubScriptionSupported(): Boolean { | |
val billingResult = | |
playStoreBillingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS) | |
var result = false | |
when (billingResult.responseCode) { | |
BillingClient.BillingResponseCode.OK -> result = true | |
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> connectToPlayBillingService() | |
else -> Timber.w("isSubScriptionSupported error: ${billingResult.debugMessage}") | |
} | |
return result | |
} | |
fun launchBillingFlow(activity: Activity, skuDetails: SkuDetails) { | |
val purchaseParams = BillingFlowParams | |
.newBuilder() | |
.setSkuDetails(skuDetails) | |
.build() | |
playStoreBillingClient.launchBillingFlow(activity, purchaseParams) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment