|
|
@@ -74,6 +74,9 @@ final class SubscriptionManager: ObservableObject {
|
|
|
private var inFlightProductLoad: Task<Void, Never>?
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
private let defaults: UserDefaults
|
|
|
+ /// Keeps premium UI in sync after a verified purchase until StoreKit entitlements catch up.
|
|
|
+ private var pendingVerifiedPurchase: (plan: PaywallPlan, purchasedAt: Date)?
|
|
|
+ private static let pendingPurchaseGraceInterval: TimeInterval = 24 * 60 * 60
|
|
|
|
|
|
init(defaults: UserDefaults = .standard) {
|
|
|
self.defaults = defaults
|
|
|
@@ -394,11 +397,19 @@ final class SubscriptionManager: ObservableObject {
|
|
|
var manageableSubscription = false
|
|
|
var resolvedPlan: PaywallPlan?
|
|
|
var resolvedRecurringPlan: PaywallPlan?
|
|
|
+ var revokedPendingPlan = false
|
|
|
|
|
|
for await result in Transaction.currentEntitlements {
|
|
|
guard case .verified(let transaction) = result else { continue }
|
|
|
guard SubscriptionProductID.all.contains(transaction.productID) else { continue }
|
|
|
- guard transaction.revocationDate == nil else { continue }
|
|
|
+
|
|
|
+ if transaction.revocationDate != nil {
|
|
|
+ if pendingVerifiedPurchase?.plan == SubscriptionProductID.plan(for: transaction.productID) {
|
|
|
+ revokedPendingPlan = true
|
|
|
+ pendingVerifiedPurchase = nil
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
|
|
|
guard let plan = SubscriptionProductID.plan(for: transaction.productID) else { continue }
|
|
|
guard await isRecurringSubscriptionEntitled(transaction: transaction) else {
|
|
|
@@ -407,6 +418,7 @@ final class SubscriptionManager: ObservableObject {
|
|
|
|
|
|
manageableSubscription = true
|
|
|
hasPremium = true
|
|
|
+ pendingVerifiedPurchase = nil
|
|
|
if resolvedRecurringPlan == nil {
|
|
|
resolvedRecurringPlan = plan
|
|
|
}
|
|
|
@@ -415,6 +427,16 @@ final class SubscriptionManager: ObservableObject {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if !hasPremium,
|
|
|
+ !revokedPendingPlan,
|
|
|
+ let pending = pendingVerifiedPurchase,
|
|
|
+ Date().timeIntervalSince(pending.purchasedAt) < Self.pendingPurchaseGraceInterval {
|
|
|
+ hasPremium = true
|
|
|
+ manageableSubscription = true
|
|
|
+ resolvedPlan = pending.plan
|
|
|
+ resolvedRecurringPlan = pending.plan
|
|
|
+ }
|
|
|
+
|
|
|
hasPremiumAccess = hasPremium
|
|
|
hasManageableSubscription = manageableSubscription
|
|
|
activePlan = resolvedPlan
|
|
|
@@ -427,6 +449,7 @@ final class SubscriptionManager: ObservableObject {
|
|
|
transaction.revocationDate == nil,
|
|
|
let plan = SubscriptionProductID.plan(for: transaction.productID) else { return }
|
|
|
|
|
|
+ pendingVerifiedPurchase = (plan: plan, purchasedAt: Date())
|
|
|
hasPremiumAccess = true
|
|
|
hasResolvedPremiumStatus = true
|
|
|
activePlan = plan
|
|
|
@@ -458,9 +481,20 @@ final class SubscriptionManager: ObservableObject {
|
|
|
await refreshPremiumAccess()
|
|
|
if hasPremiumAccess { return }
|
|
|
}
|
|
|
+
|
|
|
+ if let pending = pendingVerifiedPurchase,
|
|
|
+ Date().timeIntervalSince(pending.purchasedAt) < Self.pendingPurchaseGraceInterval {
|
|
|
+ hasPremiumAccess = true
|
|
|
+ hasResolvedPremiumStatus = true
|
|
|
+ hasManageableSubscription = true
|
|
|
+ activePlan = pending.plan
|
|
|
+ activeRecurringPlan = pending.plan
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private func isRecurringSubscriptionEntitled(transaction: Transaction) async -> Bool {
|
|
|
+ guard transaction.revocationDate == nil else { return false }
|
|
|
+
|
|
|
let productID = transaction.productID
|
|
|
let product: Product?
|
|
|
if let cached = productsByID[productID] {
|
|
|
@@ -514,6 +548,11 @@ final class SubscriptionManager: ObservableObject {
|
|
|
for await update in Transaction.updates {
|
|
|
switch update {
|
|
|
case .verified(let transaction):
|
|
|
+ if transaction.revocationDate != nil,
|
|
|
+ let plan = SubscriptionProductID.plan(for: transaction.productID),
|
|
|
+ pendingVerifiedPurchase?.plan == plan {
|
|
|
+ pendingVerifiedPurchase = nil
|
|
|
+ }
|
|
|
await transaction.finish()
|
|
|
await refreshPremiumAccess()
|
|
|
case .unverified(_, let error):
|