From 4588da09d91c4f9fa97efa0d30f3c47913fb7935 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 9 Jul 2024 14:16:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9EBillingService2=EF=BC=88?= =?UTF-8?q?=E4=BC=98=E5=8C=96API=E8=AE=BE=E8=AE=A1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module_google/GoogleServiceImpl.kt | 9 + .../module_google/billing/BillingService.kt | 57 +++-- .../module_google/billing/BillingService2.kt | 240 ++++++++++++++++++ .../ProductDetailsResponseListenerAdapter.kt | 19 -- 4 files changed, 287 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/example/module_google/billing/BillingService2.kt delete mode 100644 src/main/java/com/example/module_google/billing/ProductDetailsResponseListenerAdapter.kt diff --git a/src/main/java/com/example/module_google/GoogleServiceImpl.kt b/src/main/java/com/example/module_google/GoogleServiceImpl.kt index 0119e3e..03208ee 100644 --- a/src/main/java/com/example/module_google/GoogleServiceImpl.kt +++ b/src/main/java/com/example/module_google/GoogleServiceImpl.kt @@ -5,9 +5,11 @@ import android.content.Context import com.alibaba.android.arouter.facade.annotation.Route import com.example.module_base.config.RouterPath import com.example.module_base.support.billing.IBillingService +import com.example.module_base.support.billing.IBillingService2 import com.example.module_base.support.google.IGoogleService import com.example.module_base.support.login.ILoginService import com.example.module_google.billing.BillingService +import com.example.module_google.billing.BillingService2 import com.example.module_google.login.GoogleLoginService /** @@ -27,6 +29,13 @@ class GoogleServiceImpl : IGoogleService { return BillingService(activity, listener) } + override fun newBillingService2( + activity: Activity, + listener: IBillingService2.Listener + ): IBillingService2 { + return BillingService2(activity, listener) + } + override fun init(context: Context?) { } } \ No newline at end of file diff --git a/src/main/java/com/example/module_google/billing/BillingService.kt b/src/main/java/com/example/module_google/billing/BillingService.kt index a199bff..caf346c 100644 --- a/src/main/java/com/example/module_google/billing/BillingService.kt +++ b/src/main/java/com/example/module_google/billing/BillingService.kt @@ -18,9 +18,9 @@ import com.android.billingclient.api.QueryPurchasesParams import com.example.module_base.support.billing.IBillingService import com.example.module_base.support.billing.IProductDetails import com.example.module_base.support.billing.IPurchase -import java.io.IOException import com.example.module_google.BuildConfig +@Deprecated("后续使用BillingService2代替") class BillingService(/*活动*/ private val activity: Activity, /*监听*/ private val listener: IBillingService.Listener @@ -43,6 +43,8 @@ class BillingService(/*活动*/ /*商品列表*/ private val purchaseList: MutableList = ArrayList() + private var logEnabled = true + init { billingClient = BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build() @@ -52,18 +54,20 @@ class BillingService(/*活动*/ } } + override fun setLogEnabled(enabled: Boolean) { + logEnabled = enabled + } + override fun isServiceConnected(): Boolean { return isServiceConnected } /*开始连接Play*/ private fun startServiceConnection(executeOnSuccess: Runnable?) { + log("startServiceConnection") billingClient?.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { - Log.d( - TAG, - "Setup finished. Response code: " + billingResult.debugMessage + " code = " + billingResult.responseCode - ) + log("startServiceConnection onBillingSetupFinished code:${billingResult.responseCode}") if (billingResult.responseCode == BillingResponseCode.OK) { isServiceConnected = true executeOnSuccess?.run() @@ -73,6 +77,7 @@ class BillingService(/*活动*/ } override fun onBillingServiceDisconnected() { + log("startServiceConnection onBillingServiceDisconnected") isServiceConnected = false } }) @@ -80,12 +85,15 @@ class BillingService(/*活动*/ /*请求商品库存*/ override fun onQueryPurchases() { + log("onQueryPurchases()") val queryToExecute = Runnable { + log("onQueryPurchases() run") billingClient?.queryPurchasesAsync( QueryPurchasesParams.newBuilder() .setProductType(BillingClient.ProductType.INAPP) .build() ) { billingResult: BillingResult, purchases: List? -> + log("onQueryPurchases() result code:${billingResult.responseCode} purchases:${purchases?.size}") onPurchasesUpdated( billingResult, purchases @@ -97,7 +105,7 @@ class BillingService(/*活动*/ /*更新商品*/ override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List?) { - Log.i(TAG, "billingResult.getResponseCode()==" + billingResult.responseCode) + log("onPurchasesUpdated() code:${billingResult.responseCode} purchases:${purchases?.size}") purchaseList.clear() if (billingResult.responseCode == BillingResponseCode.OK) { if (purchases != null) { @@ -107,9 +115,6 @@ class BillingService(/*活动*/ } listener.onPurchasesUpdated(purchaseList) } else { - if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) { - } else { - } listener.onFailedHandle(billingResult.responseCode) } } @@ -127,8 +132,8 @@ class BillingService(/*活动*/ private fun verifyValidSignature(signedData: String, signature: String): Boolean { return try { Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature) - } catch (e: IOException) { - Log.e(TAG, "Got an exception trying to validate a purchase: $e") + } catch (e: Exception) { + log("verifyValidSignature() e:${e.message}") false } } @@ -147,6 +152,7 @@ class BillingService(/*活动*/ productIdList: List, listener: IBillingService.ProductDetailsResponseListener ) { + log("querySkuDetailsAsync() productIdList:${productIdList.size}") val queryRequest = Runnable { val products = ArrayList() for (productId in productIdList) { @@ -160,16 +166,22 @@ class BillingService(/*活动*/ val queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList(products) .build() + log("querySkuDetailsAsync() run") billingClient?.queryProductDetailsAsync( - queryProductDetailsParams, - ProductDetailsResponseListenerAdapter(listener) - ) + queryProductDetailsParams + ) { p0, p1 -> + log("querySkuDetailsAsync() result code:${p0.responseCode} size:${p1.size}") + listener.onProductDetailsResponse(BillingResultImpl(p0), p1.map { + ProductDetailsImpl(it) + }) + } } executeServiceRequest(queryRequest) } /*启动购买,订购流程*/ override fun initiatePurchaseFlow(productDetails: IProductDetails, recordId: String) { + log("initiatePurchaseFlow() productId:${productDetails.getProductId()}") val details = productDetails.getData() as? ProductDetails ?: return val purchaseFlowRequest = Runnable { val p = ProductDetailsParams.newBuilder() @@ -187,19 +199,20 @@ class BillingService(/*活动*/ .setObfuscatedProfileId(jsonObject.toJSONString()) .setProductDetailsParamsList(java.util.List.of(p)) .build() + log("initiatePurchaseFlow() run") val billingResult = billingClient?.launchBillingFlow(activity, purchaseParams) - Log.i( - TAG, - " initiatePurchaseFlow billingResult=" + billingResult?.responseCode + " " + billingResult?.debugMessage - ) + log("initiatePurchaseFlow() result code:${billingResult?.responseCode}") } executeServiceRequest(purchaseFlowRequest) } override fun consumeAsync(purchaseToken: String) { + log("consumeAsync()") val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build() executeServiceRequest { + log("consumeAsync() run") billingClient?.consumeAsync(consumeParams) { billingResult: BillingResult, s: String? -> + log("consumeAsync() result code:${billingResult.responseCode}") listener.onConsumeFinished( purchaseToken, billingResult.responseCode @@ -212,10 +225,16 @@ class BillingService(/*活动*/ * 销毁结算客户端并断开连接 */ override fun destroy() { - Log.d(TAG, "Destroying the manager.") + log("destroy()") if (billingClient != null && billingClient?.isReady == true) { billingClient?.endConnection() billingClient = null } } + + private fun log(message: String) { + if (logEnabled) { + Log.d(TAG, message) + } + } } \ No newline at end of file diff --git a/src/main/java/com/example/module_google/billing/BillingService2.kt b/src/main/java/com/example/module_google/billing/BillingService2.kt new file mode 100644 index 0000000..35a938f --- /dev/null +++ b/src/main/java/com/example/module_google/billing/BillingService2.kt @@ -0,0 +1,240 @@ +package com.example.module_google.billing + +import android.app.Activity +import android.util.Log +import com.alibaba.fastjson.JSONObject +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClient.BillingResponseCode +import com.android.billingclient.api.BillingClientStateListener +import com.android.billingclient.api.BillingFlowParams +import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams +import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.ConsumeParams +import com.android.billingclient.api.ProductDetails +import com.android.billingclient.api.Purchase +import com.android.billingclient.api.PurchasesUpdatedListener +import com.android.billingclient.api.QueryProductDetailsParams +import com.android.billingclient.api.QueryPurchasesParams +import com.example.module_base.support.billing.IBillingService2 +import com.example.module_base.support.billing.IProductDetails +import com.example.module_base.support.billing.OnBillingClientStateListener +import com.example.module_base.support.billing.OnConsumeResponseListener +import com.example.module_base.support.billing.OnProductDetailsResponseListener +import com.example.module_base.support.billing.OnPurchasesResponseListener +import com.example.module_google.BuildConfig + +class BillingService2(/*活动*/ + private val activity: Activity, /*监听*/ + private val listener: IBillingService2.Listener +) : IBillingService2, PurchasesUpdatedListener { + + companion object { + private const val TAG = "BillingService2" + + /*购买key*/ + private const val BASE_64_ENCODED_PUBLIC_KEY = BuildConfig.GOOGLE_BILLING_PUBLIC_KEY + } + + /*客户端*/ + private var billingClient: BillingClient? + + /*是否连接成功*/ + private var isServiceConnected = false + private set + + private var logEnabled = true + + init { + billingClient = + BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build() + } + + override fun setLogEnabled(enabled: Boolean) { + this.logEnabled = enabled + } + + override fun startConnection(listener: OnBillingClientStateListener) { + log("startConnection()") + startServiceConnection { + log("startConnection() result code:${it.responseCode}") + listener.onBillingSetupFinished(BillingResultImpl(it)) + } + } + + override fun isServiceConnected(): Boolean { + return isServiceConnected + } + + override fun queryPurchases(listener: OnPurchasesResponseListener) { + log("queryPurchases()") + val queryToExecute = Runnable { + log("queryPurchases() run") + billingClient?.queryPurchasesAsync( + QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build() + ) { p0, p1 -> + log("queryPurchases() result code:${p0.responseCode} p1:${p1.size}") + listener.onQueryPurchasesResponse( + BillingResultImpl(p0), + verifyValidSignature(p1.map { + PurchaseImpl(it) + }) + ) + } + } + executeServiceRequest(queryToExecute) + } + + override fun querySkuDetailsAsync( + productIdList: List, + listener: OnProductDetailsResponseListener + ) { + log("querySkuDetailsAsync() productIdList:${productIdList.size}") + val queryRequest = Runnable { + val products = ArrayList() + for (productId in productIdList) { + products.add( + QueryProductDetailsParams.Product.newBuilder() + .setProductId(productId) + .setProductType(BillingClient.ProductType.INAPP) + .build() + ) + } + val queryProductDetailsParams = QueryProductDetailsParams.newBuilder() + .setProductList(products) + .build() + log("querySkuDetailsAsync() run") + billingClient?.queryProductDetailsAsync( + queryProductDetailsParams + ) { p0, p1 -> + log("querySkuDetailsAsync() result code:${p0.responseCode} size:${p1.size}") + listener.onProductDetailsResponse(BillingResultImpl(p0), p1.map { + ProductDetailsImpl(it) + }) + } + } + executeServiceRequest(queryRequest) + } + + override fun consumeAsync( + purchaseToken: String, + listener: OnConsumeResponseListener + ) { + log("consumeAsync()") + val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build() + executeServiceRequest { + log("consumeAsync() run") + billingClient?.consumeAsync(consumeParams) { billingResult: BillingResult, s: String -> + log("consumeAsync() result code:${billingResult.responseCode} s:${s}") + listener.onConsumeResponse( + BillingResultImpl(billingResult), s + ) + } + } + } + + override fun launchBillingFlow(productDetails: IProductDetails, recordId: String) { + log("launchBillingFlow() productId:${productDetails.getProductId()}") + val details = productDetails.getData() as? ProductDetails ?: return + val purchaseFlowRequest = Runnable { + val p = ProductDetailsParams.newBuilder() + .setProductDetails(details) + .build() + val jsonObject = JSONObject() + val oneTimePurchaseOfferDetails = details.oneTimePurchaseOfferDetails + if (oneTimePurchaseOfferDetails != null) { + jsonObject["p"] = oneTimePurchaseOfferDetails.formattedPrice + jsonObject["a"] = oneTimePurchaseOfferDetails.priceAmountMicros / 10000 + jsonObject["c"] = oneTimePurchaseOfferDetails.priceCurrencyCode + } + val purchaseParams = BillingFlowParams.newBuilder() + .setObfuscatedAccountId(recordId) + .setObfuscatedProfileId(jsonObject.toJSONString()) + .setProductDetailsParamsList(java.util.List.of(p)) + .build() + val billingResult = billingClient?.launchBillingFlow(activity, purchaseParams) + log("launchBillingFlow() result code:${billingResult?.responseCode}") + } + executeServiceRequest(purchaseFlowRequest) + } + + /*开始连接Play*/ + private fun startServiceConnection(callback: (BillingResult) -> Unit) { + log("startServiceConnection()") + billingClient?.startConnection(object : BillingClientStateListener { + override fun onBillingSetupFinished(billingResult: BillingResult) { + log("startServiceConnection() onBillingSetupFinished code:${billingResult.responseCode}") + if (billingResult.responseCode == BillingResponseCode.OK) { + isServiceConnected = true + } else { + isServiceConnected = true + } + callback.invoke(billingResult) + } + + override fun onBillingServiceDisconnected() { + log("startServiceConnection() onBillingServiceDisconnected") + isServiceConnected = false + } + }) + } + + /*更新商品*/ + override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List?) { + log("onPurchasesUpdated code:${billingResult.responseCode} size:${purchases?.size}") + listener.onPurchasesUpdated( + BillingResultImpl(billingResult), + verifyValidSignature(purchases?.map { + PurchaseImpl(it) + } ?: emptyList()) + ) + } + + private fun verifyValidSignature(purchases: List): List { + return purchases.filter { + //验证签名数据 + verifyValidSignature(it.data.originalJson, it.data.signature) + } + } + + /*验证签名*/ + private fun verifyValidSignature(signedData: String, signature: String): Boolean { + return try { + Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature) + } catch (e: Exception) { + log("verifyValidSignature e:${e.message}") + false + } + } + + /*执行服务请求*/ + private fun executeServiceRequest(runnable: Runnable) { + if (isServiceConnected) { + runnable.run() + } else { + startServiceConnection { + if (it.responseCode == BillingResponseCode.OK) { + runnable.run() + } + } + } + } + + /** + * 销毁结算客户端并断开连接 + */ + override fun destroy() { + log("destroy()") + if (billingClient != null && billingClient?.isReady == true) { + billingClient?.endConnection() + billingClient = null + } + } + + private fun log(message: String) { + if (logEnabled) { + Log.d(TAG, message) + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/module_google/billing/ProductDetailsResponseListenerAdapter.kt b/src/main/java/com/example/module_google/billing/ProductDetailsResponseListenerAdapter.kt deleted file mode 100644 index 0ad3ea0..0000000 --- a/src/main/java/com/example/module_google/billing/ProductDetailsResponseListenerAdapter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.module_google.billing - -import com.android.billingclient.api.BillingResult -import com.android.billingclient.api.ProductDetails -import com.android.billingclient.api.ProductDetailsResponseListener -import com.example.module_base.support.billing.IBillingService - -/** - * Created by Max on 2023/11/22 20:23 - * Desc: - **/ -class ProductDetailsResponseListenerAdapter(private val listener: IBillingService.ProductDetailsResponseListener) : - ProductDetailsResponseListener { - override fun onProductDetailsResponse(p0: BillingResult, p1: MutableList) { - listener.onProductDetailsResponse(BillingResultImpl(p0), p1.map { - ProductDetailsImpl(it) - }) - } -} \ No newline at end of file