Update google play billing library

This commit is contained in:
Jonas Lochmann 2022-05-09 02:00:00 +02:00
parent 620af536ce
commit aa06c8aaed
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
5 changed files with 80 additions and 51 deletions

View file

@ -200,8 +200,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3' implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
googleApiImplementation "com.android.billingclient:billing:4.1.0" googleApiImplementation "com.android.billingclient:billing-ktx:5.0.0"
googleApiImplementation "com.android.billingclient:billing-ktx:4.1.0"
implementation('io.socket:socket.io-client:2.0.0') { implementation('io.socket:socket.io-client:2.0.0') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -123,12 +123,16 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
} }
} }
suspend fun querySkus(skuIds: List<String>): List<SkuDetails> = initAndUseClient { client -> suspend fun queryProducts(productIds: List<String>): List<ProductDetails> = initAndUseClient { client ->
val (billingResult, data) = client.querySkuDetails( val (billingResult, data) = client.queryProductDetails(
SkuDetailsParams.newBuilder() QueryProductDetailsParams.newBuilder()
.setSkusList(skuIds) .setProductList(productIds.map { skuId ->
.setType(BillingClient.SkuType.INAPP) QueryProductDetailsParams.Product.newBuilder()
.setProductId(skuId)
.setProductType(BillingClient.ProductType.INAPP)
.build() .build()
})
.build()
) )
billingResult.assertSuccess() billingResult.assertSuccess()
@ -137,7 +141,11 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
} }
suspend fun queryPurchases() = initAndUseClient { client -> suspend fun queryPurchases() = initAndUseClient { client ->
val response = client.queryPurchasesAsync(BillingClient.SkuType.INAPP) val response = client.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
)
response.billingResult.assertSuccess() response.billingResult.assertSuccess()
@ -153,7 +161,11 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
try { try {
initAndUseClient { client -> initAndUseClient { client ->
val result = client.queryPurchasesAsync(BillingClient.SkuType.INAPP) val result = client.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
)
result.billingResult.assertSuccess() result.billingResult.assertSuccess()
@ -223,7 +235,7 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
Log.d(LOG_TAG, "handlePurchase($purchase)") Log.d(LOG_TAG, "handlePurchase($purchase)")
} }
val sku = purchase.skus.single() val sku = purchase.products.single()
if (PurchaseIds.SAL_SKUS.contains(sku)) { if (PurchaseIds.SAL_SKUS.contains(sku)) {
// just acknowledge // just acknowledge
@ -262,14 +274,14 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
} }
} }
fun startPurchase(sku: String, checkAtBackend: Boolean, activity: Activity) { fun startPurchase(productId: String, checkAtBackend: Boolean, activity: Activity) {
runAsync { runAsync {
try { try {
val skuDetails = querySkus(listOf(sku)).single() val productDetails = queryProducts(listOf(productId)).single()
if (skuDetails.sku != sku) throw IllegalStateException() if (productDetails.productId != productId) throw IllegalStateException()
startPurchase(skuDetails, checkAtBackend, activity) startPurchase(productDetails, checkAtBackend, activity)
} catch (ex: Exception) { } catch (ex: Exception) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "could not start purchase", ex) Log.d(LOG_TAG, "could not start purchase", ex)
@ -280,7 +292,7 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
} }
} }
fun startPurchase(skuDetails: SkuDetails, checkAtBackend: Boolean, activity: Activity) { private fun startPurchase(productDetails: ProductDetails, checkAtBackend: Boolean, activity: Activity) {
runAsync { runAsync {
initAndUseClient { client -> initAndUseClient { client ->
try { try {
@ -299,10 +311,14 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
} }
client.launchBillingFlow( client.launchBillingFlow(
activity, activity,
BillingFlowParams.newBuilder() BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails) .setProductDetailsParamsList(listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build() .build()
))
.build()
).assertSuccess() ).assertSuccess()
} catch (ex: Exception) { } catch (ex: Exception) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {

View file

@ -62,11 +62,11 @@ class PurchaseModel(application: Application): AndroidViewModel(application) {
if (canDoPurchase.publicKey?.contentEquals(Base64.decode(BuildConfig.googlePlayKey, 0)) == false) { if (canDoPurchase.publicKey?.contentEquals(Base64.decode(BuildConfig.googlePlayKey, 0)) == false) {
statusInternal.value = PurchaseFragmentServerHasDifferentPublicKey statusInternal.value = PurchaseFragmentServerHasDifferentPublicKey
} else { } else {
val skus = activityPurchaseModel.querySkus(PurchaseIds.BUY_SKUS) val skus = activityPurchaseModel.queryProducts(PurchaseIds.BUY_SKUS)
statusInternal.value = PurchaseFragmentReady( statusInternal.value = PurchaseFragmentReady(
monthPrice = skus.find { it.sku == PurchaseIds.SKU_MONTH }?.price.toString(), monthPrice = skus.find { it.productId == PurchaseIds.SKU_MONTH }?.oneTimePurchaseOfferDetails?.formattedPrice.toString(),
yearPrice = skus.find { it.sku == PurchaseIds.SKU_YEAR }?.price.toString() yearPrice = skus.find { it.productId == PurchaseIds.SKU_YEAR }?.oneTimePurchaseOfferDetails?.formattedPrice.toString()
) )
} }
} else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) { } else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) {

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -37,18 +37,18 @@ class StayAwesomeModel(application: Application): AndroidViewModel(application)
} }
try { try {
val skus = activityPurchaseModel.querySkus(PurchaseIds.SAL_SKUS) val skus = activityPurchaseModel.queryProducts(PurchaseIds.SAL_SKUS)
val purchases = activityPurchaseModel.queryPurchases() val purchases = activityPurchaseModel.queryPurchases()
statusInternal.value = ReadyStayAwesomeStatus( statusInternal.value = ReadyStayAwesomeStatus(
PurchaseIds.SAL_SKUS.map { skuId -> PurchaseIds.SAL_SKUS.map { skuId ->
val sku = skus.find { it.sku == skuId } val sku = skus.find { it.productId == skuId }
StayAwesomeItem( StayAwesomeItem(
id = skuId, id = skuId,
title = sku?.description ?: skuId, title = sku?.description ?: skuId,
price = sku?.price ?: "???", price = sku?.oneTimePurchaseOfferDetails?.formattedPrice.toString(),
bought = purchases.find { purchase -> purchase.skus.find { sku -> sku == skuId } != null } != null bought = purchases.find { purchase -> purchase.products.find { sku -> sku == skuId } != null } != null
) )
} }
) )

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -28,20 +28,18 @@ object BillingClient {
fun endConnection() {} fun endConnection() {}
fun querySkuDetails(param: SkuDetailsParams) = QuerySkuDetailsResult.instance fun queryProductDetails(param: QueryProductDetailsParams) = QueryProductDetailsResult.instance
fun launchBillingFlow(activity: Activity, params: BillingFlowParams) = BillingResult fun launchBillingFlow(activity: Activity, params: BillingFlowParams) = BillingResult
fun acknowledgePurchase(params: AcknowledgePurchaseParams) = BillingResult fun acknowledgePurchase(params: AcknowledgePurchaseParams) = BillingResult
fun consumePurchase(params: ConsumeParams) = BillingResult fun consumePurchase(params: ConsumeParams) = BillingResult
suspend fun queryPurchasesAsync(type: String) = QueryPurchasesResult suspend fun queryPurchasesAsync(request: QueryPurchasesParams) = QueryPurchasesResult
object BillingResponseCode { object BillingResponseCode {
const val OK = 0 const val OK = 0
const val ERR = 1 const val ERR = 1
} }
object SkuType { enum class ProductType { INAPP }
const val INAPP = ""
}
object Builder { object Builder {
fun enablePendingPurchases() = this fun enablePendingPurchases() = this
@ -55,26 +53,20 @@ object BillingResult {
const val debugMessage = "only mock linked" const val debugMessage = "only mock linked"
} }
object SkuDetails { object ProductDetails {
const val sku = "" const val productId = ""
const val price = ""
const val description = "" const val description = ""
} val oneTimePurchaseOfferDetails: OfferDetails? = OfferDetails
object SkuDetailsParams { object OfferDetails {
fun newBuilder() = Builder const val formattedPrice = ""
object Builder {
fun setSkusList(list: List<String>) = this
fun setType(type: String) = this
fun build() = SkuDetailsParams
} }
} }
object Purchase { object Purchase {
const val purchaseState = PurchaseState.PURCHASED const val purchaseState = PurchaseState.PURCHASED
const val isAcknowledged = true const val isAcknowledged = true
val skus = listOf("") val products = emptyList<String>()
const val purchaseToken = "" const val purchaseToken = ""
const val originalJson = "" const val originalJson = ""
const val signature = "" const val signature = ""
@ -103,11 +95,14 @@ object ConsumeParams {
} }
object BillingFlowParams { object BillingFlowParams {
fun newBuilder() = Builder fun newBuilder() = this
fun setProductDetailsParamsList(details: List<ProductDetailsParams>) = this
fun build() = this
object Builder { object ProductDetailsParams {
fun setSkuDetails(details: SkuDetails) = this fun newBuilder() = this
fun build() = BillingFlowParams fun setProductDetails(details: ProductDetails) = this
fun build() = this
} }
} }
@ -116,9 +111,9 @@ object QueryPurchasesResult {
val purchasesList: List<Purchase> = emptyList() val purchasesList: List<Purchase> = emptyList()
} }
data class QuerySkuDetailsResult(val billingResult: BillingResult, val details: List<SkuDetails>?) { data class QueryProductDetailsResult(val billingResult: BillingResult, val details: List<ProductDetails>?) {
companion object { companion object {
val instance = QuerySkuDetailsResult(BillingResult, emptyList()) val instance = QueryProductDetailsResult(BillingResult, emptyList())
} }
} }
@ -129,4 +124,23 @@ interface BillingClientStateListener {
interface PurchasesUpdatedListener { interface PurchasesUpdatedListener {
fun onPurchasesUpdated(p0: BillingResult, p1: MutableList<Purchase>?) fun onPurchasesUpdated(p0: BillingResult, p1: MutableList<Purchase>?)
}
object QueryProductDetailsParams {
fun newBuilder() = this
fun setProductList(list: List<Product>) = this
fun build() = this
object Product {
fun newBuilder() = this
fun setProductId(id: String) = this
fun setProductType(type: BillingClient.ProductType) = this
fun build() = this
}
}
object QueryPurchasesParams {
fun newBuilder() = this
fun setProductType(type: BillingClient.ProductType) = this
fun build() = this
} }