|
|
@@ -1,618 +0,0 @@
|
|
|
-package com.adealink.weparty.wallet.pay
|
|
|
-
|
|
|
-import android.app.Activity
|
|
|
-import com.adealink.frame.base.IError
|
|
|
-import com.adealink.frame.base.Rlt
|
|
|
-import com.adealink.frame.coroutine.dispatcher.Dispatcher
|
|
|
-import com.adealink.frame.frame.BaseFrame
|
|
|
-import com.adealink.frame.log.Log
|
|
|
-import com.adealink.frame.util.AppUtil
|
|
|
-import com.adealink.weparty.module.account.AccountModule
|
|
|
-import com.adealink.weparty.module.wallet.data.PayChannel
|
|
|
-import com.adealink.weparty.module.wallet.data.PayChannelSetupError
|
|
|
-import com.adealink.weparty.module.wallet.data.PurchaseInfo
|
|
|
-import com.adealink.weparty.module.wallet.data.SkuInfo
|
|
|
-import com.adealink.weparty.module.wallet.data.SkuQueryError
|
|
|
-import com.adealink.weparty.wallet.data.TAG_GOOGLE_BILLING
|
|
|
-import com.adealink.weparty.wallet.pay.model.GooglePurchase
|
|
|
-import com.adealink.weparty.wallet.pay.model.GoogleSkuInfo
|
|
|
-import com.adealink.weparty.wallet.pay.model.PurchaseResult
|
|
|
-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.ConsumeParams
|
|
|
-import com.android.billingclient.api.PurchasesUpdatedListener
|
|
|
-import com.android.billingclient.api.QueryProductDetailsParams
|
|
|
-import com.android.billingclient.api.QueryPurchasesParams
|
|
|
-import com.android.billingclient.api.SkuDetailsParams
|
|
|
-import com.android.billingclient.api.consumePurchase
|
|
|
-import com.android.billingclient.api.queryProductDetails
|
|
|
-import com.android.billingclient.api.queryPurchasesAsync
|
|
|
-import com.android.billingclient.api.querySkuDetails
|
|
|
-import kotlinx.coroutines.channels.BufferOverflow
|
|
|
-import kotlinx.coroutines.delay
|
|
|
-import kotlinx.coroutines.flow.MutableSharedFlow
|
|
|
-import kotlinx.coroutines.flow.SharedFlow
|
|
|
-import kotlinx.coroutines.flow.first
|
|
|
-import kotlinx.coroutines.launch
|
|
|
-import kotlinx.coroutines.suspendCancellableCoroutine
|
|
|
-import kotlinx.coroutines.withContext
|
|
|
-import java.util.concurrent.atomic.AtomicBoolean
|
|
|
-import kotlin.coroutines.resume
|
|
|
-import kotlin.coroutines.resumeWithException
|
|
|
-import kotlin.math.pow
|
|
|
-
|
|
|
-
|
|
|
-class GoogleBilling : BaseFrame<IPayListener>(), IPay {
|
|
|
-
|
|
|
- /**
|
|
|
- * https://developer.android.com/reference/com/android/billingclient/api/BillingClient
|
|
|
- * 调试BillingClient对象: adb shell setprop log.tag.BillingClient VERBOSE
|
|
|
- */
|
|
|
- private var billingClient: BillingClient? = null
|
|
|
-
|
|
|
- private val _purchaseUpdates = MutableSharedFlow<PurchaseResult>(
|
|
|
- replay = 0, // 不要缓存旧值
|
|
|
- extraBufferCapacity = 1, // 防止 emit 太快被丢弃
|
|
|
- onBufferOverflow = BufferOverflow.DROP_OLDEST
|
|
|
- )
|
|
|
- private val purchaseUpdates: SharedFlow<PurchaseResult> = _purchaseUpdates
|
|
|
- private var purchasingSkuIdSet: MutableSet<String> = mutableSetOf()
|
|
|
-
|
|
|
- private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
|
|
|
- Log.i(
|
|
|
- TAG_GOOGLE_BILLING,
|
|
|
- "purchaseUpdate, resultCode: ${billingResult.responseCode}, skuIds:${purchases?.map { it.products.getOrNull(0) ?: "" }},debugMessage:${billingResult.debugMessage}"
|
|
|
- )
|
|
|
- val resCode = getPayResCodeFromBillingResponse(billingResult.responseCode)
|
|
|
- val googlePurchaseInfo =
|
|
|
- PurchaseInfo(
|
|
|
- PayChannel.Google,
|
|
|
- purchases?.map { GooglePurchase(it) } ?: arrayListOf<GooglePurchase>()
|
|
|
- )
|
|
|
- launch {
|
|
|
- if (resCode == PayRes.SUCCESS) {
|
|
|
- //先处理延迟成功交易
|
|
|
- val pendingPurchase =
|
|
|
- googlePurchaseInfo.purchases.filter { !purchasingSkuIdSet.contains(it.getSkuId()) }
|
|
|
- val pendingPurchaseInfo = PurchaseInfo(
|
|
|
- PayChannel.Google,
|
|
|
- pendingPurchase
|
|
|
- )
|
|
|
- dispatch {
|
|
|
- it.onPendingPurchaseResult(
|
|
|
- PurchaseResult(
|
|
|
- resCode,
|
|
|
- pendingPurchaseInfo
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- _purchaseUpdates.emit(
|
|
|
- PurchaseResult(
|
|
|
- resCode,
|
|
|
- googlePurchaseInfo
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- init {
|
|
|
- billingClient = BillingClient.newBuilder(AppUtil.appContext)
|
|
|
- .enablePendingPurchases()
|
|
|
- .setListener(purchasesUpdatedListener)
|
|
|
- .build()
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun ensureConnection(): BillingClient? {
|
|
|
- // 如果已经连接,直接返回
|
|
|
- billingClient?.takeIf { it.isReady }?.let { return it }
|
|
|
-
|
|
|
- // 否则建立新连接
|
|
|
- val rlt = setup()
|
|
|
- return when (rlt) {
|
|
|
- is Rlt.Failed -> {
|
|
|
- null
|
|
|
- }
|
|
|
-
|
|
|
- is Rlt.Success -> {
|
|
|
- billingClient
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun BillingClient.startConnection(): BillingResult = suspendCancellableCoroutine { continuation ->
|
|
|
- val alreadyResume = AtomicBoolean(false)
|
|
|
- startConnection(object : BillingClientStateListener {
|
|
|
- override fun onBillingSetupFinished(billingResult: BillingResult) {
|
|
|
- Log.i(
|
|
|
- TAG_GOOGLE_BILLING,
|
|
|
- "onBillingSetupFinished, resultCode: ${billingResult.responseCode}, debugMessage:${billingResult.debugMessage}"
|
|
|
- )
|
|
|
- if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
|
|
- if (alreadyResume.compareAndSet(false, true) && continuation.isActive) {
|
|
|
- Log.i(TAG_GOOGLE_BILLING, "Billing setup finished successfully.")
|
|
|
- continuation.resume(billingResult)
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (alreadyResume.compareAndSet(false, true) && continuation.isActive) {
|
|
|
- Log.d(TAG_GOOGLE_BILLING, "Billing setup finished fail.")
|
|
|
- continuation.resumeWithException(BillingConnectionException(billingResult))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * https://developer.android.com/reference/com/android/billingclient/api/BillingClientStateListener#onBillingServiceDisconnected()
|
|
|
- *
|
|
|
- * Note: This does not remove the billing service connection itself - this binding to the service will remain active,
|
|
|
- * and you will receive a call to onBillingSetupFinished when the billing service is next running and setup is complete.
|
|
|
- */
|
|
|
- override fun onBillingServiceDisconnected() {
|
|
|
- // 这里不调用 continuation,因为这不是连接完成的回调
|
|
|
- Log.i(TAG_GOOGLE_BILLING, "onBillingServiceDisconnected")
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- // 如果协程被取消,则结束连接
|
|
|
- continuation.invokeOnCancellation {
|
|
|
- endConnection()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- class BillingConnectionException(val billingResult: BillingResult) : Exception(billingResult.debugMessage)
|
|
|
-
|
|
|
- override suspend fun setup(): Rlt<Any> {
|
|
|
- return withContext(this.coroutineContext) {
|
|
|
- if (billingClient?.isReady == true) {
|
|
|
- return@withContext Rlt.Success(Any())
|
|
|
- }
|
|
|
-
|
|
|
- val maxRetries = 3
|
|
|
- var attempt = 0
|
|
|
- val retryDelay = 1000L // 1秒重试间隔
|
|
|
- var lastException: Exception? = null
|
|
|
-
|
|
|
- while (attempt < maxRetries) {
|
|
|
- try {
|
|
|
- val client = BillingClient.newBuilder(AppUtil.appContext)
|
|
|
- .enablePendingPurchases()
|
|
|
- .setListener(purchasesUpdatedListener)
|
|
|
- .build()
|
|
|
-
|
|
|
- val result = client.startConnection()
|
|
|
- if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
|
|
- billingClient = client
|
|
|
- return@withContext Rlt.Success(Any())
|
|
|
- } else {
|
|
|
- throw BillingConnectionException(result)
|
|
|
- }
|
|
|
- } catch (e: Exception) {
|
|
|
- lastException = e
|
|
|
- attempt++
|
|
|
- if (attempt < maxRetries) {
|
|
|
- delay(retryDelay)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return@withContext if (lastException is BillingConnectionException) {
|
|
|
- Rlt.Failed(
|
|
|
- PayChannelSetupError(
|
|
|
- code = getPayResCodeFromBillingResponse(lastException.billingResult.responseCode)
|
|
|
- )
|
|
|
- )
|
|
|
- } else {
|
|
|
- Rlt.Failed(
|
|
|
- PayChannelSetupError(
|
|
|
- code = -1
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override suspend fun querySkuDetails(skuIdList: List<String>): Rlt<List<SkuInfo>> {
|
|
|
- return withContext(this.coroutineContext) {
|
|
|
- if (isFeatureSupported(BillingClient.FeatureType.PRODUCT_DETAILS)) {
|
|
|
- queryProductDetailsRetry(skuIdList)
|
|
|
- } else {
|
|
|
- querySkuDetailsRetry(skuIdList)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun querySkuDetailsRetry(skuIdList: List<String>): Rlt<List<SkuInfo>> {
|
|
|
- val params = SkuDetailsParams.newBuilder()
|
|
|
- .setSkusList(skuIdList)
|
|
|
- .setType(BillingClient.SkuType.INAPP)
|
|
|
- .build()
|
|
|
-
|
|
|
- // 最大重试次数
|
|
|
- val maxRetries = 3
|
|
|
- // 初始延迟时间(毫秒)
|
|
|
- val initialDelay = 1000L
|
|
|
- // 延迟倍数,用于指数回退
|
|
|
- val delayFactor = 2.0
|
|
|
- var attempt = 0
|
|
|
-
|
|
|
- while (true) {
|
|
|
- val client = ensureConnection()
|
|
|
- if (client == null || client.isReady.not()) {
|
|
|
- return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
|
|
|
- }
|
|
|
- val skuDetailsResult = withContext(Dispatcher.WENEXT_THREAD_POOL) {
|
|
|
- client.querySkuDetails(params)
|
|
|
- }
|
|
|
- Log.i(
|
|
|
- TAG_GOOGLE_BILLING,
|
|
|
- "querySkuDetails, resultCode: ${skuDetailsResult.billingResult.responseCode}, " +
|
|
|
- "debugMessage:${skuDetailsResult.billingResult.debugMessage}, " +
|
|
|
- "skuDetailSize: ${skuDetailsResult.skuDetailsList?.size}"
|
|
|
- )
|
|
|
-
|
|
|
- when (skuDetailsResult.billingResult.responseCode) {
|
|
|
- BillingClient.BillingResponseCode.OK -> {
|
|
|
- if (skuDetailsResult.skuDetailsList.isNullOrEmpty()) {
|
|
|
- return Rlt.Failed(SkuQueryError(PayRes.PURCHASE_SKU_UN_MATCH))
|
|
|
- }
|
|
|
- return Rlt.Success(skuDetailsResult.skuDetailsList!!.map {
|
|
|
- GoogleSkuInfo(skuDetailsOld = it)
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- in setOf(
|
|
|
- BillingClient.BillingResponseCode.ERROR,
|
|
|
- BillingClient.BillingResponseCode.NETWORK_ERROR,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
|
|
|
- ) -> {
|
|
|
- attempt++
|
|
|
- if (attempt >= maxRetries) {
|
|
|
- return Rlt.Failed(
|
|
|
- SkuQueryError(
|
|
|
- getPayResCodeFromBillingResponse(
|
|
|
- skuDetailsResult.billingResult.responseCode
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
|
|
|
- }
|
|
|
-
|
|
|
- else -> {
|
|
|
- return Rlt.Failed(
|
|
|
- SkuQueryError(
|
|
|
- getPayResCodeFromBillingResponse(
|
|
|
- skuDetailsResult.billingResult.responseCode
|
|
|
- )
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun queryProductDetailsRetry(skuIdList: List<String>): Rlt<List<SkuInfo>> {
|
|
|
- val productList = skuIdList.map {
|
|
|
- QueryProductDetailsParams.Product.newBuilder()
|
|
|
- .setProductId(it)
|
|
|
- .setProductType(BillingClient.ProductType.INAPP)
|
|
|
- .build()
|
|
|
- }
|
|
|
- val params = QueryProductDetailsParams.newBuilder()
|
|
|
- .setProductList(productList)
|
|
|
- .build()
|
|
|
-
|
|
|
- // 最大重试次数
|
|
|
- val maxRetries = 3
|
|
|
- // 初始延迟时间(毫秒)
|
|
|
- val initialDelay = 1000L
|
|
|
- // 延迟倍数,用于指数回退
|
|
|
- val delayFactor = 2.0
|
|
|
- var attempt = 0
|
|
|
-
|
|
|
- while (true) {
|
|
|
- val client = ensureConnection()
|
|
|
- if (client == null || client.isReady.not()) {
|
|
|
- return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
|
|
|
- }
|
|
|
- val productDetailsResult = withContext(Dispatcher.WENEXT_THREAD_POOL) {
|
|
|
- client.queryProductDetails(params)
|
|
|
- }
|
|
|
- Log.i(
|
|
|
- TAG_GOOGLE_BILLING,
|
|
|
- "queryProductDetails, resultCode: ${productDetailsResult.billingResult.responseCode}, " +
|
|
|
- "debugMessage:${productDetailsResult.billingResult.debugMessage}, " +
|
|
|
- "skuDetailSize: ${productDetailsResult.productDetailsList?.size}"
|
|
|
- )
|
|
|
-
|
|
|
- when (productDetailsResult.billingResult.responseCode) {
|
|
|
- BillingClient.BillingResponseCode.OK -> {
|
|
|
- if (productDetailsResult.productDetailsList.isNullOrEmpty()) {
|
|
|
- return Rlt.Failed(SkuQueryError(PayRes.PURCHASE_SKU_UN_MATCH))
|
|
|
- }
|
|
|
- return Rlt.Success(productDetailsResult.productDetailsList!!.map {
|
|
|
- GoogleSkuInfo(
|
|
|
- skuDetails = it
|
|
|
- )
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- in setOf(
|
|
|
- BillingClient.BillingResponseCode.ERROR,
|
|
|
- BillingClient.BillingResponseCode.NETWORK_ERROR,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
|
|
|
- ) -> {
|
|
|
- attempt++
|
|
|
- if (attempt >= maxRetries) {
|
|
|
- return Rlt.Failed(
|
|
|
- SkuQueryError(getPayResCodeFromBillingResponse(productDetailsResult.billingResult.responseCode))
|
|
|
- )
|
|
|
- }
|
|
|
- delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
|
|
|
- }
|
|
|
-
|
|
|
- else -> {
|
|
|
- return Rlt.Failed(
|
|
|
- SkuQueryError(getPayResCodeFromBillingResponse(productDetailsResult.billingResult.responseCode))
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override suspend fun consume(token: String): Rlt<Any?> {
|
|
|
- return withContext(this.coroutineContext) {
|
|
|
- consumeRetry(token)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun consumeRetry(token: String): Rlt<Any?> {
|
|
|
- // 最大重试次数
|
|
|
- val maxRetries = 3
|
|
|
- // 初始延迟时间(毫秒)
|
|
|
- val initialDelay = 1000L
|
|
|
- // 延迟倍数,用于指数回退
|
|
|
- val delayFactor = 2.0
|
|
|
- var attempt = 0
|
|
|
-
|
|
|
- val params = ConsumeParams.newBuilder()
|
|
|
- .setPurchaseToken(token)
|
|
|
- .build()
|
|
|
- while (true) {
|
|
|
- val client = ensureConnection()
|
|
|
- if (client == null || client.isReady.not()) {
|
|
|
- return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
|
|
|
- }
|
|
|
-
|
|
|
- val consumeResult = client.consumePurchase(params)
|
|
|
-
|
|
|
- val responseCode = consumeResult.billingResult.responseCode
|
|
|
- Log.i(
|
|
|
- TAG_GOOGLE_BILLING,
|
|
|
- "consumePurchase, resultCode: $responseCode, "
|
|
|
- + "debugMessage:${consumeResult.billingResult.debugMessage}"
|
|
|
- )
|
|
|
-
|
|
|
- when (responseCode) {
|
|
|
- BillingClient.BillingResponseCode.OK -> {
|
|
|
- return Rlt.Success(null)
|
|
|
- }
|
|
|
-
|
|
|
- in setOf(
|
|
|
- BillingClient.BillingResponseCode.ERROR,
|
|
|
- BillingClient.BillingResponseCode.NETWORK_ERROR,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
|
|
|
- ) -> {
|
|
|
- attempt++
|
|
|
- if (attempt >= maxRetries) {
|
|
|
- return Rlt.Failed(
|
|
|
- IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
|
|
|
- )
|
|
|
- }
|
|
|
- delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
|
|
|
- }
|
|
|
-
|
|
|
- else -> {
|
|
|
- return Rlt.Failed(
|
|
|
- IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override suspend fun launchPaymentFlow(
|
|
|
- activity: Activity,
|
|
|
- skuInfo: SkuInfo,
|
|
|
- orderId: String
|
|
|
- ): PurchaseResult {
|
|
|
- return withContext(this.coroutineContext) {
|
|
|
- launchPaymentFlowRetry(activity, skuInfo, orderId)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun launchPaymentFlowRetry(
|
|
|
- activity: Activity,
|
|
|
- skuInfo: SkuInfo,
|
|
|
- orderId: String
|
|
|
- ): PurchaseResult {
|
|
|
- // 最大重试次数
|
|
|
- val maxRetries = 3
|
|
|
- // 初始延迟时间(毫秒)
|
|
|
- val initialDelay = 1000L
|
|
|
- // 延迟倍数,用于指数回退
|
|
|
- val delayFactor = 2.0
|
|
|
- var attempt = 0
|
|
|
-
|
|
|
- val params = BillingFlowParams.newBuilder().apply {
|
|
|
- if (isFeatureSupported(BillingClient.FeatureType.PRODUCT_DETAILS)) {
|
|
|
- val skuDetails = (skuInfo as? GoogleSkuInfo)?.skuDetails
|
|
|
- ?: return PurchaseResult(
|
|
|
- resCode = PayRes.PURCHASE_SKU_UN_MATCH,
|
|
|
- purchaseInfo = null
|
|
|
- )
|
|
|
- setProductDetailsParamsList(
|
|
|
- listOf(
|
|
|
- BillingFlowParams.ProductDetailsParams.newBuilder()
|
|
|
- .setProductDetails(skuDetails)
|
|
|
- .build()
|
|
|
- )
|
|
|
- )
|
|
|
- } else {
|
|
|
- val skuDetails = (skuInfo as? GoogleSkuInfo)?.skuDetailsOld
|
|
|
- ?: return PurchaseResult(
|
|
|
- resCode = PayRes.PURCHASE_SKU_UN_MATCH,
|
|
|
- purchaseInfo = null
|
|
|
- )
|
|
|
- setSkuDetails(skuDetails)
|
|
|
- }
|
|
|
- setObfuscatedAccountId(AccountModule.uid.toString())
|
|
|
- setObfuscatedProfileId(orderId)
|
|
|
- }.build()
|
|
|
-
|
|
|
- while (true) {
|
|
|
- Log.i(TAG_GOOGLE_BILLING, "launchBillingFlow: skuId = ${skuInfo.getSkuId()}")
|
|
|
- val client = ensureConnection()
|
|
|
- if (client == null || client.isReady.not()) {
|
|
|
- return PurchaseResult(PayRes.SET_UP_DISCONNECTED, null)
|
|
|
- }
|
|
|
- purchasingSkuIdSet.add(skuInfo.getSkuId())
|
|
|
- val launchResult = withContext(Dispatcher.UI) {
|
|
|
- client.launchBillingFlow(activity, params)
|
|
|
- }
|
|
|
-
|
|
|
- val purchaseResult = when {
|
|
|
- launchResult.responseCode != BillingClient.BillingResponseCode.OK -> {
|
|
|
- //发起支付流程不成功,可以直接重试或返回
|
|
|
- PurchaseResult(
|
|
|
- getPayResCodeFromBillingResponse(launchResult.responseCode),
|
|
|
- null
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- else -> {
|
|
|
- //等待当前支付结果
|
|
|
- purchaseUpdates
|
|
|
- .first { result ->
|
|
|
- result.purchaseInfo?.purchases?.any { it.getSkuId() == skuInfo.getSkuId() } == true
|
|
|
- || result.resCode != BillingClient.BillingResponseCode.OK
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- when (purchaseResult.resCode) {
|
|
|
- in setOf(
|
|
|
- PayRes.ERROR,
|
|
|
- PayRes.NETWORK_ERROR,
|
|
|
- PayRes.SERVICE_DISCONNECTED,
|
|
|
- PayRes.SERVICE_UNAVAILABLE
|
|
|
- ) -> {
|
|
|
- attempt++
|
|
|
- if (attempt >= maxRetries) {
|
|
|
- purchasingSkuIdSet.remove(skuInfo.getSkuId())
|
|
|
- return purchaseResult
|
|
|
- }
|
|
|
- delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
|
|
|
- }
|
|
|
-
|
|
|
- else -> {
|
|
|
- purchasingSkuIdSet.remove(skuInfo.getSkuId())
|
|
|
- return purchaseResult
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override suspend fun queryPurchases(): Rlt<PurchaseInfo> {
|
|
|
- return withContext(this.coroutineContext) {
|
|
|
- queryPurchasesRetry()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun queryPurchasesRetry(): Rlt<PurchaseInfo> {
|
|
|
- // 最大重试次数
|
|
|
- val maxRetries = 3
|
|
|
- // 初始延迟时间(毫秒)
|
|
|
- val initialDelay = 1000L
|
|
|
- // 延迟倍数,用于指数回退
|
|
|
- val delayFactor = 2.0
|
|
|
- var attempt = 0
|
|
|
-
|
|
|
- while (true) {
|
|
|
- val client = ensureConnection()
|
|
|
- if (client == null || client.isReady.not()) {
|
|
|
- return Rlt.Failed(SkuQueryError(PayRes.SET_UP_DISCONNECTED))
|
|
|
- }
|
|
|
- val result = client.queryPurchasesAsync(
|
|
|
- QueryPurchasesParams.newBuilder()
|
|
|
- .setProductType(BillingClient.ProductType.INAPP)
|
|
|
- .build()
|
|
|
- )
|
|
|
- val responseCode = result.billingResult.responseCode
|
|
|
- Log.i(TAG_GOOGLE_BILLING, "queryPurchases, resultCode: $responseCode")
|
|
|
-
|
|
|
- when (responseCode) {
|
|
|
- BillingClient.BillingResponseCode.OK -> {
|
|
|
- val googlePurchaseInfo = PurchaseInfo(
|
|
|
- PayChannel.Google,
|
|
|
- result.purchasesList.map { GooglePurchase(it) }
|
|
|
- )
|
|
|
- return Rlt.Success(googlePurchaseInfo)
|
|
|
- }
|
|
|
-
|
|
|
- in setOf(
|
|
|
- BillingClient.BillingResponseCode.ERROR,
|
|
|
- BillingClient.BillingResponseCode.NETWORK_ERROR,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
|
|
|
- BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
|
|
|
- ) -> {
|
|
|
- attempt++
|
|
|
- if (attempt >= maxRetries) {
|
|
|
- return Rlt.Failed(
|
|
|
- IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
|
|
|
- )
|
|
|
- }
|
|
|
- delay(initialDelay * delayFactor.pow(attempt.toDouble()).toLong())
|
|
|
- }
|
|
|
-
|
|
|
- else -> return Rlt.Failed(
|
|
|
- IError(serverCode = getPayResCodeFromBillingResponse(responseCode))
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun isFeatureSupported(feature: String): Boolean {
|
|
|
- return withContext(this.coroutineContext) {
|
|
|
- val client = ensureConnection()
|
|
|
- if (client == null || client.isReady.not()) {
|
|
|
- false
|
|
|
- } else {
|
|
|
- client.isFeatureSupported(feature).responseCode == BillingClient.BillingResponseCode.OK
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun getPayResCodeFromBillingResponse(responseCode: Int): Int {
|
|
|
- return when (responseCode) {
|
|
|
- BillingClient.BillingResponseCode.OK -> PayRes.SUCCESS
|
|
|
- BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED -> PayRes.FEATURE_NOT_SUPPORTED
|
|
|
- BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> PayRes.SERVICE_DISCONNECTED
|
|
|
- BillingClient.BillingResponseCode.USER_CANCELED -> PayRes.PURCHASE_USER_CANCEL
|
|
|
- BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE -> PayRes.SERVICE_UNAVAILABLE
|
|
|
- BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> PayRes.BILLING_UNAVAILABLE
|
|
|
- BillingClient.BillingResponseCode.ITEM_UNAVAILABLE -> PayRes.ITEM_UNAVAILABLE
|
|
|
- BillingClient.BillingResponseCode.DEVELOPER_ERROR -> PayRes.DEVELOPER_ERROR
|
|
|
- BillingClient.BillingResponseCode.ERROR -> PayRes.ERROR
|
|
|
- BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> PayRes.ITEM_ALREADY_OWNED
|
|
|
- BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> PayRes.ITEM_NOT_OWNED
|
|
|
- BillingClient.BillingResponseCode.NETWORK_ERROR -> PayRes.NETWORK_ERROR
|
|
|
- else -> responseCode
|
|
|
- }
|
|
|
- }
|
|
|
-}
|