ios, android: tell the buyer once a run when a swept purchase cannot be handed over (#7674)

This commit is contained in:
spaced4ndy
2026-10-09 11:59:39 +00:00
committed by GitHub
parent 57ab333a2b
commit cf3d6c45d0
2 changed files with 24 additions and 2 deletions
+11 -1
View File
@@ -111,6 +111,8 @@ final class BadgeStore: ObservableObject {
// set once presentUnfinished has read the store: until then, an Ask to Buy approved while the app was
// closed, or a purchase it died before handing over, are both unknown, so canBuy refuses to buy again
@Published private var reconciledOnce = false
// the sweep has no buyer waiting and runs at four triggers, so a failure that cannot clear is told once a run
private var sweepFailureShown = false
private var transactionUpdates: Task<Void, Never>? = nil
private init() {}
@@ -321,9 +323,17 @@ final class BadgeStore: ObservableObject {
if !badgeOneTimeProductIds.contains(receipt.productId) {
await receipt.transaction.finish()
} else if receipt.signatureVerified {
// no buyer to tell: the receipt stays unfinished and the next sweep hands it over again
// the receipt stays unfinished, so the next sweep hands it over again
do { try await handOver(receipt) } catch let error {
logger.error("BadgeStore.reconcile: \(responseError(error))")
await MainActor.run {
// showAlert presents on the top view controller, which the launch sweep can precede;
// an alert with nowhere to go must not spend the one this run gets
if !sweepFailureShown, getTopViewController() != nil {
sweepFailureShown = true
showAlert(NSLocalizedString("Purchase error", comment: "alert title"), message: redeemErrorText(error, purchase: true))
}
}
}
}
}
@@ -161,6 +161,8 @@ object BadgeStore {
// set once presentUnfinished has read the store, or failed to: until then, a slow payment completed while
// the app was closed, or a purchase it died before handing over, are both unknown, so canBuy refuses
private val reconciledOnce = mutableStateOf(false)
// the sweep has no buyer waiting and runs at four triggers, so a failure that cannot clear is told once a run
private var sweepFailureShown = false
fun purchaseState(userId: Long?): BadgePurchaseState? {
if (!badgeStoreAvailable) return null
@@ -372,12 +374,22 @@ object BadgeStore {
when (outcome) {
is BadgePurchaseOutcome.Purchased ->
if (outcome.receipt.productId !in badgeOneTimeProductIds) finish(outcome.receipt)
// no buyer to tell: the purchase stays unfinished and the next sweep hands it over again
// the purchase stays unfinished, so the next sweep hands it over again
else try {
handOver(outcome.receipt)
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e(TAG, "BadgeStore.reconcile: ${e.stackTraceToString()}")
withContext(Dispatchers.Main) {
if (!sweepFailureShown) {
sweepFailureShown = true
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.badges_purchase_error),
text = if (e is BadgeStoreError.ApiError) chatModel.controller.redeemErrorText(e.err, purchase = true)
else "${generalGetString(MR.strings.error_prefix)}: ${e.message ?: e}"
)
}
}
}
is BadgePurchaseOutcome.Pending ->
if (outcome.invoiceId != null) withContext(Dispatchers.Main) { waitingForApproval.value += outcome.invoiceId }