Tip jar: finish transactions + Transaction.updates listener
The £4.99 consumable was never finished — StoreKit kept re-delivering it through Transaction.updates on every launch. Verified purchases now call transaction.finish() (direct path), and a Transaction.updates listener finishes + acknowledges purchases that complete outside the direct call (Ask to Buy approvals, other-device purchases on the same Apple ID). Untrusted (unverified) transactions are never finished or delivered; only our own product ID is handled.
This commit is contained in:
@@ -564,6 +564,33 @@ final class TipStore: ObservableObject {
|
|||||||
@Published private(set) var purchaseInProgress = false
|
@Published private(set) var purchaseInProgress = false
|
||||||
@Published private(set) var message: String?
|
@Published private(set) var message: String?
|
||||||
|
|
||||||
|
/// Listens for transactions that complete OUTSIDE the direct purchase()
|
||||||
|
/// call — Ask to Buy approvals, payments finished on another device
|
||||||
|
/// signed into the same Apple ID. Without this, those purchases land in
|
||||||
|
/// the queue but are never finished or acknowledged.
|
||||||
|
private var updatesTask: Task<Void, Never>?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
updatesTask = Task { [weak self] in
|
||||||
|
for await update in Transaction.updates {
|
||||||
|
await self?.handle(update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
updatesTask?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handle(_ update: VerificationResult<StoreKit.Transaction>) async {
|
||||||
|
// Never deliver or finish an untrusted purchase.
|
||||||
|
guard case .verified(let transaction) = update else { return }
|
||||||
|
// Only acknowledge our own consumable (future products get their own).
|
||||||
|
guard transaction.productID == Self.productID else { return }
|
||||||
|
await transaction.finish()
|
||||||
|
message = NSLocalizedString("Thank you! Your tip has been received. ⛽", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
var displayPrice: String {
|
var displayPrice: String {
|
||||||
product?.displayPrice ?? "£4.99"
|
product?.displayPrice ?? "£4.99"
|
||||||
}
|
}
|
||||||
@@ -599,7 +626,10 @@ final class TipStore: ObservableObject {
|
|||||||
switch result {
|
switch result {
|
||||||
case .success(let verification):
|
case .success(let verification):
|
||||||
switch verification {
|
switch verification {
|
||||||
case .verified:
|
case .verified(let transaction):
|
||||||
|
// Consume the consumable — otherwise StoreKit re-delivers
|
||||||
|
// it through Transaction.updates on every launch.
|
||||||
|
await transaction.finish()
|
||||||
message = NSLocalizedString("Thank you! Your tip has been received. ⛽", comment: "")
|
message = NSLocalizedString("Thank you! Your tip has been received. ⛽", comment: "")
|
||||||
case .unverified:
|
case .unverified:
|
||||||
message = NSLocalizedString("The purchase couldn't be verified. Please try again.", comment: "")
|
message = NSLocalizedString("The purchase couldn't be verified. Please try again.", comment: "")
|
||||||
|
|||||||
Reference in New Issue
Block a user