1 Commits

Author SHA1 Message Date
max
4588da09d9 feat:新增BillingService2(优化API设计) 2024-07-09 14:16:58 +08:00
4 changed files with 287 additions and 38 deletions

View File

@@ -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?) {
}
}

View File

@@ -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<IPurchase> = 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<Purchase>? ->
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<Purchase>?) {
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<String>,
listener: IBillingService.ProductDetailsResponseListener
) {
log("querySkuDetailsAsync() productIdList:${productIdList.size}")
val queryRequest = Runnable {
val products = ArrayList<QueryProductDetailsParams.Product>()
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)
}
}
}

View File

@@ -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<String>,
listener: OnProductDetailsResponseListener
) {
log("querySkuDetailsAsync() productIdList:${productIdList.size}")
val queryRequest = Runnable {
val products = ArrayList<QueryProductDetailsParams.Product>()
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<Purchase>?) {
log("onPurchasesUpdated code:${billingResult.responseCode} size:${purchases?.size}")
listener.onPurchasesUpdated(
BillingResultImpl(billingResult),
verifyValidSignature(purchases?.map {
PurchaseImpl(it)
} ?: emptyList())
)
}
private fun verifyValidSignature(purchases: List<PurchaseImpl>): List<PurchaseImpl> {
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)
}
}
}

View File

@@ -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<ProductDetails>) {
listener.onProductDetailsResponse(BillingResultImpl(p0), p1.map {
ProductDetailsImpl(it)
})
}
}