PaymentRepository
unknown
kotlin
4 years ago
6.6 kB
8
Indexable
package *.*.*.donate
import android.app.Activity
import android.content.Context
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingClient.SkuType
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.Purchase.PurchaseState
import com.android.billingclient.api.PurchasesResponseListener
import com.android.billingclient.api.SkuDetails
import com.android.billingclient.api.SkuDetailsParams
import com.android.billingclient.api.consumePurchase
import com.android.billingclient.api.querySkuDetails
import com.naveensingh.android.screenrecorder.donate.payment.PurchaseResult
import kotlinx.coroutines.channels.Channel
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
/**
* Handles core billing & licensing logic.
*
* @author Naveen Singh (@Naveen3Singh)
*/
interface PaymentRepository {
/**
* Starts a connect to Google Play [BillingClient] and returns true
* if the connection was successful.
*/
suspend fun startBillingConnection(): Boolean
/**
* Returns the list of [SkuDetails] given the [SkuDetailsParams].
*/
suspend fun queryProducts(params: SkuDetailsParams): List<SkuDetails>
/**
* Returns a list of all [Purchase]s by the current user.
*/
suspend fun queryPurchases(skuType: String = SkuType.INAPP): List<Purchase>
/**
* Starts a Google Play purchase flow given the [SkuDetails].
*/
suspend fun startPurchaseFlow(activity: Activity, skuDetails: SkuDetails): PurchaseResult
/**
* Consume a purchased item given the [purchaseToken].
*/
suspend fun consumePurchase(purchaseToken: String): String
/**
* Consumes all previous purchases. Returns true if all purchases were consumed.
* Returns false if there were no purchases to consume.
*/
suspend fun consumePreviousPurchases(purchases: List<Purchase>? = null): Boolean
}
/** @author Naveen Singh (@Naveen3Singh) */
class RealPaymentRepository(context: Context) : PaymentRepository {
private val purchaseChannel: Channel<PurchaseResult> = Channel(Channel.UNLIMITED)
private var connected = false
private val billingClient by lazy {
BillingClient.newBuilder(context)
.setListener { billingResult, purchases ->
purchaseChannel.trySend(
element = PurchaseResult(
responseCode = billingResult.responseCode, purchases = purchases.orEmpty()
)
)
}
.enablePendingPurchases()
.build()
}
override suspend fun startBillingConnection(): Boolean {
return if (connected) connected else suspendCoroutine { continuation ->
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode.isSuccess) {
connected = true
continuation.resume(true)
} else {
connected = false
continuation.resume(false)
}
}
override fun onBillingServiceDisconnected() {
connected = false
}
})
}
}
override suspend fun queryProducts(params: SkuDetailsParams): List<SkuDetails> {
val skuDetailsResult = billingClient.querySkuDetails(params)
val billingResult = skuDetailsResult.billingResult
if (billingResult.responseCode.isSuccess) {
return skuDetailsResult.skuDetailsList.orEmpty()
} else {
throw Exception("${billingResult.responseCode}, ${billingResult.debugMessage}")
}
}
override suspend fun queryPurchases(skuType: String): List<Purchase> =
suspendCoroutine { continuation ->
// setup async listener
val purchaseResponseListener = PurchasesResponseListener { billingResult, purchases ->
val responseCode = billingResult.responseCode
if (billingResult.responseCode.isSuccess) {
continuation.resume(value = purchases)
} else if (responseCode != BillingResponseCode.USER_CANCELED) {
continuation.resumeWithException(
exception = Throwable("${billingResult.debugMessage}, ${billingResult.responseCode}")
)
}
}
// fetch purchases
billingClient.queryPurchasesAsync(skuType, purchaseResponseListener)
}
override suspend fun startPurchaseFlow(
activity: Activity,
skuDetails: SkuDetails,
): PurchaseResult {
val params = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(activity, params)
return purchaseChannel.receive()
}
override suspend fun consumePurchase(purchaseToken: String): String {
val consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build()
val consumeResult = billingClient.consumePurchase(consumeParams)
val billingResult = consumeResult.billingResult
if (billingResult.responseCode.isSuccess) {
return consumeResult.purchaseToken.orEmpty()
} else {
throw Exception(
"Error consuming purchase. Response code: ${billingResult.responseCode}, ${billingResult.debugMessage}"
)
}
}
override suspend fun consumePreviousPurchases(purchases: List<Purchase>?): Boolean {
(purchases ?: queryPurchases())
.filter { !it.isAcknowledged }
.filter { it.purchaseState == PurchaseState.PURCHASED }
.apply {
val consume = isNotEmpty()
if (consume) {
forEach {
consumePurchase(it.purchaseToken)
}
}
return consume
}
}
}Editor is loading...