Skip to content

Commit

Permalink
Fetch native purchases return optional error (#75)
Browse files Browse the repository at this point in the history
* Fetch native purchases return optional error
* Improve billing connection
  • Loading branch information
ren6 authored Feb 22, 2024
1 parent 24a5380 commit 2bd4359
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 11 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

sdkVersion=2.3.7
sdkVersion=2.3.8
4 changes: 3 additions & 1 deletion sdk/src/main/java/com/apphud/sdk/Apphud.kt
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ object Apphud {
* Compared to `Apphud.restorePurchases`, this method offers a quicker way
* to determine the presence of owned purchases as it bypasses validation by Apphud.
*
* Returns `BillingClient.BillingResponseCode` as second parameter of Pair.
*
* Usage of this function for granting premium access is not advised,
* as these purchases may not yet be validated.
*
Expand All @@ -545,7 +547,7 @@ object Apphud {
* Apphud will automatically track and validate them in the background,
* so developer doesn't need to call `Apphud.restorePurchases` afterwards.
*/
suspend fun nativePurchases(): List<Purchase> = ApphudInternal.fetchNativePurchases()
suspend fun nativePurchases(): Pair<List<Purchase>, Int> = ApphudInternal.fetchNativePurchases()

//endregion
//region === Attribution ===
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ private val mutexSync = Mutex()

private var unvalidatedPurchases = listOf<Purchase>()

internal suspend fun ApphudInternal.fetchNativePurchases(): List<Purchase> {
internal suspend fun ApphudInternal.fetchNativePurchases(): Pair<List<Purchase>, Int> {
var responseCode = BillingClient.BillingResponseCode.OK
if (unvalidatedPurchases.isEmpty()) {
val purchases = billing.queryPurchasesSync()
val result = billing.queryPurchasesSync()
val purchases = result.first
responseCode = result.second
if (!purchases.isNullOrEmpty()) {
unvalidatedPurchases = purchases
syncPurchases(unvalidatedPurchs = unvalidatedPurchases)
}
}
return unvalidatedPurchases
return Pair(unvalidatedPurchases, responseCode)
}

internal fun ApphudInternal.syncPurchases(
Expand Down
17 changes: 13 additions & 4 deletions sdk/src/main/java/com/apphud/sdk/internal/BillingWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,24 @@ internal class BillingWrapper(context: Context) : Closeable {

private val mutex = Mutex()

private var connectionResponse: Int = BillingClient.BillingResponseCode.OK

private suspend fun connectIfNeeded(): Boolean {
var result: Boolean
mutex.withLock {
if (billing.isReady) {
result = true
} else {
var retries = 0
try {
while (!billing.connect()) {
val MAX_RETRIES = 5
var connected = false
while (!connected && retries < MAX_RETRIES) {
Thread.sleep(300)
retries += 1
connected = billing.connect()
}
result = true
result = connected
} catch (ex: java.lang.Exception) {
ApphudLog.log("Connect to Billing failed: ${ex.message ?: "error"}")
result = false
Expand All @@ -58,6 +65,7 @@ internal class BillingWrapper(context: Context) : Closeable {
startConnection(
object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
connectionResponse = billingResult.responseCode
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (continuation.isActive && !resumed) {
resumed = true
Expand All @@ -72,6 +80,7 @@ internal class BillingWrapper(context: Context) : Closeable {
}

override fun onBillingServiceDisconnected() {
connectionResponse = BillingClient.BillingResponseCode.SERVICE_DISCONNECTED
}
},
)
Expand All @@ -96,9 +105,9 @@ internal class BillingWrapper(context: Context) : Closeable {
consume.callBack = value
}

suspend fun queryPurchasesSync(): List<Purchase>? {
suspend fun queryPurchasesSync(): Pair<List<Purchase>?, Int> {
val connectIfNeeded = connectIfNeeded()
if (!connectIfNeeded) return null
if (!connectIfNeeded) return Pair(null, connectionResponse)
return history.queryPurchasesSync()
}

Expand Down
15 changes: 13 additions & 2 deletions sdk/src/main/java/com/apphud/sdk/internal/HistoryWrapper.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.apphud.sdk.internal

import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.QueryPurchaseHistoryParams
import com.android.billingclient.api.QueryPurchasesParams
Expand Down Expand Up @@ -39,17 +40,22 @@ internal class HistoryWrapper(
}
}

suspend fun queryPurchasesSync(): List<Purchase> = coroutineScope {
suspend fun queryPurchasesSync(): Pair<List<Purchase>, Int> = coroutineScope {
val paramsSubs = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build()

val subsDeferred = CompletableDeferred<List<Purchase>>()

var responseResult = BillingClient.BillingResponseCode.OK

billing.queryPurchasesAsync(paramsSubs) { result, purchases ->
if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
subsDeferred.complete(purchases)
} else {
if (responseResult == BillingClient.BillingResponseCode.OK) {
responseResult = result.responseCode
}
subsDeferred.complete(emptyList())
}
}
Expand All @@ -64,14 +70,19 @@ internal class HistoryWrapper(
if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
inAppsDeferred.complete(purchases)
} else {
if (responseResult == BillingClient.BillingResponseCode.OK) {
responseResult = result.responseCode
}
inAppsDeferred.complete(emptyList())
}
}

val subsPurchases = async { subsDeferred.await() }
val inAppsPurchases = async { inAppsDeferred.await() }

subsPurchases.await() + inAppsPurchases.await()
val finalPurchases = subsPurchases.await() + inAppsPurchases.await()

return@coroutineScope Pair(finalPurchases, responseResult)
}

suspend fun queryPurchaseHistorySync(
Expand Down

0 comments on commit 2bd4359

Please sign in to comment.