Move refund detection inside ProductManager (#246)
* Detect refunds inside ProductManager Compare former value and report refund event via subject. * Hook VPN uninstallation on refund event
This commit is contained in:
parent
ba09dcffa7
commit
7ed27558fc
|
@ -71,6 +71,15 @@ class AppContext {
|
||||||
self.reviewer.reportEvent()
|
self.reviewer.reportEvent()
|
||||||
}
|
}
|
||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
|
productManager.didRefundProducts
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink {
|
||||||
|
Task {
|
||||||
|
pp_log.info("Refunds detected, uninstalling VPN profile")
|
||||||
|
await coreContext.vpnManager.uninstall()
|
||||||
|
}
|
||||||
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eligibility: ignore network settings if ineligible
|
// eligibility: ignore network settings if ineligible
|
||||||
|
|
|
@ -27,6 +27,7 @@ import Foundation
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
import StoreKit
|
import StoreKit
|
||||||
import Kvitto
|
import Kvitto
|
||||||
|
import Combine
|
||||||
|
|
||||||
enum ProductError: Error {
|
enum ProductError: Error {
|
||||||
case uneligible
|
case uneligible
|
||||||
|
@ -47,6 +48,8 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
|
|
||||||
let buildProducts: BuildProducts
|
let buildProducts: BuildProducts
|
||||||
|
|
||||||
|
let didRefundProducts = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
@Published private(set) var isRefreshingProducts = false
|
@Published private(set) var isRefreshingProducts = false
|
||||||
|
|
||||||
@Published private(set) var products: [SKProduct]
|
@Published private(set) var products: [SKProduct]
|
||||||
|
@ -61,9 +64,18 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
|
|
||||||
private var purchaseDates: [LocalProduct: Date]
|
private var purchaseDates: [LocalProduct: Date]
|
||||||
|
|
||||||
private var cancelledPurchases: Set<LocalProduct>
|
private var cancelledPurchases: Set<LocalProduct>? {
|
||||||
|
willSet {
|
||||||
private var cancelledPurchasesSnapshot: Set<LocalProduct>
|
guard cancelledPurchases != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let newCancelledPurchases = newValue, newCancelledPurchases != cancelledPurchases else {
|
||||||
|
pp_log.debug("No purchase was refunded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
detectRefunds(newCancelledPurchases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var refreshRequest: SKReceiptRefreshRequest?
|
private var refreshRequest: SKReceiptRefreshRequest?
|
||||||
|
|
||||||
|
@ -76,8 +88,7 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
purchasedAppBuild = nil
|
purchasedAppBuild = nil
|
||||||
purchasedFeatures = []
|
purchasedFeatures = []
|
||||||
purchaseDates = [:]
|
purchaseDates = [:]
|
||||||
cancelledPurchases = []
|
cancelledPurchases = nil
|
||||||
cancelledPurchasesSnapshot = []
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -216,10 +227,6 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
purchasedFeatures.contains(product)
|
purchasedFeatures.contains(product)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCancelledPurchase(_ product: LocalProduct) -> Bool {
|
|
||||||
cancelledPurchases.contains(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
func purchaseDate(forProduct product: LocalProduct) -> Date? {
|
func purchaseDate(forProduct product: LocalProduct) -> Date? {
|
||||||
purchaseDates[product]
|
purchaseDates[product]
|
||||||
}
|
}
|
||||||
|
@ -238,7 +245,7 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
purchasedAppBuild = buildNumber
|
purchasedAppBuild = buildNumber
|
||||||
}
|
}
|
||||||
purchasedFeatures.removeAll()
|
purchasedFeatures.removeAll()
|
||||||
cancelledPurchases.removeAll()
|
var newCancelledPurchases: Set<LocalProduct> = []
|
||||||
|
|
||||||
if let buildNumber = purchasedAppBuild {
|
if let buildNumber = purchasedAppBuild {
|
||||||
pp_log.debug("Original purchased build: \(buildNumber)")
|
pp_log.debug("Original purchased build: \(buildNumber)")
|
||||||
|
@ -258,7 +265,7 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
if let cancellationDate = $0.cancellationDate {
|
if let cancellationDate = $0.cancellationDate {
|
||||||
pp_log.debug("\t\(pid) [cancelled on: \(cancellationDate)]")
|
pp_log.debug("\t\(pid) [cancelled on: \(cancellationDate)]")
|
||||||
cancelledPurchases.insert(product)
|
newCancelledPurchases.insert(product)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let purchaseDate = $0.originalPurchaseDate {
|
if let purchaseDate = $0.originalPurchaseDate {
|
||||||
|
@ -272,6 +279,7 @@ class ProductManager: NSObject, ObservableObject {
|
||||||
if andNotify {
|
if andNotify {
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
cancelledPurchases = newCancelledPurchases
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,31 +292,27 @@ extension ProductManager: SKPaymentTransactionObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProductManager {
|
extension ProductManager {
|
||||||
func snapshotRefunds() {
|
private func detectRefunds(_ refunds: Set<LocalProduct>) {
|
||||||
cancelledPurchasesSnapshot = cancelledPurchases
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasNewRefunds() -> Bool {
|
|
||||||
reloadReceipt(andNotify: false)
|
|
||||||
guard cancelledPurchases != cancelledPurchasesSnapshot else {
|
|
||||||
pp_log.debug("No purchase was refunded")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let isEligibleForFullVersion = isFullVersion()
|
let isEligibleForFullVersion = isFullVersion()
|
||||||
let hasCancelledFullVersion: Bool
|
let hasCancelledFullVersion: Bool
|
||||||
let hasCancelledTrustedNetworks: Bool
|
let hasCancelledTrustedNetworks: Bool
|
||||||
|
|
||||||
if isMac {
|
if isMac {
|
||||||
hasCancelledFullVersion = !isEligibleForFullVersion && (isCancelledPurchase(.fullVersion) || isCancelledPurchase(.fullVersion_macOS))
|
hasCancelledFullVersion = !isEligibleForFullVersion && (
|
||||||
|
refunds.contains(.fullVersion) || refunds.contains(.fullVersion_macOS)
|
||||||
|
)
|
||||||
hasCancelledTrustedNetworks = false
|
hasCancelledTrustedNetworks = false
|
||||||
} else {
|
} else {
|
||||||
hasCancelledFullVersion = !isEligibleForFullVersion && (isCancelledPurchase(.fullVersion) || isCancelledPurchase(.fullVersion_iOS))
|
hasCancelledFullVersion = !isEligibleForFullVersion && (
|
||||||
hasCancelledTrustedNetworks = !isEligibleForFullVersion && isCancelledPurchase(.trustedNetworks)
|
refunds.contains(.fullVersion) || refunds.contains(.fullVersion_iOS)
|
||||||
|
)
|
||||||
|
hasCancelledTrustedNetworks = !isEligibleForFullVersion && refunds.contains(.trustedNetworks)
|
||||||
}
|
}
|
||||||
|
|
||||||
// review features and potentially revert them if they were used (Siri is handled in AppDelegate)
|
// review features and potentially revert them if they were used (Siri is handled in AppDelegate)
|
||||||
return hasCancelledFullVersion || hasCancelledTrustedNetworks
|
if hasCancelledFullVersion || hasCancelledTrustedNetworks {
|
||||||
|
didRefundProducts.send()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,6 @@ extension OrganizerView {
|
||||||
|
|
||||||
@ObservedObject private var vpnManager: VPNManager
|
@ObservedObject private var vpnManager: VPNManager
|
||||||
|
|
||||||
@ObservedObject private var productManager: ProductManager
|
|
||||||
|
|
||||||
@Binding private var alertType: AlertType?
|
@Binding private var alertType: AlertType?
|
||||||
|
|
||||||
@Binding private var didHandleSubreddit: Bool
|
@Binding private var didHandleSubreddit: Bool
|
||||||
|
@ -45,7 +43,6 @@ extension OrganizerView {
|
||||||
init(alertType: Binding<AlertType?>, didHandleSubreddit: Binding<Bool>) {
|
init(alertType: Binding<AlertType?>, didHandleSubreddit: Binding<Bool>) {
|
||||||
profileManager = .shared
|
profileManager = .shared
|
||||||
vpnManager = .shared
|
vpnManager = .shared
|
||||||
productManager = .shared
|
|
||||||
_alertType = alertType
|
_alertType = alertType
|
||||||
_didHandleSubreddit = didHandleSubreddit
|
_didHandleSubreddit = didHandleSubreddit
|
||||||
}
|
}
|
||||||
|
@ -85,16 +82,6 @@ extension OrganizerView {
|
||||||
|
|
||||||
private func onScenePhase(_ phase: ScenePhase) {
|
private func onScenePhase(_ phase: ScenePhase) {
|
||||||
switch phase {
|
switch phase {
|
||||||
case .inactive:
|
|
||||||
productManager.snapshotRefunds()
|
|
||||||
|
|
||||||
case .active:
|
|
||||||
if productManager.hasNewRefunds() {
|
|
||||||
Task { @MainActor in
|
|
||||||
await vpnManager.uninstall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case .background:
|
case .background:
|
||||||
persist()
|
persist()
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
|
|
Loading…
Reference in New Issue