From 8448bc28b5b3784e5590093f52f8b1add6e2e4f5 Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Thu, 1 Oct 2026 14:12:05 +0400
Subject: [PATCH] ios, android: word purchase errors for a purchase, not a code
---
apps/ios/Shared/Model/SimpleXAPI.swift | 12 +++++++++---
apps/ios/Shared/Views/Badges/BadgeStore.swift | 2 +-
.../Shared/Views/Badges/BadgesCheckOrderView.swift | 2 +-
.../Shared/Views/Badges/BadgesRedeemCodeView.swift | 2 +-
.../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 9 ++++++---
.../common/views/badges/BadgesCheckOrderView.kt | 3 ++-
.../common/views/badges/BadgesRedeemCodeView.kt | 2 +-
.../src/commonMain/resources/MR/base/strings.xml | 2 ++
8 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 62760b043a..8d7f327c61 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -2229,13 +2229,19 @@ func apiCloseBadgeInvoice(_ userId: Int64, _ invoiceId: String) async throws {
}
// localized where the user can act on it; otherwise the error itself, so a screenshot says what happened
-func redeemErrorText(_ error: Error) -> String {
+func redeemErrorText(_ error: Error, purchase: Bool) -> String {
if case let .error(.badgeRedeemError(e)) = error as? ChatError {
switch e {
case .invalidCode: return NSLocalizedString("This code is not valid.", comment: "alert message")
case .invalidReceipt: break
- case .serviceNotConfigured: return NSLocalizedString("This app version cannot redeem badge codes.", comment: "alert message")
- case .badgeActive: return NSLocalizedString("This profile already has a badge. Redeem the code on another profile, or once this badge ends.", comment: "alert message")
+ case .serviceNotConfigured:
+ return purchase
+ ? NSLocalizedString("This app version cannot buy badges.", comment: "alert message")
+ : NSLocalizedString("This app version cannot redeem badge codes.", comment: "alert message")
+ case .badgeActive:
+ return purchase
+ ? NSLocalizedString("This profile already has a badge. The purchase will be added when this badge ends.", comment: "alert message")
+ : NSLocalizedString("This profile already has a badge. Redeem the code on another profile, or once this badge ends.", comment: "alert message")
case let .serviceError(code): if let text = badgeServiceErrorText(code) { return text }
case let .invalidResponse(message):
return String.localizedStringWithFormat(NSLocalizedString("The badge service sent an unexpected response: %@", comment: "alert message"), message)
diff --git a/apps/ios/Shared/Views/Badges/BadgeStore.swift b/apps/ios/Shared/Views/Badges/BadgeStore.swift
index ae48c9548f..b25117cdda 100644
--- a/apps/ios/Shared/Views/Badges/BadgeStore.swift
+++ b/apps/ios/Shared/Views/Badges/BadgeStore.swift
@@ -258,7 +258,7 @@ final class BadgeStore: ObservableObject {
logger.error("BadgeStore.presentPurchase: \(responseError(error))")
let refused = badgeReceiptRefused(error)
if refused { await finish(receipt) }
- let text = redeemErrorText(error)
+ let text = redeemErrorText(error, purchase: true)
alertText = refused ? text : text + "\n\n" + NSLocalizedString("The purchase will be retried, and the badge will arrive.", comment: "alert message")
}
await loadCurrentBadgeState()
diff --git a/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift b/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift
index 113665c620..a5214ffb3e 100644
--- a/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift
+++ b/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift
@@ -114,7 +114,7 @@ struct BadgesCheckOrderView: View {
alert = SomeAlert(
alert: Alert(
title: Text("Purchase error"),
- message: Text(verbatim: String(describing: error))
+ message: Text(verbatim: redeemErrorText(error, purchase: true))
),
id: "badgePurchaseError"
)
diff --git a/apps/ios/Shared/Views/Badges/BadgesRedeemCodeView.swift b/apps/ios/Shared/Views/Badges/BadgesRedeemCodeView.swift
index ed53b21173..82b225e5b2 100644
--- a/apps/ios/Shared/Views/Badges/BadgesRedeemCodeView.swift
+++ b/apps/ios/Shared/Views/Badges/BadgesRedeemCodeView.swift
@@ -59,7 +59,7 @@ func redeemBadgeCode(_ user: User, _ code: String) async -> BadgeRedeemOutcome {
}
} catch let error {
logger.error("apiRedeemBadgeCode: \(responseError(error))")
- return .refused(message: redeemErrorText(error))
+ return .refused(message: redeemErrorText(error, purchase: false))
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index 596df2ca0b..2607fdb35b 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -607,6 +607,7 @@ object ChatController {
suspend fun apiCreateBadgeInvoice(rh: Long?, userId: Long): String {
val r = sendCmd(rh, CC.ApiCreateBadgeInvoice(userId))
if (r is API.Result && r.res is CR.BadgeInvoice) return r.res.invoiceId
+ if (r is API.Error) throw BadgeStoreError.InvoiceRefused(r.err)
throw Exception("apiCreateBadgeInvoice: unexpected ${r.responseType}")
}
@@ -617,13 +618,15 @@ object ChatController {
}
// localized where the user can act on it; otherwise the error itself, so a screenshot says what happened
- fun redeemErrorText(err: ChatError?): String {
+ fun redeemErrorText(err: ChatError?, purchase: Boolean): String {
if (err is ChatError.ChatErrorChat && err.errorType is ChatErrorType.CEBadgeRedeemError) {
when (val e = err.errorType.badgeRedeemError) {
is BadgeRedeemError.InvalidCode -> return generalGetString(MR.strings.badges_error_invalid_code)
is BadgeRedeemError.InvalidReceipt -> {}
- is BadgeRedeemError.ServiceNotConfigured -> return generalGetString(MR.strings.badges_error_service_not_configured)
- is BadgeRedeemError.BadgeActive -> return generalGetString(MR.strings.badges_error_already_active)
+ is BadgeRedeemError.ServiceNotConfigured ->
+ return generalGetString(if (purchase) MR.strings.badges_error_service_not_configured_purchase else MR.strings.badges_error_service_not_configured)
+ is BadgeRedeemError.BadgeActive ->
+ return generalGetString(if (purchase) MR.strings.badges_error_already_active_purchase else MR.strings.badges_error_already_active)
is BadgeRedeemError.ServiceError -> badgeServiceErrorText(e.serviceError)?.let { return it }
is BadgeRedeemError.InvalidResponse -> return String.format(generalGetString(MR.strings.badges_error_bad_service_response), e.message)
is BadgeRedeemError.UnknownKeyIndex, is BadgeRedeemError.CredentialNotVerified -> return generalGetString(MR.strings.badges_error_credential_not_verified)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt
index d02d854200..fd60c363fe 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesCheckOrderView.kt
@@ -100,7 +100,8 @@ private fun purchase(level: BadgeLevel, period: BadgePeriod, purchasing: Mutable
purchasing.value = false
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.badges_purchase_error),
- text = e.toString()
+ text = if (e is BadgeStoreError.InvoiceRefused) chatModel.controller.redeemErrorText(e.err, purchase = true)
+ else "${generalGetString(MR.strings.error_prefix)}: ${e.message ?: e}"
)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesRedeemCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesRedeemCodeView.kt
index 1254671f59..3cc18f911d 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesRedeemCodeView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/BadgesRedeemCodeView.kt
@@ -85,7 +85,7 @@ suspend fun redeemBadgeCode(rhId: Long?, user: User, code: String): BadgeRedeemO
}
is BadgeRedeemResult.Failed -> {
Log.e(TAG, "apiRedeemBadgeCode: ${result.err?.string}")
- BadgeRedeemOutcome.Refused(chatModel.controller.redeemErrorText(result.err))
+ BadgeRedeemOutcome.Refused(chatModel.controller.redeemErrorText(result.err, purchase = false))
}
}
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index c602416945..67710ca214 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -3211,7 +3211,9 @@
Cannot redeem code
This code is not valid.
This app version cannot redeem badge codes.
+ This app version cannot buy badges.
This profile already has a badge. Redeem the code on another profile, or once this badge ends.
+ This profile already has a badge. The purchase will be added when this badge ends.
This code was not recognized.
This code has already been used.
This code has expired.