diff --git a/CHANGELOG.md b/CHANGELOG.md index 4879caf9..9dc6b6a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- "Restore purchases" not working. [#459](https://github.com/passepartoutvpn/passepartout-apple/issues/459) +- Purchase is not credited if any refund was issued in the past. [#461](https://github.com/passepartoutvpn/passepartout-apple/issues/461) + ## 2.3.2 (2024-01-09) ### Fixed diff --git a/Gemfile.lock b/Gemfile.lock index 5b66ef09..6a7bf6a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -207,6 +207,7 @@ GEM PLATFORMS arm64-darwin-22 x86_64-darwin-19 + x86_64-linux DEPENDENCIES dotenv diff --git a/Passepartout/App/Constants/Constants+App.swift b/Passepartout/App/Constants/Constants+App.swift index b9665a20..29cf5846 100644 --- a/Passepartout/App/Constants/Constants+App.swift +++ b/Passepartout/App/Constants/Constants+App.swift @@ -163,10 +163,11 @@ extension Constants { private static let parentPath = "Library/Caches" static let level: LoggerLevel = { - guard let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"], let levelNum = Int(levelString) else { - return .info + guard let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"], + let levelNum = Int(levelString) else { + return .debug } - return .init(rawValue: levelNum) ?? .info + return .init(rawValue: levelNum) ?? .debug }() static let maxBytes = 100000 diff --git a/Passepartout/App/Context/AppContext.swift b/Passepartout/App/Context/AppContext.swift index cdd85702..8b5a5ffe 100644 --- a/Passepartout/App/Context/AppContext.swift +++ b/Passepartout/App/Context/AppContext.swift @@ -122,15 +122,6 @@ private extension AppContext { self?.reviewer.reportEvent() } }.store(in: &cancellables) - - productManager.didRefundProducts - .receive(on: DispatchQueue.main) - .sink { [weak self] in - Task { - pp_log.info("Refunds detected, uninstalling VPN profile") - await self?.coreContext.vpnManager.uninstall() - } - }.store(in: &cancellables) } // eligibility: ignore network settings if ineligible diff --git a/PassepartoutLibrary/Sources/PassepartoutFrontend/Managers/ProductManager.swift b/PassepartoutLibrary/Sources/PassepartoutFrontend/Managers/ProductManager.swift index 71cf622a..332d3281 100644 --- a/PassepartoutLibrary/Sources/PassepartoutFrontend/Managers/ProductManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutFrontend/Managers/ProductManager.swift @@ -38,8 +38,6 @@ public final class ProductManager: NSObject, ObservableObject { public let buildProducts: BuildProducts - public let didRefundProducts = PassthroughSubject() - @Published public private(set) var appType: AppType @Published public private(set) var isRefreshingProducts = false @@ -58,19 +56,6 @@ public final class ProductManager: NSObject, ObservableObject { private var purchaseDates: [LocalProduct: Date] - private var cancelledPurchases: Set? { - willSet { - guard cancelledPurchases != nil else { - return - } - guard let newCancelledPurchases = newValue, newCancelledPurchases != cancelledPurchases else { - pp_log.debug("No purchase was refunded") - return - } - detectRefunds(newCancelledPurchases) - } - } - public init(inApp: any LocalInApp, receiptReader: ReceiptReader, overriddenAppType: AppType? = nil, @@ -85,7 +70,6 @@ public final class ProductManager: NSObject, ObservableObject { purchasedAppBuild = nil purchasedFeatures = [] purchaseDates = [:] - cancelledPurchases = nil super.init() @@ -194,15 +178,24 @@ public final class ProductManager: NSObject, ObservableObject { extension ProductManager { public func isEligible(forFeature feature: LocalProduct) -> Bool { - if let purchasedAppBuild = purchasedAppBuild { + if let purchasedAppBuild { if feature == .networkSettings && buildProducts.hasProduct(.networkSettings, atBuild: purchasedAppBuild) { return true } } + pp_log.verbose("Eligibility: purchasedFeatures = \(purchasedFeatures)") + pp_log.verbose("Eligibility: purchaseDates = \(purchaseDates)") + pp_log.verbose("Eligibility: isIncludedInFullVersion(\(feature)) = \(isIncludedInFullVersion(feature))") if isIncludedInFullVersion(feature) { - return isFullVersion() || isActivePurchase(feature) + let isFullVersion = isFullVersion() + let isActive = isActivePurchase(feature) + pp_log.verbose("Eligibility: isFullVersion() = \(isFullVersion)") + pp_log.verbose("Eligibility: isActivePurchase(\(feature)) = \(isActive)") + return isFullVersion || isActive } - return isActivePurchase(feature) + let isActive = isActivePurchase(feature) + pp_log.verbose("Eligibility: isActivePurchase(\(feature)) = \(isActive)") + return isActive } public func isEligible(forProvider providerName: ProviderName) -> Bool { @@ -219,11 +212,11 @@ extension ProductManager { extension ProductManager { func isActivePurchase(_ feature: LocalProduct) -> Bool { - purchasedFeatures.contains(feature) && cancelledPurchases?.contains(feature) != true + purchasedFeatures.contains(feature) } func isActivePurchase(where predicate: (LocalProduct) -> Bool) -> Bool { - purchasedFeatures.contains(where: predicate) && cancelledPurchases?.contains(where: predicate) != true + purchasedFeatures.contains(where: predicate) } func isCurrentPlatformVersion() -> Bool { @@ -236,16 +229,19 @@ extension ProductManager { public func isFullVersion() -> Bool { if appType == .fullVersion { + pp_log.verbose("Eligibility: appType = .fullVersion") return true } + pp_log.verbose("Eligibility: isCurrentPlatformVersion() = \(isCurrentPlatformVersion())") if isCurrentPlatformVersion() { return true } + pp_log.verbose("Eligibility: isActivePurchase(.fullVersion) = \(isActivePurchase(.fullVersion))") return isActivePurchase(.fullVersion) } public func isPayingUser() -> Bool { - !purchasedFeatures.subtracting(cancelledPurchases ?? []).isEmpty + !purchasedFeatures.isEmpty } } @@ -262,7 +258,6 @@ extension ProductManager { purchasedAppBuild = originalBuildNumber } purchasedFeatures.removeAll() - var newCancelledPurchases: Set = [] if let buildNumber = purchasedAppBuild { pp_log.debug("Original purchased build: \(buildNumber)") @@ -282,7 +277,6 @@ extension ProductManager { } if let cancellationDate = $0.cancellationDate { pp_log.debug("\t\(pid) [cancelled on: \(cancellationDate)]") - newCancelledPurchases.insert(product) return } if let purchaseDate = $0.originalPurchaseDate { @@ -296,7 +290,6 @@ extension ProductManager { if andNotify { objectWillChange.send() } - cancelledPurchases = newCancelledPurchases } } @@ -310,28 +303,4 @@ private extension ProductManager { false #endif } - - // FIXME: in-app, this is incomplete - func detectRefunds(_ refunds: Set) { - let isEligibleForFullVersion = isFullVersion() - let hasCancelledFullVersion: Bool - let hasCancelledTrustedNetworks: Bool - - if isMac { - hasCancelledFullVersion = !isEligibleForFullVersion && ( - refunds.contains(.fullVersion) || refunds.contains(.fullVersion_macOS) - ) - hasCancelledTrustedNetworks = false - } else { - hasCancelledFullVersion = !isEligibleForFullVersion && ( - 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) - if hasCancelledFullVersion || hasCancelledTrustedNetworks { - didRefundProducts.send() - } - } }