Fully check result code when processing purchase results.

This commit is contained in:
Alex Hart
2025-10-01 09:50:42 -03:00
committed by Michelle Tang
parent 76448f5426
commit 87535a917a
3 changed files with 98 additions and 93 deletions

View File

@@ -252,7 +252,10 @@ class BackupStateObserver(
if (SignalStore.backup.subscriptionStateMismatchDetected) {
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] A mismatch was detected.")
val hasActiveGooglePlayBillingSubscription = when (val purchaseResult = AppDependencies.billingApi.queryPurchases()) {
val purchaseResult = AppDependencies.billingApi.queryPurchases()
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] queryPurchase result: $purchaseResult")
val hasActiveGooglePlayBillingSubscription = when (purchaseResult) {
is BillingPurchaseResult.Success -> {
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] Found a purchase: $purchaseResult")
purchaseResult.isAcknowledged && purchaseResult.isAutoRenewing

View File

@@ -118,6 +118,12 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
val purchase: BillingPurchaseResult = AppDependencies.billingApi.queryPurchases()
Log.i(TAG, "Retrieved purchase result from Billing api: $purchase", true)
if (purchase !is BillingPurchaseResult.Success && purchase !is BillingPurchaseResult.None) {
Log.w(TAG, "Possible error when grabbing purchase from billing API. Clearing mismatch and exiting.")
SignalStore.backup.subscriptionStateMismatchDetected = false
return Result.success()
}
val hasActivePurchase = purchase is BillingPurchaseResult.Success && purchase.isAcknowledged
val product: BillingProduct? = AppDependencies.billingApi.queryProduct()

View File

@@ -74,89 +74,7 @@ internal class BillingApiImpl(
private val internalResults = MutableSharedFlow<BillingPurchaseResult>()
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
val result = when (billingResult.responseCode) {
BillingResponseCode.OK -> {
if (purchases == null) {
Log.d(TAG, "purchasesUpdatedListener: No purchases.", true)
BillingPurchaseResult.None
} else {
Log.d(TAG, "purchasesUpdatedListener: ${purchases.size} purchases.", true)
val newestPurchase = purchases.maxByOrNull { it.purchaseTime }
if (newestPurchase == null) {
Log.d(TAG, "purchasesUpdatedListener: no purchase.", true)
BillingPurchaseResult.None
} else {
Log.d(TAG, "purchasesUpdatedListener: successful purchase at ${newestPurchase.purchaseTime}", true)
BillingPurchaseResult.Success(
purchaseState = newestPurchase.purchaseState.toBillingPurchaseState(),
purchaseToken = newestPurchase.purchaseToken,
isAcknowledged = newestPurchase.isAcknowledged,
purchaseTime = newestPurchase.purchaseTime,
isAutoRenewing = newestPurchase.isAutoRenewing
)
}
}
}
BillingResponseCode.BILLING_UNAVAILABLE -> {
Log.d(TAG, "purchasesUpdatedListener: Billing unavailable.", true)
BillingPurchaseResult.BillingUnavailable
}
BillingResponseCode.USER_CANCELED -> {
Log.d(TAG, "purchasesUpdatedListener: User cancelled.", true)
BillingPurchaseResult.UserCancelled
}
BillingResponseCode.ERROR -> {
Log.d(TAG, "purchasesUpdatedListener: error.", true)
BillingPurchaseResult.GenericError
}
BillingResponseCode.NETWORK_ERROR -> {
Log.d(TAG, "purchasesUpdatedListener: Network error.", true)
BillingPurchaseResult.NetworkError
}
BillingResponseCode.DEVELOPER_ERROR -> {
Log.d(TAG, "purchasesUpdatedListener: Developer error.", true)
BillingPurchaseResult.GenericError
}
BillingResponseCode.FEATURE_NOT_SUPPORTED -> {
Log.d(TAG, "purchasesUpdatedListener: Feature not supported.", true)
BillingPurchaseResult.FeatureNotSupported
}
BillingResponseCode.ITEM_ALREADY_OWNED -> {
Log.d(TAG, "purchasesUpdatedListener: Already owned.", true)
BillingPurchaseResult.AlreadySubscribed
}
BillingResponseCode.ITEM_NOT_OWNED -> {
error("This shouldn't happen during the purchase process")
}
BillingResponseCode.ITEM_UNAVAILABLE -> {
Log.d(TAG, "purchasesUpdatedListener: Item is unavailable", true)
BillingPurchaseResult.TryAgainLater
}
BillingResponseCode.SERVICE_UNAVAILABLE -> {
Log.d(TAG, "purchasesUpdatedListener: Service is unavailable.", true)
BillingPurchaseResult.TryAgainLater
}
BillingResponseCode.SERVICE_DISCONNECTED -> {
Log.d(TAG, "purchasesUpdatedListener: Service is disconnected.", true)
BillingPurchaseResult.TryAgainLater
}
else -> {
Log.d(TAG, "purchasesUpdatedListener: No purchases.", true)
BillingPurchaseResult.None
}
}
val result = handlePurchaseResult(billingResult, purchases)
coroutineScope.launch { internalResults.emit(result) }
}
@@ -208,15 +126,7 @@ internal class BillingApiImpl(
billingClient.queryPurchasesAsync(param)
}
val purchase = result.purchasesList.maxByOrNull { it.purchaseTime } ?: return BillingPurchaseResult.None
return BillingPurchaseResult.Success(
purchaseState = purchase.purchaseState.toBillingPurchaseState(),
purchaseTime = purchase.purchaseTime,
purchaseToken = purchase.purchaseToken,
isAcknowledged = purchase.isAcknowledged,
isAutoRenewing = purchase.isAutoRenewing
)
return handlePurchaseResult(result.billingResult, result.purchasesList)
}
/**
@@ -369,6 +279,92 @@ internal class BillingApiImpl(
}
}
private fun handlePurchaseResult(billingResult: BillingResult, purchases: List<Purchase>?): BillingPurchaseResult {
return when (billingResult.responseCode) {
BillingResponseCode.OK -> {
if (purchases == null) {
Log.d(TAG, "handlePurchaseResult: No purchases.", true)
BillingPurchaseResult.None
} else {
Log.d(TAG, "handlePurchaseResult: ${purchases.size} purchases.", true)
val newestPurchase = purchases.maxByOrNull { it.purchaseTime }
if (newestPurchase == null) {
Log.d(TAG, "handlePurchaseResult: no purchase.", true)
BillingPurchaseResult.None
} else {
Log.d(TAG, "handlePurchaseResult: successful purchase at ${newestPurchase.purchaseTime}", true)
BillingPurchaseResult.Success(
purchaseState = newestPurchase.purchaseState.toBillingPurchaseState(),
purchaseToken = newestPurchase.purchaseToken,
isAcknowledged = newestPurchase.isAcknowledged,
purchaseTime = newestPurchase.purchaseTime,
isAutoRenewing = newestPurchase.isAutoRenewing
)
}
}
}
BillingResponseCode.BILLING_UNAVAILABLE -> {
Log.d(TAG, "handlePurchaseResult: Billing unavailable.", true)
BillingPurchaseResult.BillingUnavailable
}
BillingResponseCode.USER_CANCELED -> {
Log.d(TAG, "handlePurchaseResult: User cancelled.", true)
BillingPurchaseResult.UserCancelled
}
BillingResponseCode.ERROR -> {
Log.d(TAG, "handlePurchaseResult: error.", true)
BillingPurchaseResult.GenericError
}
BillingResponseCode.NETWORK_ERROR -> {
Log.d(TAG, "handlePurchaseResult: Network error.", true)
BillingPurchaseResult.NetworkError
}
BillingResponseCode.DEVELOPER_ERROR -> {
Log.d(TAG, "handlePurchaseResult: Developer error.", true)
BillingPurchaseResult.GenericError
}
BillingResponseCode.FEATURE_NOT_SUPPORTED -> {
Log.d(TAG, "handlePurchaseResult: Feature not supported.", true)
BillingPurchaseResult.FeatureNotSupported
}
BillingResponseCode.ITEM_ALREADY_OWNED -> {
Log.d(TAG, "handlePurchaseResult: Already owned.", true)
BillingPurchaseResult.AlreadySubscribed
}
BillingResponseCode.ITEM_NOT_OWNED -> {
error("This shouldn't happen during the purchase process")
}
BillingResponseCode.ITEM_UNAVAILABLE -> {
Log.d(TAG, "handlePurchaseResult: Item is unavailable", true)
BillingPurchaseResult.TryAgainLater
}
BillingResponseCode.SERVICE_UNAVAILABLE -> {
Log.d(TAG, "handlePurchaseResult: Service is unavailable.", true)
BillingPurchaseResult.TryAgainLater
}
BillingResponseCode.SERVICE_DISCONNECTED -> {
Log.d(TAG, "handlePurchaseResult: Service is disconnected.", true)
BillingPurchaseResult.TryAgainLater
}
else -> {
Log.d(TAG, "handlePurchaseResult: No purchases.", true)
BillingPurchaseResult.None
}
}
}
private class BillingListener(
private val onStateUpdate: (State) -> Unit
) : BillingClientStateListener {