feat:新增BillingService2(优化API设计)
This commit is contained in:
@@ -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?) {
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user