ios, android: catch a failed purchase presentation, word store purchase errors, and sweep abandoned purchases on a profile switch

This commit is contained in:
spaced4ndy
2026-10-02 18:28:12 +04:00
parent 7da11f71fd
commit e80e885aac
6 changed files with 39 additions and 4 deletions
@@ -789,6 +789,7 @@ object ChatController {
chatModel.users.clear()
chatModel.users.addAll(users)
getUserChatData(rhId, keepingChatId = keepingChatId)
withLongRunningApi { BadgeStore.presentUnfinished() }
val invitation = chatModel.callInvitations.values.firstOrNull { inv -> inv.user.userId == toUserId }
if (invitation != null && currentUser != null) {
chatModel.callManager.reportNewIncomingCall(invitation.copy(user = currentUser))
@@ -7468,6 +7469,10 @@ fun badgeServiceErrorText(code: BadgeServiceErrorCode): String? = when (code) {
is BadgeServiceErrorCode.Internal -> generalGetString(MR.strings.badges_error_service_internal)
is BadgeServiceErrorCode.ReceiptInvalid -> generalGetString(MR.strings.badges_error_receipt_invalid)
is BadgeServiceErrorCode.ReceiptUsed -> generalGetString(MR.strings.badges_error_receipt_used)
is BadgeServiceErrorCode.PaymentPending -> generalGetString(MR.strings.badges_error_payment_pending)
is BadgeServiceErrorCode.ProviderUnavailable -> generalGetString(MR.strings.badges_error_provider_unavailable)
is BadgeServiceErrorCode.ProviderNotConfigured -> generalGetString(MR.strings.badges_error_provider_not_configured)
is BadgeServiceErrorCode.ProductUnavailable -> generalGetString(MR.strings.badges_error_product_unavailable)
else -> null
}
@@ -9,6 +9,7 @@ import chat.simplex.common.platform.*
import chat.simplex.common.views.helpers.AlertManager
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.res.MR
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
@@ -299,10 +300,14 @@ object BadgeStore {
val refused = badgeReceiptRefused(r.err)
if (refused) finish(receipt)
val text = chatModel.controller.redeemErrorText(r.err, purchase = true)
alertText = if (refused) text else text + "\n\n" + generalGetString(MR.strings.badges_purchase_will_retry)
alertText = if (refused || retryCannotCredit(r.err)) text else text + "\n\n" + generalGetString(MR.strings.badges_purchase_will_retry)
}
null -> {}
}
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e(TAG, "BadgeStore.presentPurchase: ${e.stackTraceToString()}")
alertText = "${generalGetString(MR.strings.error_prefix)}: ${e.message ?: e}" + "\n\n" + generalGetString(MR.strings.badges_purchase_will_retry)
} finally {
withContext(NonCancellable) { chatModel.controller.loadBadgeState(chatModel.remoteHostId()) }
userWaiting = withContext(Dispatchers.Main + NonCancellable) { presenting.remove(receipt.token) == true }
@@ -419,3 +424,10 @@ private fun badgeReceiptRefused(err: ChatError?): Boolean {
val code = (redeemError as? BadgeRedeemError.ServiceError)?.serviceError
return code is BadgeServiceErrorCode.ReceiptInvalid || code is BadgeServiceErrorCode.ReceiptUsed
}
private fun retryCannotCredit(err: ChatError?): Boolean =
when (val e = ((err as? ChatError.ChatErrorChat)?.errorType as? ChatErrorType.CEBadgeRedeemError)?.badgeRedeemError) {
is BadgeRedeemError.BadgeActive, is BadgeRedeemError.ServiceNotConfigured -> true
is BadgeRedeemError.ServiceError -> e.serviceError is BadgeServiceErrorCode.ProviderNotConfigured || e.serviceError is BadgeServiceErrorCode.ProductUnavailable
else -> false
}
@@ -3225,6 +3225,10 @@
<string name="badges_error_service_internal">The badge service reported an internal error.</string>
<string name="badges_error_receipt_invalid">The store did not confirm this purchase.</string>
<string name="badges_error_receipt_used">This purchase was already credited to a badge on another device or profile.</string>
<string name="badges_error_payment_pending">The payment has not been confirmed yet.</string>
<string name="badges_error_provider_unavailable">The store could not be reached.</string>
<string name="badges_error_provider_not_configured">Badges cannot be bought in the app yet.</string>
<string name="badges_error_product_unavailable">This badge is not on sale.</string>
<string name="badges_error_badge_ended">The code was accepted, but the badge it grants has already ended.</string>
<string name="badges_error_unknown">The code could not be redeemed.</string>
<string name="badges_code_you_scanned_is_not_badge_code">The code you scanned is not a badge code.</string>