Skip to content

Commit

Permalink
Add retries to queryPurchaseHistoryAsync (#1448)
Browse files Browse the repository at this point in the history
Counterpart of #1444
for `queryPurchaseHistoryAsync`
  • Loading branch information
vegaro authored Nov 17, 2023
1 parent 15660d7 commit 17b5983
Show file tree
Hide file tree
Showing 5 changed files with 800 additions and 289 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import com.revenuecat.purchases.common.verboseLog
import com.revenuecat.purchases.google.usecase.GetBillingConfigUseCase
import com.revenuecat.purchases.google.usecase.QueryProductDetailsUseCase
import com.revenuecat.purchases.google.usecase.QueryProductDetailsUseCaseParams
import com.revenuecat.purchases.google.usecase.QueryPurchaseHistoryUseCase
import com.revenuecat.purchases.google.usecase.QueryPurchaseHistoryUseCaseParams
import com.revenuecat.purchases.models.GooglePurchasingData
import com.revenuecat.purchases.models.GoogleReplacementMode
import com.revenuecat.purchases.models.InAppMessageType
Expand Down Expand Up @@ -290,33 +292,18 @@ internal class BillingWrapper(
onReceivePurchaseHistoryError: (PurchasesError) -> Unit,
) {
log(LogIntent.DEBUG, RestoreStrings.QUERYING_PURCHASE_HISTORY.format(productType))
executeRequestOnUIThread { connectionError ->
if (connectionError == null) {
withConnectedClient {
queryPurchaseHistoryAsyncEnsuringOneResponse(productType) {
billingResult, purchaseHistoryRecordList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
purchaseHistoryRecordList.takeUnless { it.isNullOrEmpty() }?.forEach {
log(
LogIntent.RC_PURCHASE_SUCCESS,
RestoreStrings.PURCHASE_HISTORY_RETRIEVED
.format(it.toHumanReadableDescription()),
)
} ?: log(LogIntent.DEBUG, RestoreStrings.PURCHASE_HISTORY_EMPTY)
onReceivePurchaseHistory(purchaseHistoryRecordList ?: emptyList())
} else {
onReceivePurchaseHistoryError(
billingResult.responseCode.billingResponseToPurchasesError(
"Error receiving purchase history. ${billingResult.toHumanReadableDescription()}",
).also { errorLog(it) },
)
}
}
}
} else {
onReceivePurchaseHistoryError(connectionError)
}
}
val useCase = QueryPurchaseHistoryUseCase(
QueryPurchaseHistoryUseCaseParams(
dateProvider,
diagnosticsTrackerIfEnabled,
productType,
),
onReceivePurchaseHistory,
onReceivePurchaseHistoryError,
::withConnectedClient,
::executeRequestOnUIThread,
)
useCase.run()
}

override fun queryAllPurchases(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.revenuecat.purchases.google.usecase

import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.PurchaseHistoryRecord
import com.revenuecat.purchases.ProductType
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCallback
import com.revenuecat.purchases.common.DateProvider
import com.revenuecat.purchases.common.DefaultDateProvider
import com.revenuecat.purchases.common.LogIntent
import com.revenuecat.purchases.common.between
import com.revenuecat.purchases.common.diagnostics.DiagnosticsTracker
import com.revenuecat.purchases.common.errorLog
import com.revenuecat.purchases.common.log
import com.revenuecat.purchases.common.toHumanReadableDescription
import com.revenuecat.purchases.google.buildQueryPurchaseHistoryParams
import com.revenuecat.purchases.google.toHumanReadableDescription
import com.revenuecat.purchases.strings.PurchaseStrings
import com.revenuecat.purchases.strings.RestoreStrings
import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.time.Duration

internal data class QueryPurchaseHistoryUseCaseParams(
val dateProvider: DateProvider = DefaultDateProvider(),
val diagnosticsTrackerIfEnabled: DiagnosticsTracker?,
@BillingClient.ProductType val productType: String,
)

internal class QueryPurchaseHistoryUseCase(
private val useCaseParams: QueryPurchaseHistoryUseCaseParams,
val onReceive: (List<PurchaseHistoryRecord>) -> Unit,
val onError: PurchasesErrorCallback,
val withConnectedClient: (BillingClient.() -> Unit) -> Unit,
executeRequestOnUIThread: ((PurchasesError?) -> Unit) -> Unit,
) : BillingClientUseCase<List<PurchaseHistoryRecord>?>(onError, executeRequestOnUIThread) {

override val errorMessage: String
get() = "Error receiving purchase history"

override fun executeAsync() {
withConnectedClient {
val hasResponded = AtomicBoolean(false)
val requestStartTime = useCaseParams.dateProvider.now

useCaseParams.productType.buildQueryPurchaseHistoryParams()?.let { queryPurchaseHistoryParams ->
queryPurchaseHistoryAsync(queryPurchaseHistoryParams) { billingResult, purchaseHistory ->
if (hasResponded.getAndSet(true)) {
log(
LogIntent.GOOGLE_ERROR,
RestoreStrings.EXTRA_QUERY_PURCHASE_HISTORY_RESPONSE.format(billingResult.responseCode),
)
} else {
trackGoogleQueryPurchaseHistoryRequestIfNeeded(
useCaseParams.productType,
billingResult,
requestStartTime,
)
processResult(billingResult, purchaseHistory)
}
}
} ?: run {
errorLog(PurchaseStrings.INVALID_PRODUCT_TYPE.format("getPurchaseType"))
val devErrorResponseCode = BillingResult.newBuilder()
.setResponseCode(BillingClient.BillingResponseCode.DEVELOPER_ERROR)
.build()
processResult(devErrorResponseCode, null)
}
}
}

override fun onOk(received: List<PurchaseHistoryRecord>?) {
received.takeUnless { it.isNullOrEmpty() }?.forEach {
log(
LogIntent.RC_PURCHASE_SUCCESS,
RestoreStrings.PURCHASE_HISTORY_RETRIEVED
.format(it.toHumanReadableDescription()),
)
} ?: log(LogIntent.DEBUG, RestoreStrings.PURCHASE_HISTORY_EMPTY)
onReceive(received ?: emptyList())
}

private fun trackGoogleQueryPurchaseHistoryRequestIfNeeded(
@BillingClient.ProductType productType: String,
billingResult: BillingResult,
requestStartTime: Date,
) {
useCaseParams.diagnosticsTrackerIfEnabled?.trackGoogleQueryPurchaseHistoryRequest(
productType,
billingResult.responseCode,
billingResult.debugMessage,
responseTime = Duration.between(requestStartTime, useCaseParams.dateProvider.now),
)
}
}
Loading

0 comments on commit 17b5983

Please sign in to comment.