From 3290f45a08f18202ae7da4d66014e4018e6a9e66 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 4 Dec 2023 12:05:04 +0100 Subject: [PATCH 01/68] refactor(payments): create PurchaseManager.swift --- IVPNClient.xcodeproj/project.pbxproj | 4 ++++ IVPNClient/Managers/PurchaseManager.swift | 28 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 IVPNClient/Managers/PurchaseManager.swift diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index 386cb68ec..816276464 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 8223C54F22EAEC7000CD283D /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8223C54B22E9E93A00CD283D /* Session.swift */; }; 8223C55022EAEC7100CD283D /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8223C54B22E9E93A00CD283D /* Session.swift */; }; 822563922431E03A00AE7F8D /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822563912431E03A00AE7F8D /* AccountView.swift */; }; + 8228C8D22B1DE906005977D3 /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8228C8D12B1DE906005977D3 /* PurchaseManager.swift */; }; 8229196E2182EB1C00978BBA /* String+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8229196D2182EB1B00978BBA /* String+Ext.swift */; }; 822919712182EB1C00978BBA /* String+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8229196D2182EB1B00978BBA /* String+Ext.swift */; }; 822920A02480FA3600476FC1 /* ServersSort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8229209F2480FA3600476FC1 /* ServersSort.swift */; }; @@ -498,6 +499,7 @@ 8223C54B22E9E93A00CD283D /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; 8223C54D22EAE93F00CD283D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 822563912431E03A00AE7F8D /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; + 8228C8D12B1DE906005977D3 /* PurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; 8229196D2182EB1B00978BBA /* String+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Ext.swift"; sourceTree = ""; }; 8229209F2480FA3600476FC1 /* ServersSort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersSort.swift; sourceTree = ""; }; 822B85D821B941A200715691 /* NotificationName+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationName+Ext.swift"; sourceTree = ""; }; @@ -1006,6 +1008,7 @@ 8206F32224367A240056B465 /* VPNErrorObserver.swift */, 825E836225A4834200938240 /* APIPublicKeyPin.swift */, 826C1F8325DBEF1800314C4B /* DNSManager.swift */, + 8228C8D12B1DE906005977D3 /* PurchaseManager.swift */, ); path = Managers; sourceTree = ""; @@ -2357,6 +2360,7 @@ 8206E5D022967E37003119AF /* UserActivityType.swift in Sources */, 82A6D74A24A3780B00D6C0E1 /* ConnectToServerPopupView.swift in Sources */, 828772FB221C28E000D5E330 /* FlagImageView.swift in Sources */, + 8228C8D22B1DE906005977D3 /* PurchaseManager.swift in Sources */, 82E7880C22B0DA0D00A98D76 /* NETunnelProviderProtocol+Ext.swift in Sources */, 82968A35298A98C300077E0A /* KeyChain.swift in Sources */, 82F638C2217DA89000410318 /* AddressType.swift in Sources */, diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift new file mode 100644 index 000000000..5a72823f0 --- /dev/null +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -0,0 +1,28 @@ +// +// PurchaseManager.swift +// IVPN iOS app +// https://github.com/ivpn/ios-app +// +// Created by Juraj Hilje on 2023-12-04. +// Copyright (c) 2023 IVPN Limited. +// +// This file is part of the IVPN iOS app. +// +// The IVPN iOS app is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any later version. +// +// The IVPN iOS app is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License +// along with the IVPN iOS app. If not, see . +// + +import StoreKit + +class PurchaseManager { + +} From d983758b95419dfcd2d3dd2d64df07aac6b7b6e5 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 4 Dec 2023 12:09:34 +0100 Subject: [PATCH 02/68] refactor(payments): set DEPLOYMENT_TARGET to 15.0 --- IVPNClient.xcodeproj/project.pbxproj | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index 816276464..299245070 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -2529,7 +2529,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UnitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2560,7 +2560,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UnitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2596,7 +2596,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "wireguard-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2636,7 +2636,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "wireguard-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2668,7 +2668,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2697,7 +2697,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2725,7 +2725,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2792,7 +2792,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ""; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -2821,7 +2821,7 @@ ); HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "$(SRCROOT)/IVPNClient/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2857,7 +2857,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UnitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2903,7 +2903,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "openvpn-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2946,7 +2946,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "wireguard-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2985,7 +2985,7 @@ INFOPLIST_FILE = IVPNWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = IVPNWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 IVPN. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -3020,7 +3020,7 @@ INFOPLIST_FILE = IVPNWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = IVPNWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 IVPN. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; @@ -3054,7 +3054,7 @@ INFOPLIST_FILE = IVPNWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = IVPNWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 IVPN. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; @@ -3098,7 +3098,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "openvpn-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3150,7 +3150,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "openvpn-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3224,7 +3224,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ""; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -3281,7 +3281,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ""; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; @@ -3310,7 +3310,7 @@ ); HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "$(SRCROOT)/IVPNClient/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3349,7 +3349,7 @@ ); HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "$(SRCROOT)/IVPNClient/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From d7d86606009219297d11a7af8719994926a4ff6f Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 4 Dec 2023 14:25:09 +0100 Subject: [PATCH 03/68] feat(payments): update PurchaseManager.swift --- IVPNClient/Managers/PurchaseManager.swift | 54 ++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 5a72823f0..83022aab6 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -23,6 +23,58 @@ import StoreKit -class PurchaseManager { +class PurchaseManager: NSObject { + + // MARK: - Properties - + + private(set) var products: [Product] = [] + + override init() { + super.init() + SKPaymentQueue.default().add(self) + } + + // MARK: - Methods - + + func loadProducts() async throws { + products = try await Product.products(for: ProductIdentifier.all) + } + + func purchase(_ product: Product) async throws { + let result = try await product.purchase() + + switch result { + case let .success(.verified(transaction)): + // Successful purchase + await transaction.finish() + case .success(.unverified(_, _)): + // Successful purchase but transaction/receipt can't be verified + // Could be a jailbroken phone + break + case .pending: + // Transaction waiting on SCA (Strong Customer Authentication) or + // approval from Ask to Buy + break + case .userCancelled: + // ^^^ + break + @unknown default: + break + } + } + +} + +// MARK: - SKPaymentTransactionObserver - + +extension PurchaseManager: SKPaymentTransactionObserver { + + func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + + } + + func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { + return true + } } From cfb1eb751767342312deb5693cbbf7a12d87e481 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 4 Dec 2023 14:29:07 +0100 Subject: [PATCH 04/68] refactor: update ProductIdentifier model --- IVPNClient.xcodeproj/project.pbxproj | 8 ++++---- IVPNClient/Managers/IAPManager.swift | 2 +- IVPNClient/Managers/PurchaseManager.swift | 2 +- ...roductIdentifier.swift => ProductId.swift} | 12 ++--------- IVPNClient/Models/Service.swift | 20 +++++++++---------- 5 files changed, 18 insertions(+), 26 deletions(-) rename IVPNClient/Models/{ProductIdentifier.swift => ProductId.swift} (90%) diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index 299245070..6a02d59e1 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -95,7 +95,7 @@ 8247A5ED215D037600E8D680 /* UserDefaults+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825A43FC215CCFE70076131F /* UserDefaults+Ext.swift */; }; 8247C0602A7CF54300A7C02F /* V2RayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8247C05F2A7CF54300A7C02F /* V2RayConfig.swift */; }; 8247E1DA22686217006C0C08 /* IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8247E1D922686217006C0C08 /* IAPManager.swift */; }; - 8247E1DE22687C28006C0C08 /* ProductIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8247E1DD22687C28006C0C08 /* ProductIdentifier.swift */; }; + 8247E1DE22687C28006C0C08 /* ProductId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8247E1DD22687C28006C0C08 /* ProductId.swift */; }; 82486FAD2A277058009B53F4 /* liboqs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 82486FAC2A277058009B53F4 /* liboqs.a */; }; 824B141C2609D5E700766B05 /* DNSProtocolTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824B141B2609D5E700766B05 /* DNSProtocolTypeTests.swift */; }; 824B86B926D3D16100D0101A /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824B86AD26D3D16100D0101A /* FileManager+Extension.swift */; }; @@ -526,7 +526,7 @@ 824777E621A6BC3A001EEFAF /* Network+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Network+CoreDataProperties.swift"; sourceTree = ""; }; 8247C05F2A7CF54300A7C02F /* V2RayConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V2RayConfig.swift; sourceTree = ""; }; 8247E1D922686217006C0C08 /* IAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPManager.swift; sourceTree = ""; }; - 8247E1DD22687C28006C0C08 /* ProductIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductIdentifier.swift; sourceTree = ""; }; + 8247E1DD22687C28006C0C08 /* ProductId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductId.swift; sourceTree = ""; }; 82486FAC2A277058009B53F4 /* liboqs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = liboqs.a; sourceTree = ""; }; 82486FB02A27705F009B53F4 /* sha3x4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha3x4.h; sourceTree = ""; }; 82486FB12A27705F009B53F4 /* oqsconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = oqsconfig.h; sourceTree = ""; }; @@ -1024,7 +1024,7 @@ 82C9739F217DFA9C00CE06D4 /* Host.swift */, 9C6942361DD218A900F9A801 /* AccessDetails.swift */, 9C3031341DB42EF900C38B0C /* Application.swift */, - 8247E1DD22687C28006C0C08 /* ProductIdentifier.swift */, + 8247E1DD22687C28006C0C08 /* ProductId.swift */, 9CB2CE1E1DAA5258007A4D2D /* Authentication.swift */, 9CBFF02F2102254800FE1757 /* Settings.swift */, 826C56D122FD4F2600D2B76A /* ServiceStatus.swift */, @@ -2379,7 +2379,7 @@ 821CA2D7287C5AB20067F70D /* PortViewController.swift in Sources */, 82061F66238D2730009DDF4D /* ICMPHeader.swift in Sources */, 824BC466240906ED00A61B29 /* VPNStatusViewModel.swift in Sources */, - 8247E1DE22687C28006C0C08 /* ProductIdentifier.swift in Sources */, + 8247E1DE22687C28006C0C08 /* ProductId.swift in Sources */, 829DF2822497953C000DC2DB /* UIButton+Ext.swift in Sources */, 82234B6721BA7F3500B082DE /* Logger.swift in Sources */, 82DB75EC239E75EB0073E846 /* NEVPNStatus+Ext.swift in Sources */, diff --git a/IVPNClient/Managers/IAPManager.swift b/IVPNClient/Managers/IAPManager.swift index ce9225166..b50ed4023 100644 --- a/IVPNClient/Managers/IAPManager.swift +++ b/IVPNClient/Managers/IAPManager.swift @@ -50,7 +50,7 @@ class IAPManager { // MARK: - Methods - func fetchProducts(completion: @escaping ([SKProduct]?, String?) -> Void) { - SwiftyStoreKit.retrieveProductsInfo(ProductIdentifier.all) { result in + SwiftyStoreKit.retrieveProductsInfo(ProductId.all) { result in if !result.retrievedProducts.isEmpty { self.products = Array(result.retrievedProducts) completion(Array(result.retrievedProducts), nil) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 83022aab6..4fbe28f1e 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -37,7 +37,7 @@ class PurchaseManager: NSObject { // MARK: - Methods - func loadProducts() async throws { - products = try await Product.products(for: ProductIdentifier.all) + products = try await Product.products(for: ProductId.all) } func purchase(_ product: Product) async throws { diff --git a/IVPNClient/Models/ProductIdentifier.swift b/IVPNClient/Models/ProductId.swift similarity index 90% rename from IVPNClient/Models/ProductIdentifier.swift rename to IVPNClient/Models/ProductId.swift index 730185db6..f7e38871a 100644 --- a/IVPNClient/Models/ProductIdentifier.swift +++ b/IVPNClient/Models/ProductId.swift @@ -1,13 +1,5 @@ // -// ProductIdentifier.swift -// IVPNClient -// -// Created by Juraj Hilje on 18/04/2019. -// Copyright © 2019 IVPN. All rights reserved. -// - -// -// ProductIdentifier.swift +// ProductId.swift // IVPN iOS app // https://github.com/ivpn/ios-app // @@ -31,7 +23,7 @@ import Foundation -struct ProductIdentifier { +struct ProductId { static let standardWeek = "net.ivpn.subscriptions.standard.1week" static let standardMonth = "net.ivpn.subscriptions.standard.1month" diff --git a/IVPNClient/Models/Service.swift b/IVPNClient/Models/Service.swift index 347361fd8..1c3d0d14f 100644 --- a/IVPNClient/Models/Service.swift +++ b/IVPNClient/Models/Service.swift @@ -89,28 +89,28 @@ struct Service { case .standard: switch duration { case .week: - return ProductIdentifier.standardWeek + return ProductId.standardWeek case .month: - return ProductIdentifier.standardMonth + return ProductId.standardMonth case .year: - return ProductIdentifier.standardYear + return ProductId.standardYear case .twoYears: - return ProductIdentifier.standardTwoYears + return ProductId.standardTwoYears case .threeYears: - return ProductIdentifier.standardThreeYears + return ProductId.standardThreeYears } case .pro: switch duration { case .week: - return ProductIdentifier.proWeek + return ProductId.proWeek case .month: - return ProductIdentifier.proMonth + return ProductId.proMonth case .year: - return ProductIdentifier.proYear + return ProductId.proYear case .twoYears: - return ProductIdentifier.proTwoYears + return ProductId.proTwoYears case .threeYears: - return ProductIdentifier.proThreeYears + return ProductId.proThreeYears } } } From 60949775b272bdc1a77260c77e811053f29927ff Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 4 Dec 2023 16:10:13 +0100 Subject: [PATCH 05/68] refactor(payments): update PurchaseManager.swift --- IVPNClient/Managers/IAPManager.swift | 4 ---- IVPNClient/Managers/PurchaseManager.swift | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/IVPNClient/Managers/IAPManager.swift b/IVPNClient/Managers/IAPManager.swift index b50ed4023..e46a5d2f3 100644 --- a/IVPNClient/Managers/IAPManager.swift +++ b/IVPNClient/Managers/IAPManager.swift @@ -216,10 +216,6 @@ class IAPManager { return formatter.string(from: product.price) ?? "" } - func completeTransactions() { - SwiftyStoreKit.completeTransactions(atomically: true) { _ in } - } - // MARK: - Private methods - private func purchaseParams(purchase: PurchaseDetails, endpoint: String) -> [URLQueryItem] { diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 4fbe28f1e..12874ea2d 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -27,15 +27,20 @@ class PurchaseManager: NSObject { // MARK: - Properties - - private(set) var products: [Product] = [] + static let shared = PurchaseManager() - override init() { - super.init() - SKPaymentQueue.default().add(self) + var canMakePurchases: Bool { + return SKPaymentQueue.canMakePayments() } + private(set) var products: [Product] = [] + // MARK: - Methods - + func startObserver() { + SKPaymentQueue.default().add(self) + } + func loadProducts() async throws { products = try await Product.products(for: ProductId.all) } @@ -63,6 +68,14 @@ class PurchaseManager: NSObject { } } + func getProduct(id: String) -> Product? { + for product in products where product.id == id { + return product + } + + return nil + } + } // MARK: - SKPaymentTransactionObserver - From e7adaaf5cf1fd4d0f6223d43b8e2461e9a36f7a3 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 4 Dec 2023 16:43:14 +0100 Subject: [PATCH 06/68] refactor(payments): update PurchaseManager.swift --- IVPNClient/Managers/PurchaseManager.swift | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 12874ea2d..3c09f7fee 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -35,6 +35,18 @@ class PurchaseManager: NSObject { private(set) var products: [Product] = [] + private var apiEndpoint: String { + if KeyChain.sessionToken != nil { + if !Application.shared.serviceStatus.isNewStyleAccount() { + return Config.apiPaymentAddLegacy + } + + return Config.apiPaymentAdd + } + + return Config.apiPaymentInitial + } + // MARK: - Methods - func startObserver() { @@ -68,6 +80,26 @@ class PurchaseManager: NSObject { } } + func finishPurchase(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + let endpoint = apiEndpoint + let params = purchaseParams(transaction: transaction, endpoint: endpoint) + let request = ApiRequestDI(method: .post, endpoint: endpoint, params: params) + + ApiService.shared.requestCustomError(request) { (result: ResultCustomError) in + switch result { + case .success(let sessionStatus): + Application.shared.serviceStatus = sessionStatus.serviceStatus + // try await transaction.finish() + completion(sessionStatus.serviceStatus, nil) + log(.info, message: "Purchase was successfully finished.") + case .failure(let error): + let defaultErrorResult = ErrorResult(status: 500, message: "Purchase was completed but service cannot be activated. Restart application to retry.") + completion(nil, error ?? defaultErrorResult) + log(.error, message: "There was an error with purchase completion: \(error?.message ?? "")") + } + } + } + func getProduct(id: String) -> Product? { for product in products where product.id == id { return product @@ -76,6 +108,59 @@ class PurchaseManager: NSObject { return nil } + // MARK: - Private methods - + + private func base64receipt() -> String { + if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { + do { + let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) + return receiptData.base64EncodedString(options: []) + } + catch { + log(.error, message: "Couldn't read receipt data with error: \(error.localizedDescription)") + } + } + + return "" + } + + private func purchaseParams(transaction: Transaction, endpoint: String) -> [URLQueryItem] { + let productId = transaction.productID + let transactionId = transaction.id.formatted() + let receipt = base64receipt() + + switch endpoint { + case Config.apiPaymentInitial: + return [ + URLQueryItem(name: "account_id", value: KeyChain.tempUsername ?? ""), + URLQueryItem(name: "product_id", value: productId), + URLQueryItem(name: "transaction_id", value: transactionId), + URLQueryItem(name: "receipt", value: receipt) + ] + case Config.apiPaymentAdd: + return [ + URLQueryItem(name: "session_token", value: KeyChain.sessionToken ?? ""), + URLQueryItem(name: "product_id", value: productId), + URLQueryItem(name: "transaction_id", value: transactionId), + URLQueryItem(name: "receipt", value: receipt) + ] + case Config.apiPaymentAddLegacy: + return [ + URLQueryItem(name: "username", value: KeyChain.username ?? ""), + URLQueryItem(name: "productId", value: productId), + URLQueryItem(name: "transactionId", value: transactionId), + URLQueryItem(name: "receiptData", value: receipt) + ] + default: + return [] + } + } + + private func restorePurchaseParams() -> [URLQueryItem] { + let receipt = base64receipt() + return [URLQueryItem(name: "receipt", value: receipt)] + } + } // MARK: - SKPaymentTransactionObserver - From 424665f2bb9d0d224a8ed7381e159a416fdbb88a Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 6 Dec 2023 15:31:36 +0100 Subject: [PATCH 07/68] refactor(payments): update PaymentViewController --- IVPNClient/Models/Service.swift | 8 ++++-- .../Payment/PaymentViewController.swift | 28 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/IVPNClient/Models/Service.swift b/IVPNClient/Models/Service.swift index 1c3d0d14f..1bdd91635 100644 --- a/IVPNClient/Models/Service.swift +++ b/IVPNClient/Models/Service.swift @@ -33,9 +33,11 @@ struct Service { // MARK: - Computed properties - var priceText: String { - guard !IAPManager.shared.products.isEmpty else { return "" } - guard let product = IAPManager.shared.getProduct(identifier: productId) else { return "" } - return IAPManager.shared.productPrice(product: product) + guard let product = PurchaseManager.shared.getProduct(id: productId) else { + return "" + } + + return product.displayPrice } var durationText: String { diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index ca64c8302..c3c6a6b05 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -54,7 +54,7 @@ class PaymentViewController: UITableViewController { lazy var retryButton: UIButton = { let button = UIButton(type: .system) - button.addTarget(self, action: #selector(fetchProducts), for: .touchUpInside) + button.addTarget(self, action: #selector(loadProducts), for: .touchUpInside) button.setTitle("Retry", for: .normal) button.sizeToFit() button.isHidden = true @@ -128,7 +128,11 @@ class PaymentViewController: UITableViewController { service = Service(type: serviceType, duration: .year) } - fetchProducts() + Task { @MainActor in + do { + await loadProducts() + } + } } } @@ -175,21 +179,15 @@ class PaymentViewController: UITableViewController { } } - @objc private func fetchProducts() { + @objc private func loadProducts() async { displayMode = .loading - IAPManager.shared.fetchProducts { [weak self] products, error in - guard let self = self else { return } - - if error != nil { - self.showAlert(title: "iTunes Store error", message: "Cannot connect to iTunes Store") - self.displayMode = .error - return - } - - if products != nil { - self.displayMode = .content - } + do { + try await PurchaseManager.shared.loadProducts() + displayMode = .content + } catch { + showAlert(title: "iTunes Store error", message: "Cannot connect to iTunes Store") + displayMode = .error } } From a946b46d02beacb39382316851b4e9556b5434c3 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 6 Dec 2023 15:36:01 +0100 Subject: [PATCH 08/68] refactor(payments): update PaymentViewController --- .../Signup/Payment/PaymentViewController.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index c3c6a6b05..8bf5a3dbd 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -54,7 +54,7 @@ class PaymentViewController: UITableViewController { lazy var retryButton: UIButton = { let button = UIButton(type: .system) - button.addTarget(self, action: #selector(loadProducts), for: .touchUpInside) + button.addTarget(self, action: #selector(load), for: .touchUpInside) button.setTitle("Retry", for: .normal) button.sizeToFit() button.isHidden = true @@ -128,11 +128,7 @@ class PaymentViewController: UITableViewController { service = Service(type: serviceType, duration: .year) } - Task { @MainActor in - do { - await loadProducts() - } - } + load() } } @@ -179,7 +175,15 @@ class PaymentViewController: UITableViewController { } } - @objc private func loadProducts() async { + @objc private func load() { + Task { @MainActor in + do { + await loadProducts() + } + } + } + + private func loadProducts() async { displayMode = .loading do { From 7ace0ccbdd96eff0ad1627acf33270d1bb6ff698 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 6 Dec 2023 15:55:08 +0100 Subject: [PATCH 09/68] refactor(payments): update UIViewController+Ext.swift --- IVPNClient/Utilities/Extensions/UIViewController+Ext.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IVPNClient/Utilities/Extensions/UIViewController+Ext.swift b/IVPNClient/Utilities/Extensions/UIViewController+Ext.swift index 3b35775fd..410556486 100644 --- a/IVPNClient/Utilities/Extensions/UIViewController+Ext.swift +++ b/IVPNClient/Utilities/Extensions/UIViewController+Ext.swift @@ -256,7 +256,7 @@ extension UIViewController { } func deviceCanMakePurchases() -> Bool { - guard IAPManager.shared.canMakePurchases else { + guard PurchaseManager.shared.canMakePurchases else { showAlert(title: "Error", message: "In-App Purchases are not available on your device.") return false } From 7dfa42b8a190f1b49fbf8dfbc2a192fdc4b71a62 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 7 Dec 2023 15:44:19 +0100 Subject: [PATCH 10/68] refactor(payments): update PaymentViewController --- IVPNClient/Managers/PurchaseManager.swift | 23 +++++++++-- .../Payment/PaymentViewController.swift | 39 ++++++++++--------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 3c09f7fee..c470b9530 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -57,13 +57,18 @@ class PurchaseManager: NSObject { products = try await Product.products(for: ProductId.all) } - func purchase(_ product: Product) async throws { + func purchase(_ productId: String) async throws -> Transaction? { + guard let product = getProduct(id: productId) else { + return nil + } + let result = try await product.purchase() switch result { case let .success(.verified(transaction)): // Successful purchase - await transaction.finish() + // await transaction.finish() + return transaction case .success(.unverified(_, _)): // Successful purchase but transaction/receipt can't be verified // Could be a jailbroken phone @@ -78,9 +83,11 @@ class PurchaseManager: NSObject { @unknown default: break } + + return nil } - func finishPurchase(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + func completePurchase(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { let endpoint = apiEndpoint let params = purchaseParams(transaction: transaction, endpoint: endpoint) let request = ApiRequestDI(method: .post, endpoint: endpoint, params: params) @@ -89,7 +96,7 @@ class PurchaseManager: NSObject { switch result { case .success(let sessionStatus): Application.shared.serviceStatus = sessionStatus.serviceStatus - // try await transaction.finish() + self.finishTransaction(transaction) completion(sessionStatus.serviceStatus, nil) log(.info, message: "Purchase was successfully finished.") case .failure(let error): @@ -100,6 +107,14 @@ class PurchaseManager: NSObject { } } + func finishTransaction(_ transaction: Transaction) { + Task { @MainActor in + do { + await transaction.finish() + } + } + } + func getProduct(id: String) -> Product? { for product in products where product.id == id { return product diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index 8bf5a3dbd..0192f77c2 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -22,7 +22,7 @@ // import UIKit -import SwiftyStoreKit +import StoreKit import SnapKit import JGProgressHUD @@ -101,7 +101,11 @@ class PaymentViewController: UITableViewController { } @IBAction func purchase(_ sender: UIButton) { - purchaseProduct(identifier: service.productId) + Task { @MainActor in + do { + await purchaseProduct(identifier: service.productId) + } + } } @IBAction func close() { @@ -195,31 +199,30 @@ class PaymentViewController: UITableViewController { } } - private func purchaseProduct(identifier: String) { - guard deviceCanMakePurchases() else { return } + private func purchaseProduct(identifier: String) async { + guard deviceCanMakePurchases() else { + return + } hud.indicatorView = JGProgressHUDIndeterminateIndicatorView() hud.detailTextLabel.text = "Processing payment..." hud.show(in: (navigationController?.view)!) - IAPManager.shared.purchaseProduct(identifier: identifier) { [weak self] purchase, error in - guard let self = self else { return } - - if let error = error { - self.showErrorAlert(title: "Error", message: error) - self.hud.dismiss() - return - } - - if let purchase = purchase { - self.completePurchase(purchase: purchase) + do { + if let transaction = try await PurchaseManager.shared.purchase(identifier) { + completePurchase(transaction: transaction) } + } catch { + showErrorAlert(title: "Error", message: error.localizedDescription) + hud.dismiss() } } - private func completePurchase(purchase: PurchaseDetails) { - IAPManager.shared.completePurchase(purchase: purchase) { [weak self] serviceStatus, error in - guard let self = self else { return } + private func completePurchase(transaction: Transaction) { + PurchaseManager.shared.completePurchase(transaction: transaction) { [weak self] serviceStatus, error in + guard let self = self else { + return + } self.hud.dismiss() From c814d6e70e747091c38501a34e6737775b55dd6d Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 7 Dec 2023 21:35:03 +0100 Subject: [PATCH 11/68] refactor(payments): update PurchaseManager.swift --- IVPNClient/Managers/PurchaseManager.swift | 6 ++---- .../Signup/Payment/PaymentViewController.swift | 12 ++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index c470b9530..01359d430 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -108,10 +108,8 @@ class PurchaseManager: NSObject { } func finishTransaction(_ transaction: Transaction) { - Task { @MainActor in - do { - await transaction.finish() - } + Task { + await transaction.finish() } } diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index 0192f77c2..694b02b14 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -101,10 +101,8 @@ class PaymentViewController: UITableViewController { } @IBAction func purchase(_ sender: UIButton) { - Task { @MainActor in - do { - await purchaseProduct(identifier: service.productId) - } + Task { + await purchaseProduct(identifier: service.productId) } } @@ -180,10 +178,8 @@ class PaymentViewController: UITableViewController { } @objc private func load() { - Task { @MainActor in - do { - await loadProducts() - } + Task { + await loadProducts() } } From 03b4e359d7254ef4fed7e8fee0c2219b04ff19d9 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 7 Dec 2023 21:39:37 +0100 Subject: [PATCH 12/68] refactor: update LoginViewController --- IVPNClient/Scenes/Signup/LoginViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IVPNClient/Scenes/Signup/LoginViewController.swift b/IVPNClient/Scenes/Signup/LoginViewController.swift index 14a3b5eac..023db0825 100644 --- a/IVPNClient/Scenes/Signup/LoginViewController.swift +++ b/IVPNClient/Scenes/Signup/LoginViewController.swift @@ -110,8 +110,8 @@ class LoginViewController: UIViewController { return } - if account != nil { - self.userName.text = account?.accountId + if let account = account { + self.userName.text = account.accountId self.sessionManager.createSession() } } From 04665f0f3cce453fb346d7a8e6364a11708fac69 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Fri, 8 Dec 2023 11:05:42 +0100 Subject: [PATCH 13/68] refactor(payments): update SelectPlanViewController --- .../SelectPlan/SelectPlanViewController.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/IVPNClient/Scenes/Signup/SelectPlan/SelectPlanViewController.swift b/IVPNClient/Scenes/Signup/SelectPlan/SelectPlanViewController.swift index 77c7aec6c..668dc9ddd 100644 --- a/IVPNClient/Scenes/Signup/SelectPlan/SelectPlanViewController.swift +++ b/IVPNClient/Scenes/Signup/SelectPlan/SelectPlanViewController.swift @@ -59,7 +59,7 @@ class SelectPlanViewController: UITableViewController { lazy var retryButton: UIButton = { let button = UIButton(type: .system) - button.addTarget(self, action: #selector(fetchProducts), for: .touchUpInside) + button.addTarget(self, action: #selector(load), for: .touchUpInside) button.setTitle("Retry", for: .normal) button.sizeToFit() button.isHidden = true @@ -133,7 +133,7 @@ class SelectPlanViewController: UITableViewController { super.viewDidAppear(animated) if displayMode == .loading { - fetchProducts() + load() } segueStarted = false @@ -197,22 +197,22 @@ class SelectPlanViewController: UITableViewController { } } - @objc private func fetchProducts() { + @objc private func load() { + Task { + await loadProducts() + } + } + + private func loadProducts() async { displayMode = .loading - IAPManager.shared.fetchProducts { [weak self] products, error in - guard let self = self else { return } - - if error != nil { - self.showAlert(title: "iTunes Store error", message: "Cannot connect to iTunes Store") - self.displayMode = .error - return - } - - if products != nil { - self.updateSubscriptions() - self.displayMode = .content - } + do { + try await PurchaseManager.shared.loadProducts() + updateSubscriptions() + displayMode = .content + } catch { + showAlert(title: "iTunes Store error", message: "Cannot connect to iTunes Store") + displayMode = .error } } From e2578a6c7553467f9a8e004c7db05d49bb5272ba Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 11 Dec 2023 11:41:29 +0100 Subject: [PATCH 14/68] feat(payments): update PurchaseManager --- IVPNClient/Managers/PurchaseManager.swift | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 01359d430..b2b2f5645 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -87,6 +87,23 @@ class PurchaseManager: NSObject { return nil } + func restorePurchases(completion: @escaping (Account?, ErrorResult?) -> Void) { + Task { + for await result in Transaction.currentEntitlements { + guard case .verified(let transaction) = result else { + continue + } + + if transaction.revocationDate == nil { + self.completeRestoredPurchase(transaction: transaction) { account, error in + completion(account, error) + log(.info, message: "Purchases are restored.") + } + } + } + } + } + func completePurchase(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { let endpoint = apiEndpoint let params = purchaseParams(transaction: transaction, endpoint: endpoint) @@ -107,6 +124,29 @@ class PurchaseManager: NSObject { } } + func completeRestoredPurchase(transaction: Transaction, completion: @escaping (Account?, ErrorResult?) -> Void) { + let params = restorePurchaseParams() + let request = ApiRequestDI(method: .post, endpoint: Config.apiPaymentRestore, params: params) + + ApiService.shared.requestCustomError(request) { (result: ResultCustomError) in + switch result { + case .success(let account): + self.finishTransaction(transaction) + KeyChain.username = account.accountId + completion(account, nil) + log(.info, message: "Purchase was successfully restored.") + case .failure(let error): + let defaultErrorResult = ErrorResult(status: 500, message: "Purchase was restored but service cannot be activated. Restart application to retry.") + completion(nil, error ?? defaultErrorResult) + log(.error, message: "There was an error with purchase completion: \(error?.message ?? "")") + } + } + } + + func sync() async -> Bool { + return ((try? await AppStore.sync()) != nil) + } + func finishTransaction(_ transaction: Transaction) { Task { await transaction.finish() From a72c9bff676c5da8e6271edc8e28e6fe9188bc11 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Mon, 11 Dec 2023 11:42:07 +0100 Subject: [PATCH 15/68] refactor(payments): update LoginViewController --- IVPNClient/Scenes/Signup/LoginViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IVPNClient/Scenes/Signup/LoginViewController.swift b/IVPNClient/Scenes/Signup/LoginViewController.swift index 023db0825..94cdff23c 100644 --- a/IVPNClient/Scenes/Signup/LoginViewController.swift +++ b/IVPNClient/Scenes/Signup/LoginViewController.swift @@ -102,7 +102,7 @@ class LoginViewController: UIViewController { hud.detailTextLabel.text = "Restoring purchases..." hud.show(in: (navigationController?.view)!) - IAPManager.shared.restorePurchases { account, error in + PurchaseManager.shared.restorePurchases { account, error in self.hud.dismiss() if let error = error { From 191d2f34619b6f66c986965e1ce6e497a562347c Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 12 Dec 2023 10:28:42 +0100 Subject: [PATCH 16/68] feat(payments): update PurchaseManager --- IVPNClient/AppDelegate.swift | 2 +- IVPNClient/Managers/PurchaseManager.swift | 26 ++++++++++++++----- .../Payment/PaymentViewController.swift | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/IVPNClient/AppDelegate.swift b/IVPNClient/AppDelegate.swift index 5bd838243..1f698a036 100644 --- a/IVPNClient/AppDelegate.swift +++ b/IVPNClient/AppDelegate.swift @@ -85,7 +85,7 @@ class AppDelegate: UIResponder { return } - IAPManager.shared.finishIncompletePurchases { serviceStatus, _ in + PurchaseManager.shared.completeUnfinishedTransactions { serviceStatus, _ in guard let viewController = UIApplication.topViewController() else { return } if let serviceStatus = serviceStatus { diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index b2b2f5645..a3bc880eb 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -95,7 +95,7 @@ class PurchaseManager: NSObject { } if transaction.revocationDate == nil { - self.completeRestoredPurchase(transaction: transaction) { account, error in + self.getAccountFor(transaction: transaction) { account, error in completion(account, error) log(.info, message: "Purchases are restored.") } @@ -104,7 +104,23 @@ class PurchaseManager: NSObject { } } - func completePurchase(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + func completeUnfinishedTransactions(completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + Task { + for await result in Transaction.unfinished { + guard case .verified(let transaction) = result else { + continue + } + + if transaction.revocationDate == nil { + complete(transaction: transaction) { serviceStatus, error in + completion(serviceStatus, error) + } + } + } + } + } + + func complete(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { let endpoint = apiEndpoint let params = purchaseParams(transaction: transaction, endpoint: endpoint) let request = ApiRequestDI(method: .post, endpoint: endpoint, params: params) @@ -124,7 +140,7 @@ class PurchaseManager: NSObject { } } - func completeRestoredPurchase(transaction: Transaction, completion: @escaping (Account?, ErrorResult?) -> Void) { + func getAccountFor(transaction: Transaction, completion: @escaping (Account?, ErrorResult?) -> Void) { let params = restorePurchaseParams() let request = ApiRequestDI(method: .post, endpoint: Config.apiPaymentRestore, params: params) @@ -143,10 +159,6 @@ class PurchaseManager: NSObject { } } - func sync() async -> Bool { - return ((try? await AppStore.sync()) != nil) - } - func finishTransaction(_ transaction: Transaction) { Task { await transaction.finish() diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index 694b02b14..86cb1e1e1 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -215,7 +215,7 @@ class PaymentViewController: UITableViewController { } private func completePurchase(transaction: Transaction) { - PurchaseManager.shared.completePurchase(transaction: transaction) { [weak self] serviceStatus, error in + PurchaseManager.shared.complete(transaction: transaction) { [weak self] serviceStatus, error in guard let self = self else { return } From 11fc204e111e8ab8eb683353cc95a01da9879dba Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 12 Dec 2023 11:12:14 +0100 Subject: [PATCH 17/68] feat(payments): update error handling --- IVPNClient/Managers/PurchaseManager.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index a3bc880eb..10e38668e 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -96,11 +96,16 @@ class PurchaseManager: NSObject { if transaction.revocationDate == nil { self.getAccountFor(transaction: transaction) { account, error in + log(.info, message: "Purchase is restored.") completion(account, error) - log(.info, message: "Purchases are restored.") + return } } } + + let error = ErrorResult(status: 500, message: "There are no purchases to restore.") + log(.error, message: "There are no purchases to restore.") + completion(nil, error) } } From e9fd70701571ed932188cf66b16fb0fa34f1fd62 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 12 Dec 2023 12:02:09 +0100 Subject: [PATCH 18/68] chore: clean up PurchaseManager --- IVPNClient/Managers/PurchaseManager.swift | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index 10e38668e..c0ada5a4d 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -49,10 +49,6 @@ class PurchaseManager: NSObject { // MARK: - Methods - - func startObserver() { - SKPaymentQueue.default().add(self) - } - func loadProducts() async throws { products = try await Product.products(for: ProductId.all) } @@ -232,17 +228,3 @@ class PurchaseManager: NSObject { } } - -// MARK: - SKPaymentTransactionObserver - - -extension PurchaseManager: SKPaymentTransactionObserver { - - func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { - - } - - func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { - return true - } - -} From 74f5a128e17a1bafa0bf8a343901d8d8dd95149d Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 12 Dec 2023 13:28:36 +0100 Subject: [PATCH 19/68] feat(payments): update error handling --- IVPNClient/AppDelegate.swift | 5 + IVPNClient/Managers/PurchaseManager.swift | 113 +++++++++++++++------- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/IVPNClient/AppDelegate.swift b/IVPNClient/AppDelegate.swift index 1f698a036..3c57f09ff 100644 --- a/IVPNClient/AppDelegate.swift +++ b/IVPNClient/AppDelegate.swift @@ -94,6 +94,10 @@ class AppDelegate: UIResponder { } } + private func listenTransactionUpdates() { + PurchaseManager.shared.listenTransactionUpdates() + } + private func resetLastPingTimestamp() { UserDefaults.shared.set(0, forKey: "LastPingTimestamp") } @@ -295,6 +299,7 @@ extension AppDelegate: UIApplicationDelegate { evaluateUITests() registerUserDefaults() finishIncompletePurchases() + listenTransactionUpdates() createLogFiles() resetLastPingTimestamp() clearURLCache() diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index c0ada5a4d..a1f1a4899 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -33,6 +33,8 @@ class PurchaseManager: NSObject { return SKPaymentQueue.canMakePayments() } + var updateListenerTask: Task? = nil + private(set) var products: [Product] = [] private var apiEndpoint: String { @@ -49,6 +51,10 @@ class PurchaseManager: NSObject { // MARK: - Methods - + func listenTransactionUpdates() { + updateListenerTask = listenForTransactions() + } + func loadProducts() async throws { products = try await Product.products(for: ProductId.all) } @@ -83,25 +89,17 @@ class PurchaseManager: NSObject { return nil } - func restorePurchases(completion: @escaping (Account?, ErrorResult?) -> Void) { - Task { - for await result in Transaction.currentEntitlements { + func listenForTransactions() -> Task { + return Task { + for await result in Transaction.updates { guard case .verified(let transaction) = result else { continue } if transaction.revocationDate == nil { - self.getAccountFor(transaction: transaction) { account, error in - log(.info, message: "Purchase is restored.") - completion(account, error) - return - } + complete(transaction: transaction) { _, _ in } } } - - let error = ErrorResult(status: 500, message: "There are no purchases to restore.") - log(.error, message: "There are no purchases to restore.") - completion(nil, error) } } @@ -121,9 +119,43 @@ class PurchaseManager: NSObject { } } + func restorePurchases(completion: @escaping (Account?, ErrorResult?) -> Void) { + Task { + for await result in Transaction.currentEntitlements { + guard case .verified(let transaction) = result else { + continue + } + + if transaction.revocationDate == nil { + self.getAccountFor(transaction: transaction) { account, error in + log(.info, message: "Purchase is restored.") + completion(account, error) + return + } + } + } + + let error = ErrorResult(status: 500, message: "There are no purchases to restore.") + log(.error, message: "There are no purchases to restore.") + completion(nil, error) + } + } + + func finishTransaction(_ transaction: Transaction) { + Task { + await transaction.finish() + } + } + func complete(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + let defaultError = ErrorResult(status: 500, message: "Purchase was completed but service cannot be activated. Restart application to retry.") let endpoint = apiEndpoint - let params = purchaseParams(transaction: transaction, endpoint: endpoint) + + guard let params = purchaseParams(transaction: transaction, endpoint: endpoint) else { + completion(nil, defaultError) + return + } + let request = ApiRequestDI(method: .post, endpoint: endpoint, params: params) ApiService.shared.requestCustomError(request) { (result: ResultCustomError) in @@ -134,15 +166,19 @@ class PurchaseManager: NSObject { completion(sessionStatus.serviceStatus, nil) log(.info, message: "Purchase was successfully finished.") case .failure(let error): - let defaultErrorResult = ErrorResult(status: 500, message: "Purchase was completed but service cannot be activated. Restart application to retry.") - completion(nil, error ?? defaultErrorResult) + completion(nil, error ?? defaultError) log(.error, message: "There was an error with purchase completion: \(error?.message ?? "")") } } } func getAccountFor(transaction: Transaction, completion: @escaping (Account?, ErrorResult?) -> Void) { - let params = restorePurchaseParams() + let defaultError = ErrorResult(status: 500, message: "Purchase was restored but service cannot be activated. Restart application to retry.") + guard let params = restorePurchaseParams() else { + completion(nil, defaultError) + return + } + let request = ApiRequestDI(method: .post, endpoint: Config.apiPaymentRestore, params: params) ApiService.shared.requestCustomError(request) { (result: ResultCustomError) in @@ -153,19 +189,12 @@ class PurchaseManager: NSObject { completion(account, nil) log(.info, message: "Purchase was successfully restored.") case .failure(let error): - let defaultErrorResult = ErrorResult(status: 500, message: "Purchase was restored but service cannot be activated. Restart application to retry.") - completion(nil, error ?? defaultErrorResult) + completion(nil, error ?? defaultError) log(.error, message: "There was an error with purchase completion: \(error?.message ?? "")") } } } - func finishTransaction(_ transaction: Transaction) { - Task { - await transaction.finish() - } - } - func getProduct(id: String) -> Product? { for product in products where product.id == id { return product @@ -176,10 +205,10 @@ class PurchaseManager: NSObject { // MARK: - Private methods - - private func base64receipt() -> String { - if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { + private func base64receipt() -> String? { + if let receiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: receiptURL.path) { do { - let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) + let receiptData = try Data(contentsOf: receiptURL, options: .alwaysMapped) return receiptData.base64EncodedString(options: []) } catch { @@ -187,32 +216,43 @@ class PurchaseManager: NSObject { } } - return "" + return nil } - private func purchaseParams(transaction: Transaction, endpoint: String) -> [URLQueryItem] { + private func purchaseParams(transaction: Transaction, endpoint: String) -> [URLQueryItem]? { let productId = transaction.productID let transactionId = transaction.id.formatted() - let receipt = base64receipt() + guard let receipt = base64receipt() else { + return nil + } switch endpoint { case Config.apiPaymentInitial: + guard let tempUsername = KeyChain.tempUsername else { + return nil + } return [ - URLQueryItem(name: "account_id", value: KeyChain.tempUsername ?? ""), + URLQueryItem(name: "account_id", value: tempUsername), URLQueryItem(name: "product_id", value: productId), URLQueryItem(name: "transaction_id", value: transactionId), URLQueryItem(name: "receipt", value: receipt) ] case Config.apiPaymentAdd: + guard let sessionToken = KeyChain.sessionToken else { + return nil + } return [ - URLQueryItem(name: "session_token", value: KeyChain.sessionToken ?? ""), + URLQueryItem(name: "session_token", value: sessionToken), URLQueryItem(name: "product_id", value: productId), URLQueryItem(name: "transaction_id", value: transactionId), URLQueryItem(name: "receipt", value: receipt) ] case Config.apiPaymentAddLegacy: + guard let username = KeyChain.username else { + return nil + } return [ - URLQueryItem(name: "username", value: KeyChain.username ?? ""), + URLQueryItem(name: "username", value: username), URLQueryItem(name: "productId", value: productId), URLQueryItem(name: "transactionId", value: transactionId), URLQueryItem(name: "receiptData", value: receipt) @@ -222,8 +262,11 @@ class PurchaseManager: NSObject { } } - private func restorePurchaseParams() -> [URLQueryItem] { - let receipt = base64receipt() + private func restorePurchaseParams() -> [URLQueryItem]? { + guard let receipt = base64receipt() else { + return nil + } + return [URLQueryItem(name: "receipt", value: receipt)] } From 53eb71881e74f5e75c2a6789e46f6fb508f17119 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Tue, 12 Dec 2023 14:15:39 +0100 Subject: [PATCH 20/68] feat(payments): update PurchaseManager --- IVPNClient/AppDelegate.swift | 14 ++++- IVPNClient/Managers/PurchaseManager.swift | 54 ++++++++++--------- .../Payment/PaymentViewController.swift | 2 +- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/IVPNClient/AppDelegate.swift b/IVPNClient/AppDelegate.swift index 3c57f09ff..23f51af9f 100644 --- a/IVPNClient/AppDelegate.swift +++ b/IVPNClient/AppDelegate.swift @@ -86,7 +86,9 @@ class AppDelegate: UIResponder { } PurchaseManager.shared.completeUnfinishedTransactions { serviceStatus, _ in - guard let viewController = UIApplication.topViewController() else { return } + guard let viewController = UIApplication.topViewController() else { + return + } if let serviceStatus = serviceStatus { viewController.showSubscriptionActivatedAlert(serviceStatus: serviceStatus) @@ -95,7 +97,15 @@ class AppDelegate: UIResponder { } private func listenTransactionUpdates() { - PurchaseManager.shared.listenTransactionUpdates() + PurchaseManager.shared.listenTransactionUpdates { serviceStatus, _ in + guard let viewController = UIApplication.topViewController() else { + return + } + + if let serviceStatus = serviceStatus { + viewController.showSubscriptionActivatedAlert(serviceStatus: serviceStatus) + } + } } private func resetLastPingTimestamp() { diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index a1f1a4899..e50e6b33f 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -49,16 +49,24 @@ class PurchaseManager: NSObject { return Config.apiPaymentInitial } - // MARK: - Methods - - - func listenTransactionUpdates() { - updateListenerTask = listenForTransactions() + deinit { + updateListenerTask?.cancel() } + // MARK: - Methods - + func loadProducts() async throws { products = try await Product.products(for: ProductId.all) } + func getProduct(id: String) -> Product? { + for product in products where product.id == id { + return product + } + + return nil + } + func purchase(_ productId: String) async throws -> Transaction? { guard let product = getProduct(id: productId) else { return nil @@ -89,15 +97,17 @@ class PurchaseManager: NSObject { return nil } - func listenForTransactions() -> Task { - return Task { + func listenTransactionUpdates(completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + updateListenerTask = Task { for await result in Transaction.updates { guard case .verified(let transaction) = result else { continue } if transaction.revocationDate == nil { - complete(transaction: transaction) { _, _ in } + complete(transaction) { serviceStatus, error in + completion(serviceStatus, error) + } } } } @@ -111,7 +121,7 @@ class PurchaseManager: NSObject { } if transaction.revocationDate == nil { - complete(transaction: transaction) { serviceStatus, error in + complete(transaction) { serviceStatus, error in completion(serviceStatus, error) } } @@ -141,13 +151,7 @@ class PurchaseManager: NSObject { } } - func finishTransaction(_ transaction: Transaction) { - Task { - await transaction.finish() - } - } - - func complete(transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { + func complete(_ transaction: Transaction, completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { let defaultError = ErrorResult(status: 500, message: "Purchase was completed but service cannot be activated. Restart application to retry.") let endpoint = apiEndpoint @@ -172,7 +176,15 @@ class PurchaseManager: NSObject { } } - func getAccountFor(transaction: Transaction, completion: @escaping (Account?, ErrorResult?) -> Void) { + // MARK: - Private methods - + + private func finishTransaction(_ transaction: Transaction) { + Task { + await transaction.finish() + } + } + + private func getAccountFor(transaction: Transaction, completion: @escaping (Account?, ErrorResult?) -> Void) { let defaultError = ErrorResult(status: 500, message: "Purchase was restored but service cannot be activated. Restart application to retry.") guard let params = restorePurchaseParams() else { completion(nil, defaultError) @@ -195,16 +207,6 @@ class PurchaseManager: NSObject { } } - func getProduct(id: String) -> Product? { - for product in products where product.id == id { - return product - } - - return nil - } - - // MARK: - Private methods - - private func base64receipt() -> String? { if let receiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: receiptURL.path) { do { diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index 86cb1e1e1..a91c34655 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -215,7 +215,7 @@ class PaymentViewController: UITableViewController { } private func completePurchase(transaction: Transaction) { - PurchaseManager.shared.complete(transaction: transaction) { [weak self] serviceStatus, error in + PurchaseManager.shared.complete(transaction) { [weak self] serviceStatus, error in guard let self = self else { return } From 76700b1c6334316b5fd03e63a5eec75234ef103c Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 13 Dec 2023 13:52:12 +0100 Subject: [PATCH 21/68] refactor(payments): update PurchaseManager --- IVPNClient/Managers/PurchaseManager.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index e50e6b33f..a7681b47d 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -38,15 +38,11 @@ class PurchaseManager: NSObject { private(set) var products: [Product] = [] private var apiEndpoint: String { - if KeyChain.sessionToken != nil { - if !Application.shared.serviceStatus.isNewStyleAccount() { - return Config.apiPaymentAddLegacy - } - - return Config.apiPaymentAdd + guard let _ = KeyChain.sessionToken else { + return Config.apiPaymentInitial } - return Config.apiPaymentInitial + return Application.shared.serviceStatus.isNewStyleAccount() ? Config.apiPaymentAdd : Config.apiPaymentAddLegacy } deinit { @@ -77,18 +73,21 @@ class PurchaseManager: NSObject { switch result { case let .success(.verified(transaction)): // Successful purchase - // await transaction.finish() + log(.info, message: "Purchase \(productId): success") return transaction case .success(.unverified(_, _)): // Successful purchase but transaction/receipt can't be verified // Could be a jailbroken phone + log(.info, message: "Purchase \(productId): success, unverified") break case .pending: // Transaction waiting on SCA (Strong Customer Authentication) or // approval from Ask to Buy + log(.info, message: "Purchase \(productId): pending") break case .userCancelled: // ^^^ + log(.info, message: "Purchase \(productId): userCancelled") break @unknown default: break @@ -105,6 +104,7 @@ class PurchaseManager: NSObject { } if transaction.revocationDate == nil { + log(.info, message: "Completing updated transaction.") complete(transaction) { serviceStatus, error in completion(serviceStatus, error) } @@ -121,6 +121,7 @@ class PurchaseManager: NSObject { } if transaction.revocationDate == nil { + log(.info, message: "Completing unfinished transaction.") complete(transaction) { serviceStatus, error in completion(serviceStatus, error) } @@ -260,7 +261,7 @@ class PurchaseManager: NSObject { URLQueryItem(name: "receiptData", value: receipt) ] default: - return [] + return nil } } From c8c730a7dc69385521254652ea07db306cc3b1a4 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 14 Dec 2023 17:14:37 +0100 Subject: [PATCH 22/68] refactor(payments): update AppDelegate --- IVPNClient/AppDelegate.swift | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/IVPNClient/AppDelegate.swift b/IVPNClient/AppDelegate.swift index 23f51af9f..547c193de 100644 --- a/IVPNClient/AppDelegate.swift +++ b/IVPNClient/AppDelegate.swift @@ -80,27 +80,15 @@ class AppDelegate: UIResponder { FileSystemManager.createLogFiles() } - private func finishIncompletePurchases() { - guard Application.shared.authentication.isLoggedIn || KeyChain.tempUsername != nil else { - return - } - - PurchaseManager.shared.completeUnfinishedTransactions { serviceStatus, _ in - guard let viewController = UIApplication.topViewController() else { - return - } - - if let serviceStatus = serviceStatus { - viewController.showSubscriptionActivatedAlert(serviceStatus: serviceStatus) - } - } - } - private func listenTransactionUpdates() { - PurchaseManager.shared.listenTransactionUpdates { serviceStatus, _ in + PurchaseManager.shared.listenTransactionUpdates { serviceStatus, error in guard let viewController = UIApplication.topViewController() else { return } + + if let error = error { + viewController.showErrorAlert(title: "Error", message: error.message) + } if let serviceStatus = serviceStatus { viewController.showSubscriptionActivatedAlert(serviceStatus: serviceStatus) @@ -308,7 +296,6 @@ extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { evaluateUITests() registerUserDefaults() - finishIncompletePurchases() listenTransactionUpdates() createLogFiles() resetLastPingTimestamp() From d5bbb9a63769d70a736f0affa57105971a6da708 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Thu, 14 Dec 2023 17:19:04 +0100 Subject: [PATCH 23/68] refactor(payments): update PurchaseManager --- IVPNClient/Managers/PurchaseManager.swift | 42 +++++++---------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index a7681b47d..de1eb953c 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -73,21 +73,21 @@ class PurchaseManager: NSObject { switch result { case let .success(.verified(transaction)): // Successful purchase - log(.info, message: "Purchase \(productId): success") + log(.info, message: "[Store] Purchase \(productId): success") return transaction case .success(.unverified(_, _)): // Successful purchase but transaction/receipt can't be verified // Could be a jailbroken phone - log(.info, message: "Purchase \(productId): success, unverified") + log(.info, message: "[Store] Purchase \(productId): success, unverified") break case .pending: // Transaction waiting on SCA (Strong Customer Authentication) or // approval from Ask to Buy - log(.info, message: "Purchase \(productId): pending") + log(.info, message: "[Store] Purchase \(productId): pending") break case .userCancelled: // ^^^ - log(.info, message: "Purchase \(productId): userCancelled") + log(.info, message: "[Store] Purchase \(productId): userCancelled") break @unknown default: break @@ -104,24 +104,7 @@ class PurchaseManager: NSObject { } if transaction.revocationDate == nil { - log(.info, message: "Completing updated transaction.") - complete(transaction) { serviceStatus, error in - completion(serviceStatus, error) - } - } - } - } - } - - func completeUnfinishedTransactions(completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { - Task { - for await result in Transaction.unfinished { - guard case .verified(let transaction) = result else { - continue - } - - if transaction.revocationDate == nil { - log(.info, message: "Completing unfinished transaction.") + log(.info, message: "[Store] Completing updated transaction.") complete(transaction) { serviceStatus, error in completion(serviceStatus, error) } @@ -139,7 +122,7 @@ class PurchaseManager: NSObject { if transaction.revocationDate == nil { self.getAccountFor(transaction: transaction) { account, error in - log(.info, message: "Purchase is restored.") + log(.info, message: "[Store] Purchase is restored.") completion(account, error) return } @@ -147,7 +130,7 @@ class PurchaseManager: NSObject { } let error = ErrorResult(status: 500, message: "There are no purchases to restore.") - log(.error, message: "There are no purchases to restore.") + log(.error, message: "[Store] There are no purchases to restore.") completion(nil, error) } } @@ -169,10 +152,10 @@ class PurchaseManager: NSObject { Application.shared.serviceStatus = sessionStatus.serviceStatus self.finishTransaction(transaction) completion(sessionStatus.serviceStatus, nil) - log(.info, message: "Purchase was successfully finished.") + log(.info, message: "[Store] Purchase was successfully finished.") case .failure(let error): completion(nil, error ?? defaultError) - log(.error, message: "There was an error with purchase completion: \(error?.message ?? "")") + log(.error, message: "[Store] There was an error with purchase completion: \(error?.message ?? "")") } } } @@ -200,10 +183,10 @@ class PurchaseManager: NSObject { self.finishTransaction(transaction) KeyChain.username = account.accountId completion(account, nil) - log(.info, message: "Purchase was successfully restored.") + log(.info, message: "[Store] Purchase was successfully restored.") case .failure(let error): completion(nil, error ?? defaultError) - log(.error, message: "There was an error with purchase completion: \(error?.message ?? "")") + log(.error, message: "[Store] There was an error with purchase completion: \(error?.message ?? "")") } } } @@ -215,10 +198,11 @@ class PurchaseManager: NSObject { return receiptData.base64EncodedString(options: []) } catch { - log(.error, message: "Couldn't read receipt data with error: \(error.localizedDescription)") + log(.error, message: "[Store] Couldn't read receipt with error: \(error.localizedDescription)") } } + log(.error, message: "[Store] Couldn't read receipt") return nil } From cccd23c5816fd3df1af05ec83ae388c25c86b969 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Fri, 15 Dec 2023 10:48:13 +0100 Subject: [PATCH 24/68] refactor(payments): update PaymentViewController --- IVPNClient/Managers/PurchaseManager.swift | 8 ++++---- .../Payment/PaymentViewController.swift | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/IVPNClient/Managers/PurchaseManager.swift b/IVPNClient/Managers/PurchaseManager.swift index de1eb953c..9c87b0569 100644 --- a/IVPNClient/Managers/PurchaseManager.swift +++ b/IVPNClient/Managers/PurchaseManager.swift @@ -63,7 +63,7 @@ class PurchaseManager: NSObject { return nil } - func purchase(_ productId: String) async throws -> Transaction? { + func purchase(_ productId: String) async throws -> Product.PurchaseResult? { guard let product = getProduct(id: productId) else { return nil } @@ -71,10 +71,10 @@ class PurchaseManager: NSObject { let result = try await product.purchase() switch result { - case let .success(.verified(transaction)): + case .success(.verified(_)): // Successful purchase log(.info, message: "[Store] Purchase \(productId): success") - return transaction + break case .success(.unverified(_, _)): // Successful purchase but transaction/receipt can't be verified // Could be a jailbroken phone @@ -93,7 +93,7 @@ class PurchaseManager: NSObject { break } - return nil + return result } func listenTransactionUpdates(completion: @escaping (ServiceStatus?, ErrorResult?) -> Void) { diff --git a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift index a91c34655..787bf4ccb 100644 --- a/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift +++ b/IVPNClient/Scenes/Signup/Payment/PaymentViewController.swift @@ -205,9 +205,24 @@ class PaymentViewController: UITableViewController { hud.show(in: (navigationController?.view)!) do { - if let transaction = try await PurchaseManager.shared.purchase(identifier) { - completePurchase(transaction: transaction) + if let result = try await PurchaseManager.shared.purchase(identifier) { + switch result { + case let .success(.verified(transaction)): + completePurchase(transaction: transaction) + return + case .success(.unverified(_, _)): + showErrorAlert(title: "Error", message: "Payment failed verification checks") + case .pending: + showAlert(title: "Pending payment", message: "Payment is pending for approval. We will complete the transaction as soon as payment is approved.") + return + case .userCancelled: + break + @unknown default: + break + } } + + hud.dismiss() } catch { showErrorAlert(title: "Error", message: error.localizedDescription) hud.dismiss() From 2945b8e5adace4f7738271829bc36f72999e7f9b Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Fri, 15 Dec 2023 13:05:23 +0100 Subject: [PATCH 25/68] refactor(payments): create Store.storekit --- IVPNClient.xcodeproj/project.pbxproj | 4 + .../xcschemes/IVPNClient.xcscheme | 3 + IVPNClient/Store.storekit | 385 ++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 IVPNClient/Store.storekit diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index 6a02d59e1..156e92985 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ 824BC466240906ED00A61B29 /* VPNStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824BC465240906ED00A61B29 /* VPNStatusViewModel.swift */; }; 82526BEF24123D2900E00880 /* NetworkViewTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82526BEE24123D2900E00880 /* NetworkViewTableCell.swift */; }; 8252747E21F1F80400D4B8B5 /* ServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8252747D21F1F80400D4B8B5 /* ServerViewController.swift */; }; + 825443982B2A1B8F00D77095 /* Store.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 825443972B2A1B8F00D77095 /* Store.storekit */; }; 82555005220ACAAF004763A7 /* VPNServersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82555004220ACAAF004763A7 /* VPNServersTests.swift */; }; 82589A2B21FB5A580009CC6C /* UIImage+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82589A2A21FB5A580009CC6C /* UIImage+Ext.swift */; }; 825E834F25A327EB00938240 /* CaptchaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825E834E25A327EB00938240 /* CaptchaViewController.swift */; }; @@ -549,6 +550,7 @@ 824F56072233FE6F00BCDD5C /* libwg-go.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 82526BEE24123D2900E00880 /* NetworkViewTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkViewTableCell.swift; sourceTree = ""; }; 8252747D21F1F80400D4B8B5 /* ServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerViewController.swift; sourceTree = ""; }; + 825443972B2A1B8F00D77095 /* Store.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Store.storekit; sourceTree = ""; }; 82555004220ACAAF004763A7 /* VPNServersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNServersTests.swift; sourceTree = ""; }; 8258649C2237A0830081DC4B /* SDCAlertView.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDCAlertView.framework; path = ../Carthage/Build/iOS/SDCAlertView.framework; sourceTree = ""; }; 825864A02237B1060081DC4B /* Bamboo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Bamboo.framework; path = ../Carthage/Build/iOS/Bamboo.framework; sourceTree = ""; }; @@ -1657,6 +1659,7 @@ 82FF0D4123153D1000440E5D /* Colors.xcassets */, 9CB2CE261DAA6C1B007A4D2D /* IVPNClient.entitlements */, 9CB2CE321DAF9283007A4D2D /* Model.xcdatamodeld */, + 825443972B2A1B8F00D77095 /* Store.storekit */, ); path = IVPNClient; sourceTree = ""; @@ -1996,6 +1999,7 @@ 9CDDD5B41D9D2F9F00D39924 /* Main.storyboard in Resources */, 9C2833741D9D3EB60024C553 /* Initial.storyboard in Resources */, 826470C42446F67100403A14 /* Signup.storyboard in Resources */, + 825443982B2A1B8F00D77095 /* Store.storekit in Resources */, 82FF0D4223153D1000440E5D /* Colors.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/IVPNClient.xcodeproj/xcshareddata/xcschemes/IVPNClient.xcscheme b/IVPNClient.xcodeproj/xcshareddata/xcschemes/IVPNClient.xcscheme index b86e17588..2d24c5fc7 100644 --- a/IVPNClient.xcodeproj/xcshareddata/xcschemes/IVPNClient.xcscheme +++ b/IVPNClient.xcodeproj/xcshareddata/xcschemes/IVPNClient.xcscheme @@ -109,6 +109,9 @@ isEnabled = "YES"> + + Date: Mon, 4 Dec 2023 12:09:34 +0100 Subject: [PATCH 26/68] refactor(payments): set DEPLOYMENT_TARGET to 15.0 --- IVPNClient.xcodeproj/project.pbxproj | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index 386cb68ec..a76f6f36c 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -2525,7 +2525,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UnitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2556,7 +2556,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UnitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2592,7 +2592,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "wireguard-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2632,7 +2632,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "wireguard-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2664,7 +2664,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2693,7 +2693,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2721,7 +2721,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2788,7 +2788,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ""; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -2817,7 +2817,7 @@ ); HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "$(SRCROOT)/IVPNClient/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2853,7 +2853,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = UnitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2899,7 +2899,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "openvpn-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2942,7 +2942,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "wireguard-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -2981,7 +2981,7 @@ INFOPLIST_FILE = IVPNWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = IVPNWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 IVPN. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -3016,7 +3016,7 @@ INFOPLIST_FILE = IVPNWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = IVPNWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 IVPN. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; @@ -3050,7 +3050,7 @@ INFOPLIST_FILE = IVPNWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = IVPNWidget; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 IVPN. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; @@ -3094,7 +3094,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "openvpn-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3146,7 +3146,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "openvpn-tunnel-provider/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3220,7 +3220,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ""; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -3277,7 +3277,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ""; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; @@ -3306,7 +3306,7 @@ ); HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "$(SRCROOT)/IVPNClient/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3345,7 +3345,7 @@ ); HEADER_SEARCH_PATHS = "\"$(SRCROOT)/IVPNClient/liboqs/include\""; INFOPLIST_FILE = "$(SRCROOT)/IVPNClient/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From 09b1824225a3cc6895cd1301b4586daecf892e61 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 13 Dec 2023 16:26:53 +0100 Subject: [PATCH 27/68] refactor: update NETunnelProviderProtocol+Ext.swift --- .../NETunnelProviderProtocol+Ext.swift | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift index 7c87819db..37cfdf00e 100644 --- a/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift +++ b/IVPNClient/Utilities/Extensions/NETunnelProviderProtocol+Ext.swift @@ -83,16 +83,12 @@ extension NETunnelProviderProtocol { ) proto.disconnectOnSleep = !UserDefaults.shared.keepAlive - if #available(iOS 15.1, *) { - if #available(iOS 16, *) { } else { - proto.includeAllNetworks = UserDefaults.shared.killSwitch - } + if #available(iOS 16, *) { } else { + proto.includeAllNetworks = UserDefaults.shared.killSwitch } - if #available(iOS 14.2, *) { - proto.includeAllNetworks = disableLanAccess() - proto.excludeLocalNetworks = !disableLanAccess() - } + proto.includeAllNetworks = disableLanAccess() + proto.excludeLocalNetworks = !disableLanAccess() return proto } @@ -183,16 +179,12 @@ extension NETunnelProviderProtocol { configuration.providerConfiguration = tunnel.generateProviderConfiguration() configuration.disconnectOnSleep = !UserDefaults.shared.keepAlive - if #available(iOS 15.1, *) { - if #available(iOS 16, *) { } else { - configuration.includeAllNetworks = UserDefaults.shared.killSwitch - } - } - - if #available(iOS 14.2, *) { - configuration.includeAllNetworks = disableLanAccess() - configuration.excludeLocalNetworks = !disableLanAccess() + if #available(iOS 16, *) { } else { + configuration.includeAllNetworks = UserDefaults.shared.killSwitch } + + configuration.includeAllNetworks = disableLanAccess() + configuration.excludeLocalNetworks = !disableLanAccess() return configuration } From 065d147471338d7c9376b3416716cff7deba6e04 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 13 Dec 2023 16:29:56 +0100 Subject: [PATCH 28/68] refactor: update VPNManager.swift --- IVPNClient/Managers/VPNManager.swift | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/IVPNClient/Managers/VPNManager.swift b/IVPNClient/Managers/VPNManager.swift index c2cfc37cd..98bcf7fb5 100644 --- a/IVPNClient/Managers/VPNManager.swift +++ b/IVPNClient/Managers/VPNManager.swift @@ -131,10 +131,8 @@ class VPNManager { manager.saveToPreferences { error in if let error = error, error.code == 5 { manager.isOnDemandEnabled = false - if #available(iOS 15.1, *) { - if #available(iOS 16, *) { } else { - manager.protocolConfiguration?.includeAllNetworks = false - } + if #available(iOS 16, *) { } else { + manager.protocolConfiguration?.includeAllNetworks = false } NotificationCenter.default.post(name: Notification.Name.VPNConfigurationDisabled, object: nil) return @@ -272,10 +270,8 @@ class VPNManager { manager.loadFromPreferences { _ in manager.onDemandRules = [NEOnDemandRule]() manager.isOnDemandEnabled = false - if #available(iOS 15.1, *) { - if #available(iOS 16, *) { } else { - manager.protocolConfiguration?.includeAllNetworks = false - } + if #available(iOS 16, *) { } else { + manager.protocolConfiguration?.includeAllNetworks = false } manager.saveToPreferences { _ in } } @@ -327,10 +323,8 @@ class VPNManager { manager.loadFromPreferences { _ in manager.onDemandRules = [NEOnDemandRule]() manager.isOnDemandEnabled = false - if #available(iOS 15.1, *) { - if #available(iOS 16, *) { } else { - manager.protocolConfiguration?.includeAllNetworks = false - } + if #available(iOS 16, *) { } else { + manager.protocolConfiguration?.includeAllNetworks = false } manager.saveToPreferences(completionHandler: completion) } From 90ff1045b185c31ba419a070aa22b05a5f255a88 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 13 Dec 2023 16:30:06 +0100 Subject: [PATCH 29/68] refactor: update SettingsViewController.swift --- .../Scenes/ViewControllers/SettingsViewController.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/IVPNClient/Scenes/ViewControllers/SettingsViewController.swift b/IVPNClient/Scenes/ViewControllers/SettingsViewController.swift index 3a910a421..7205075ed 100644 --- a/IVPNClient/Scenes/ViewControllers/SettingsViewController.swift +++ b/IVPNClient/Scenes/ViewControllers/SettingsViewController.swift @@ -359,11 +359,10 @@ extension SettingsViewController { // Kill Switch if indexPath.section == 3 && indexPath.row == 4 { - if #available(iOS 15.1, *) { - if #available(iOS 16, *) { } else { - return UITableView.automaticDimension - } + if #available(iOS 16, *) { } else { + return UITableView.automaticDimension } + return 0 } From 52a7428e3ee67fadabb2a6365834d2f27ef87038 Mon Sep 17 00:00:00 2001 From: Juraj Hilje Date: Wed, 13 Dec 2023 16:56:10 +0100 Subject: [PATCH 30/68] refactor: remove StaticWebViewController.swift --- IVPNClient.xcodeproj/project.pbxproj | 4 -- IVPNClient/Managers/NavigationManager.swift | 9 ---- IVPNClient/Scenes/Base.lproj/Main.storyboard | 54 ++++--------------- .../StaticWebViewController.swift | 49 ----------------- 4 files changed, 10 insertions(+), 106 deletions(-) delete mode 100644 IVPNClient/Scenes/ViewControllers/StaticWebViewController.swift diff --git a/IVPNClient.xcodeproj/project.pbxproj b/IVPNClient.xcodeproj/project.pbxproj index a76f6f36c..883d27e6f 100644 --- a/IVPNClient.xcodeproj/project.pbxproj +++ b/IVPNClient.xcodeproj/project.pbxproj @@ -194,7 +194,6 @@ 82A6D74A24A3780B00D6C0E1 /* ConnectToServerPopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A6D74924A3780B00D6C0E1 /* ConnectToServerPopupView.swift */; }; 82A7F10523C8661B0015A357 /* ServiceStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A7F10423C8661B0015A357 /* ServiceStatusTests.swift */; }; 82AA8818231E330A00E18ECB /* SessionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AA8817231E330A00E18ECB /* SessionStatus.swift */; }; - 82AAF0E92253A4A8005E792F /* StaticWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AAF0E82253A4A8005E792F /* StaticWebViewController.swift */; }; 82AB0875291A6B5F0084625A /* AddCustomPortViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AB0874291A6B5F0084625A /* AddCustomPortViewController.swift */; }; 82AB0877291A6B9C0084625A /* CustomPort+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AB0876291A6B9C0084625A /* CustomPort+CoreDataClass.swift */; }; 82AB0879291A6BB90084625A /* CustomPort+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AB0878291A6BB90084625A /* CustomPort+CoreDataProperties.swift */; }; @@ -618,7 +617,6 @@ 82A9E8C323471EBE007BCA7E /* release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = release.xcconfig; sourceTree = ""; }; 82A9E8C423471EBE007BCA7E /* staging.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = staging.xcconfig; sourceTree = ""; }; 82AA8817231E330A00E18ECB /* SessionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionStatus.swift; sourceTree = ""; }; - 82AAF0E82253A4A8005E792F /* StaticWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticWebViewController.swift; sourceTree = ""; }; 82AB0874291A6B5F0084625A /* AddCustomPortViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCustomPortViewController.swift; sourceTree = ""; }; 82AB0876291A6B9C0084625A /* CustomPort+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomPort+CoreDataClass.swift"; sourceTree = ""; }; 82AB0878291A6BB90084625A /* CustomPort+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomPort+CoreDataProperties.swift"; sourceTree = ""; }; @@ -1260,7 +1258,6 @@ 8252747D21F1F80400D4B8B5 /* ServerViewController.swift */, 828772F8221C01C300D5E330 /* ServersConfigurationTableViewController.swift */, 8221377A2227E75E001E1BF5 /* CustomDNSViewController.swift */, - 82AAF0E82253A4A8005E792F /* StaticWebViewController.swift */, 8269CAC22264962F00CF488A /* AntiTrackerViewController.swift */, 82F917382344861A0025ED3A /* TermsOfServiceViewController.swift */, 8201A5032356536B008C83DB /* UpgradePlanViewController.swift */, @@ -2319,7 +2316,6 @@ 823FFB072338DF1800F91A5D /* Capability.swift in Sources */, 82061F65238D2730009DDF4D /* Ping.swift in Sources */, 82AB0879291A6BB90084625A /* CustomPort+CoreDataProperties.swift in Sources */, - 82AAF0E92253A4A8005E792F /* StaticWebViewController.swift in Sources */, 8282482A225C7312001314F8 /* WireGuardRegenerationRateCell.swift in Sources */, 824777EA21A6BC3A001EEFAF /* Network+CoreDataProperties.swift in Sources */, 82C34D6E26FB02F900F06016 /* WireGuardEndpoint.swift in Sources */, diff --git a/IVPNClient/Managers/NavigationManager.swift b/IVPNClient/Managers/NavigationManager.swift index f0ac8fdab..4dbf82188 100644 --- a/IVPNClient/Managers/NavigationManager.swift +++ b/IVPNClient/Managers/NavigationManager.swift @@ -82,15 +82,6 @@ class NavigationManager { return navController! } - static func getStaticWebViewController(resourceName: String, screenTitle: String) -> UIViewController { - let storyBoard = UIStoryboard(name: "Main", bundle: nil) - let viewController = storyBoard.instantiateViewController(withIdentifier: "staticWebView") as! StaticWebViewController - viewController.resourceName = resourceName - viewController.screenTitle = screenTitle - - return viewController - } - static func getTermsOfServiceViewController() -> UIViewController { let storyBoard = UIStoryboard(name: "Initial", bundle: nil) diff --git a/IVPNClient/Scenes/Base.lproj/Main.storyboard b/IVPNClient/Scenes/Base.lproj/Main.storyboard index 4f04e5a7c..8e8ee6b8c 100644 --- a/IVPNClient/Scenes/Base.lproj/Main.storyboard +++ b/IVPNClient/Scenes/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - +