diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-10-31 11:06:46 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-10-31 11:06:46 +0100 |
| commit | 0aa84c5c41a489507560ff2cac3c9ac015b024e8 (patch) | |
| tree | 7b9db45e882bebc355a21ee987ec62b9fcc011af | |
| parent | 96526fe223025af0fcb4cc9092e963c5b87f5f20 (diff) | |
| parent | 295b3236d91449a60444449612fead8ae0ab5434 (diff) | |
| download | mullvadvpn-0aa84c5c41a489507560ff2cac3c9ac015b024e8.tar.xz mullvadvpn-0aa84c5c41a489507560ff2cac3c9ac015b024e8.zip | |
Merge branch 'refactor-store-payments'
17 files changed, 374 insertions, 316 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index bf46a635ec..dc134a6eb4 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -116,9 +116,9 @@ 58421032282E42B000F24E46 /* UpdateDeviceDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */; }; 58421034282E4B1500F24E46 /* TunnelSettingsV2+REST.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58421033282E4B1500F24E46 /* TunnelSettingsV2+REST.swift */; }; 584592612639B4A200EF967F /* TermsOfServiceContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584592602639B4A200EF967F /* TermsOfServiceContentView.swift */; }; - 5846227126E229F20035F7C2 /* AppStoreSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227026E229F20035F7C2 /* AppStoreSubscription.swift */; }; - 5846227326E22A160035F7C2 /* AppStorePaymentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227226E22A160035F7C2 /* AppStorePaymentObserver.swift */; }; - 5846227726E22A7C0035F7C2 /* AppStorePaymentManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227626E22A7C0035F7C2 /* AppStorePaymentManagerDelegate.swift */; }; + 5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227026E229F20035F7C2 /* StoreSubscription.swift */; }; + 5846227326E22A160035F7C2 /* StorePaymentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227226E22A160035F7C2 /* StorePaymentObserver.swift */; }; + 5846227726E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227626E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift */; }; 584B17AB27637DE40057F3B8 /* ReconnectTunnelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584B17AA27637DE40057F3B8 /* ReconnectTunnelOperation.swift */; }; 584D26C2270C8542004EA533 /* SettingsStaticTextFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584D26C1270C8542004EA533 /* SettingsStaticTextFooterView.swift */; }; 584D26C4270C855B004EA533 /* PreferencesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584D26C3270C855A004EA533 /* PreferencesDataSource.swift */; }; @@ -133,7 +133,7 @@ 585B4B8726D9098900555C4C /* TunnelStatusNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */; }; 585C6F4C28F80745005196BE /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 585C6F4B28F80745005196BE /* Logging */; }; 585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585CA70E25F8C44600B47C62 /* UIMetrics.swift */; }; - 585E820327F3285E00939F0E /* SendAppStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendAppStoreReceiptOperation.swift */; }; + 585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; }; 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5868585524054096000B8131 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868585424054096000B8131 /* AppButton.swift */; }; 5868BD33261DCD2600E6027F /* CustomSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868BD32261DCD2600E6027F /* CustomSplitViewController.swift */; }; @@ -155,6 +155,8 @@ 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587425C02299833500CA2045 /* RootContainerViewController.swift */; }; 5875960A26F371FC00BF6711 /* Tunnel+Messaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */; }; 5877D70F282137E8002FCFC7 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; + 5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27429093A310096FC88 /* StorePaymentEvent.swift */; }; + 5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */; }; 587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */; }; 587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */; }; 587AD7C623421D7000E93A53 /* TunnelSettingsV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */; }; @@ -263,7 +265,7 @@ 58D889C828DDF53500583FA8 /* ResultOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F7D26427EB50A300E4D821 /* ResultOperation.swift */; }; 58D889C928DDF53500583FA8 /* InputOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B732851FF3F00E92647 /* InputOperation.swift */; }; 58D889CA28DDF53500583FA8 /* ResultOperation+Output.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58059DDF2846823E002B1049 /* ResultOperation+Output.swift */; }; - 58DF28A52417CB4B00E836B0 /* AppStorePaymentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */; }; + 58DF28A52417CB4B00E836B0 /* StorePaymentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF28A42417CB4B00E836B0 /* StorePaymentManager.swift */; }; 58E0729D28814AAE008902F8 /* PacketTunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0729C28814AAE008902F8 /* PacketTunnelConfiguration.swift */; }; 58E0729F28814ACC008902F8 /* WireGuardLogLevel+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0729E28814ACC008902F8 /* WireGuardLogLevel+Logging.swift */; }; 58E072A128814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E072A028814B0E008902F8 /* MullvadEndpoint+WgEndpoint.swift */; }; @@ -286,10 +288,10 @@ 58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F3C0A3249CB069003E76BE /* HeaderBarView.swift */; }; 58F7CA882692E34000FC59FD /* WireguardKeysContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */; }; 58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */; }; - 58FB865526E8BF3100F188BC /* AppStorePaymentManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865426E8BF3100F188BC /* AppStorePaymentManagerError.swift */; }; + 58FB865526E8BF3100F188BC /* StorePaymentManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */; }; 58FB865A26EA214400F188BC /* RelayCacheTrackerObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */; }; 58FC040A27B3EE03001C21F0 /* TunnelMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */; }; - 58FD5BE724192A2C00112C88 /* AppStoreReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BE624192A2B00112C88 /* AppStoreReceipt.swift */; }; + 58FD5BE724192A2C00112C88 /* StoreReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BE624192A2B00112C88 /* StoreReceipt.swift */; }; 58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; }; 58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */; }; 58FEEB46260A028D00A621A8 /* GeoJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB45260A028D00A621A8 /* GeoJSON.swift */; }; @@ -593,9 +595,9 @@ 58421033282E4B1500F24E46 /* TunnelSettingsV2+REST.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelSettingsV2+REST.swift"; sourceTree = "<group>"; }; 584592602639B4A200EF967F /* TermsOfServiceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServiceContentView.swift; sourceTree = "<group>"; }; 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsRequestOperation.swift; sourceTree = "<group>"; }; - 5846227026E229F20035F7C2 /* AppStoreSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreSubscription.swift; sourceTree = "<group>"; }; - 5846227226E22A160035F7C2 /* AppStorePaymentObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentObserver.swift; sourceTree = "<group>"; }; - 5846227626E22A7C0035F7C2 /* AppStorePaymentManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentManagerDelegate.swift; sourceTree = "<group>"; }; + 5846227026E229F20035F7C2 /* StoreSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreSubscription.swift; sourceTree = "<group>"; }; + 5846227226E22A160035F7C2 /* StorePaymentObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentObserver.swift; sourceTree = "<group>"; }; + 5846227626E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentManagerDelegate.swift; sourceTree = "<group>"; }; 584B17AA27637DE40057F3B8 /* ReconnectTunnelOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReconnectTunnelOperation.swift; sourceTree = "<group>"; }; 584B26F3237434D00073B10E /* RelaySelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorTests.swift; sourceTree = "<group>"; }; 584D26BE270C550B004EA533 /* AnyIPAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyIPAddress.swift; sourceTree = "<group>"; }; @@ -610,7 +612,7 @@ 585DA87626B024A600B8C587 /* CachedRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRelays.swift; sourceTree = "<group>"; }; 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProviderMessage.swift; sourceTree = "<group>"; }; 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelStatus.swift; sourceTree = "<group>"; }; - 585E820227F3285E00939F0E /* SendAppStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendAppStoreReceiptOperation.swift; sourceTree = "<group>"; }; + 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendStoreReceiptOperation.swift; sourceTree = "<group>"; }; 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; }; 5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MullvadVPN.entitlements; sourceTree = "<group>"; }; 5868585424054096000B8131 /* AppButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppButton.swift; sourceTree = "<group>"; }; @@ -628,6 +630,8 @@ 5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Messaging.swift"; sourceTree = "<group>"; }; 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraints.swift; sourceTree = "<group>"; }; 58781CD422AFBA39009B9D8E /* RelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; }; + 5878A27429093A310096FC88 /* StorePaymentEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentEvent.swift; sourceTree = "<group>"; }; + 5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentBlockObserver.swift; sourceTree = "<group>"; }; 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDataThrottling.swift; sourceTree = "<group>"; }; 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderHost.swift; sourceTree = "<group>"; }; 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV1.swift; sourceTree = "<group>"; }; @@ -716,7 +720,7 @@ 58D0C79D23F1CEBA00FE9BA7 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = "<group>"; }; 58D0C79F23F1CECF00FE9BA7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MullvadVPNScreenshots.swift; sourceTree = "<group>"; }; - 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentManager.swift; sourceTree = "<group>"; }; + 58DF28A42417CB4B00E836B0 /* StorePaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentManager.swift; sourceTree = "<group>"; }; 58DF5B732851FF3F00E92647 /* InputOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputOperation.swift; sourceTree = "<group>"; }; 58DF5B752852108E00E92647 /* InputInjectionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputInjectionBuilder.swift; sourceTree = "<group>"; }; 58DF5B772852178600E92647 /* OperationInputInjectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationInputInjectionTests.swift; sourceTree = "<group>"; }; @@ -752,10 +756,10 @@ 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardKeysContentView.swift; sourceTree = "<group>"; }; 58F7D26427EB50A300E4D821 /* ResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultOperation.swift; sourceTree = "<group>"; }; 58F8AC0D25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportReviewViewController.swift; sourceTree = "<group>"; }; - 58FB865426E8BF3100F188BC /* AppStorePaymentManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentManagerError.swift; sourceTree = "<group>"; }; + 58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentManagerError.swift; sourceTree = "<group>"; }; 58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTrackerObserver.swift; sourceTree = "<group>"; }; 58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitor.swift; sourceTree = "<group>"; }; - 58FD5BE624192A2B00112C88 /* AppStoreReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreReceipt.swift; sourceTree = "<group>"; }; + 58FD5BE624192A2B00112C88 /* StoreReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreReceipt.swift; sourceTree = "<group>"; }; 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Formatting.swift"; sourceTree = "<group>"; }; 58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseButton.swift; sourceTree = "<group>"; }; 58FEEB45260A028D00A621A8 /* GeoJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoJSON.swift; sourceTree = "<group>"; }; @@ -1036,17 +1040,20 @@ path = MullvadVPN; sourceTree = "<group>"; }; - 5846226F26E229CD0035F7C2 /* AppStorePaymentManager */ = { + 5846226F26E229CD0035F7C2 /* StorePaymentManager */ = { isa = PBXGroup; children = ( - 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */, - 5846227626E22A7C0035F7C2 /* AppStorePaymentManagerDelegate.swift */, - 58FB865426E8BF3100F188BC /* AppStorePaymentManagerError.swift */, - 5846227226E22A160035F7C2 /* AppStorePaymentObserver.swift */, - 5846227026E229F20035F7C2 /* AppStoreSubscription.swift */, - 585E820227F3285E00939F0E /* SendAppStoreReceiptOperation.swift */, + 5878A27629093A4F0096FC88 /* StorePaymentBlockObserver.swift */, + 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */, + 5878A27429093A310096FC88 /* StorePaymentEvent.swift */, + 58DF28A42417CB4B00E836B0 /* StorePaymentManager.swift */, + 5846227626E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift */, + 58FB865426E8BF3100F188BC /* StorePaymentManagerError.swift */, + 5846227226E22A160035F7C2 /* StorePaymentObserver.swift */, + 58FD5BE624192A2B00112C88 /* StoreReceipt.swift */, + 5846227026E229F20035F7C2 /* StoreSubscription.swift */, ); - path = AppStorePaymentManager; + path = StorePaymentManager; sourceTree = "<group>"; }; 584F991F2902CBDD001F858D /* Frameworks */ = { @@ -1201,8 +1208,7 @@ 58CCA01722426713004F3011 /* AccountViewController.swift */, 5868585424054096000B8131 /* AppButton.swift */, 58CE5E63224146200008646E /* AppDelegate.swift */, - 5846226F26E229CD0035F7C2 /* AppStorePaymentManager */, - 58FD5BE624192A2B00112C88 /* AppStoreReceipt.swift */, + 5846226F26E229CD0035F7C2 /* StorePaymentManager */, 58CE5E6A224146210008646E /* Assets.xcassets */, 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */, 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */, @@ -2029,7 +2035,7 @@ 587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */, 589A454C28DDF5E100565204 /* Swizzle.swift in Sources */, 5857F24724C882D700CF6F47 /* SelectLocationNavigationController.swift in Sources */, - 5846227126E229F20035F7C2 /* AppStoreSubscription.swift in Sources */, + 5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */, 58421030282D8A3C00F24E46 /* UpdateAccountDataOperation.swift in Sources */, 58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */, 587EB672271451E300123C75 /* PreferencesViewModel.swift in Sources */, @@ -2039,6 +2045,7 @@ 58BA693123EADA6A009DC256 /* SimulatorTunnelProvider.swift in Sources */, 587B753B2666467500DEF7E9 /* NotificationBannerView.swift in Sources */, 58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */, + 5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */, 58E6771F24ADFE7800AA26E7 /* SettingsNavigationController.swift in Sources */, 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */, 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */, @@ -2065,14 +2072,14 @@ 58F2E14C276A61C000A79513 /* RotateKeyOperation.swift in Sources */, 5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */, 58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */, - 5846227326E22A160035F7C2 /* AppStorePaymentObserver.swift in Sources */, + 5846227326E22A160035F7C2 /* StorePaymentObserver.swift in Sources */, 58F2E146276A2C9900A79513 /* StopTunnelOperation.swift in Sources */, E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */, 58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */, 582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */, 5820EDAB288FF0D2006BF4E4 /* DeviceRowView.swift in Sources */, 75FD0C2128B108570021E33E /* ShortcutsDataSource.swift in Sources */, - 5846227726E22A7C0035F7C2 /* AppStorePaymentManagerDelegate.swift in Sources */, + 5846227726E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift in Sources */, 58EF581125D69DB400AEBA94 /* StatusImageView.swift in Sources */, 58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */, 58EE2E3B272FF814003BFF93 /* SettingsDataSourceDelegate.swift in Sources */, @@ -2092,14 +2099,15 @@ 75FD0C2528B117D30021E33E /* ShortcutsViewController.swift in Sources */, 5871FBA0254C26C00051A0A4 /* NSRegularExpression+IPAddress.swift in Sources */, 58F7CA882692E34000FC59FD /* WireguardKeysContentView.swift in Sources */, + 5878A27729093A4F0096FC88 /* StorePaymentBlockObserver.swift in Sources */, 5868585524054096000B8131 /* AppButton.swift in Sources */, 58E25F812837BBBB002CFB2C /* SceneDelegate.swift in Sources */, - 585E820327F3285E00939F0E /* SendAppStoreReceiptOperation.swift in Sources */, + 585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */, 584B17AB27637DE40057F3B8 /* ReconnectTunnelOperation.swift in Sources */, 5820676426E771DB00655B05 /* TunnelManagerErrors.swift in Sources */, 585B4B8726D9098900555C4C /* TunnelStatusNotificationProvider.swift in Sources */, 063F026628FFE11C001FA09F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */, - 58DF28A52417CB4B00E836B0 /* AppStorePaymentManager.swift in Sources */, + 58DF28A52417CB4B00E836B0 /* StorePaymentManager.swift in Sources */, 583DA21425FA4B5C00318683 /* LocationDataSource.swift in Sources */, 587EB6742714520600123C75 /* PreferencesDataSourceDelegate.swift in Sources */, 582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */, @@ -2113,7 +2121,7 @@ 5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */, 58CE5E66224146200008646E /* LoginViewController.swift in Sources */, 58EF580B25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift in Sources */, - 58FD5BE724192A2C00112C88 /* AppStoreReceipt.swift in Sources */, + 58FD5BE724192A2C00112C88 /* StoreReceipt.swift in Sources */, 5892A45E265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift in Sources */, 580909D32876D09A0078138D /* RevokedDeviceViewController.swift in Sources */, 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */, @@ -2131,7 +2139,7 @@ 5872631D283F755900E14ADF /* IntentHandlers.swift in Sources */, 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */, 580F8B8628197958002E0998 /* DNSSettings.swift in Sources */, - 58FB865526E8BF3100F188BC /* AppStorePaymentManagerError.swift in Sources */, + 58FB865526E8BF3100F188BC /* StorePaymentManagerError.swift in Sources */, 58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */, 587D9676288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift in Sources */, 58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */, diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index fdf1d271d0..11862d8f9a 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -16,7 +16,7 @@ protocol AccountViewControllerDelegate: AnyObject { func accountViewControllerDidLogout(_ controller: AccountViewController) } -class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelObserver { +class AccountViewController: UIViewController, StorePaymentObserver, TunnelObserver { private let alertPresenter = AlertPresenter() private let contentView: AccountContentView = { @@ -83,13 +83,13 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb ) contentView.logoutButton.addTarget(self, action: #selector(doLogout), for: .touchUpInside) - AppStorePaymentManager.shared.addPaymentObserver(self) + StorePaymentManager.shared.addPaymentObserver(self) TunnelManager.shared.addObserver(self) updateView(from: TunnelManager.shared.deviceState) applyViewState(animated: false) - if AppStorePaymentManager.canMakePayments { + if StorePaymentManager.canMakePayments { requestStoreProducts() } else { setProductState(.cannotMakePurchases, animated: false) @@ -99,11 +99,11 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb // MARK: - Private methods private func requestStoreProducts() { - let productKind = AppStoreSubscription.thirtyDays + let productKind = StoreSubscription.thirtyDays setProductState(.fetching(productKind), animated: true) - _ = AppStorePaymentManager.shared + _ = StorePaymentManager.shared .requestProducts(with: [productKind]) { [weak self] completion in let productState: ProductState = completion.value?.products.first .map { .received($0) } ?? .failed @@ -158,14 +158,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb navigationItem.setHidesBackButton(!isInteractionEnabled, animated: animated) } - private func didProcessPayment(_ payment: SKPayment) { - guard case let .makingPayment(pendingPayment) = paymentState, - pendingPayment == payment else { return } - - setPaymentState(.none, animated: true) - } - - private func showPaymentErrorAlert(error: AppStorePaymentManager.Error) { + private func showPaymentErrorAlert(error: StorePaymentManagerError) { let alertController = UIAlertController( title: NSLocalizedString( "CANNOT_COMPLETE_PURCHASE_ALERT_TITLE", @@ -191,7 +184,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb alertPresenter.enqueue(alertController, presentingController: self) } - private func showRestorePurchasesErrorAlert(error: AppStorePaymentManager.Error) { + private func showRestorePurchasesErrorAlert(error: StorePaymentManagerError) { let alertController = UIAlertController( title: NSLocalizedString( "RESTORE_PURCHASES_FAILURE_ALERT_TITLE", @@ -337,35 +330,32 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb updateView(from: deviceState) } - // MARK: - AppStorePaymentObserver + // MARK: - StorePaymentObserver - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction?, - payment: SKPayment, - accountToken: String?, - didFailWithError error: AppStorePaymentManager.Error + func storePaymentManager( + _ manager: StorePaymentManager, + didReceiveEvent event: StorePaymentEvent ) { - switch error { - case .storePayment(SKError.paymentCancelled): - break + guard case let .makingPayment(payment) = paymentState, + payment == event.payment else { return } - default: - showPaymentErrorAlert(error: error) - } - - didProcessPayment(payment) - } + switch event { + case let .finished(paymentCompletion): + showTimeAddedConfirmationAlert( + with: paymentCompletion.serverResponse, + context: .purchase + ) - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction, - accountToken: String, - didFinishWithResponse response: REST.CreateApplePaymentResponse - ) { - showTimeAddedConfirmationAlert(with: response, context: .purchase) + case let .failure(paymentFailure): + switch paymentFailure.error { + case .storePayment(SKError.paymentCancelled): + break + default: + showPaymentErrorAlert(error: paymentFailure.error) + } + } - didProcessPayment(transaction.payment) + setPaymentState(.none, animated: true) } // MARK: - Actions @@ -394,7 +384,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb } let payment = SKPayment(product: product) - AppStorePaymentManager.shared.addPayment(payment, for: accountData.number) + StorePaymentManager.shared.addPayment(payment, for: accountData.number) setPaymentState(.makingPayment(payment), animated: true) } @@ -406,7 +396,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb setPaymentState(.restoringPurchases, animated: true) - _ = AppStorePaymentManager.shared.restorePurchases(for: accountData.number) { completion in + _ = StorePaymentManager.shared.restorePurchases(for: accountData.number) { completion in switch completion { case let .success(response): self.showTimeAddedConfirmationAlert(with: response, context: .restoration) @@ -441,7 +431,7 @@ private extension AccountViewController { enum ProductState { case none - case fetching(AppStoreSubscription) + case fetching(StoreSubscription) case received(SKProduct) case failed case cannotMakePurchases diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index acff5835fe..f1958e9955 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -64,7 +64,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.logger.debug("Finished initialization.") NotificationManager.shared.updateNotifications() - AppStorePaymentManager.shared.startPaymentQueueMonitoring() + StorePaymentManager.shared.startPaymentQueueMonitoring() operation.finish() } @@ -267,8 +267,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Private private func setupPaymentHandler() { - AppStorePaymentManager.shared.delegate = self - AppStorePaymentManager.shared.addPaymentObserver(TunnelManager.shared) + StorePaymentManager.shared.delegate = self + StorePaymentManager.shared.addPaymentObserver(TunnelManager.shared) } private func setupNotificationHandler() { @@ -282,9 +282,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - AppStorePaymentManagerDelegate -extension AppDelegate: AppStorePaymentManagerDelegate { - func appStorePaymentManager( - _ manager: AppStorePaymentManager, +extension AppDelegate: StorePaymentManagerDelegate { + func storePaymentManager( + _ manager: StorePaymentManager, didRequestAccountTokenFor payment: SKPayment ) -> String? { // Since we do not persist the relation between payment and account number between the diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift deleted file mode 100644 index a57306aba9..0000000000 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// AppStorePaymentManagerError.swift -// AppStorePaymentManagerError -// -// Created by pronebird on 08/09/2021. -// Copyright © 2021 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadREST -import MullvadTypes - -extension AppStorePaymentManager { - /// An error type emitted by `AppStorePaymentManager`. - enum Error: LocalizedError, WrappingError { - /// Failure to find the account token associated with the transaction. - case noAccountSet - - /// Failure to validate the account token. - case validateAccount(REST.Error) - - /// Failure to handle payment transaction. Contains error returned by StoreKit. - case storePayment(Swift.Error) - - /// Failure to read the AppStore receipt. - case readReceipt(Swift.Error) - - /// Failure to send the AppStore receipt to backend. - case sendReceipt(REST.Error) - - var errorDescription: String? { - switch self { - case .noAccountSet: - return "Account is not set." - case .validateAccount: - return "Account validation error." - case .storePayment: - return "Store payment error." - case .readReceipt: - return "Read recept error." - case .sendReceipt: - return "Send receipt error." - } - } - - var underlyingError: Swift.Error? { - switch self { - case .noAccountSet: - return nil - case let .sendReceipt(error): - return error - case let .validateAccount(error): - return error - case let .readReceipt(error): - return error - case let .storePayment(error): - return error - } - } - } -} diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift deleted file mode 100644 index 306f5b3bef..0000000000 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// AppStorePaymentObserver.swift -// AppStorePaymentObserver -// -// Created by pronebird on 03/09/2021. -// Copyright © 2021 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadREST -import StoreKit - -protocol AppStorePaymentObserver: AnyObject { - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction?, - payment: SKPayment, - accountToken: String?, - didFailWithError error: AppStorePaymentManager.Error - ) - - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction, - accountToken: String, - didFinishWithResponse response: REST.CreateApplePaymentResponse - ) -} diff --git a/ios/MullvadVPN/DisplayChainedError.swift b/ios/MullvadVPN/DisplayChainedError.swift index 2174696ede..4bfa351f4f 100644 --- a/ios/MullvadVPN/DisplayChainedError.swift +++ b/ios/MullvadVPN/DisplayChainedError.swift @@ -107,7 +107,7 @@ extension SKError: LocalizedError { } } -extension AppStorePaymentManager.Error: DisplayChainedError { +extension StorePaymentManagerError: DisplayChainedError { var errorChainDescription: String? { switch self { case .noAccountSet: @@ -144,7 +144,7 @@ extension AppStorePaymentManager.Error: DisplayChainedError { } case let .readReceipt(readReceiptError): - if readReceiptError is AppStoreReceiptNotFound { + if readReceiptError is StoreReceiptNotFound { return NSLocalizedString( "RECEIPT_NOT_FOUND_ERROR", tableName: "AppStorePaymentManager", diff --git a/ios/MullvadVPN/OutOfTimeViewController.swift b/ios/MullvadVPN/OutOfTimeViewController.swift index 8672132abb..d55490ccc2 100644 --- a/ios/MullvadVPN/OutOfTimeViewController.swift +++ b/ios/MullvadVPN/OutOfTimeViewController.swift @@ -80,7 +80,7 @@ private extension OutOfTimeViewController { } func addObservers() { - AppStorePaymentManager.shared.addPaymentObserver(self) + StorePaymentManager.shared.addPaymentObserver(self) TunnelManager.shared.addObserver(self) } @@ -116,7 +116,7 @@ private extension OutOfTimeViewController { private extension OutOfTimeViewController { func setUpInAppPurchases() { - if AppStorePaymentManager.canMakePayments { + if StorePaymentManager.canMakePayments { requestStoreProducts() } else { setProductState(.cannotMakePurchases, animated: false) @@ -124,11 +124,11 @@ private extension OutOfTimeViewController { } func requestStoreProducts() { - let productKind = AppStoreSubscription.thirtyDays + let productKind = StoreSubscription.thirtyDays setProductState(.fetching(productKind), animated: true) - _ = AppStorePaymentManager.shared + _ = StorePaymentManager.shared .requestProducts(with: [productKind]) { [weak self] completion in let productState: ProductState = completion.value?.products.first .map { .received($0) } ?? .failed @@ -200,7 +200,7 @@ private extension OutOfTimeViewController { } let payment = SKPayment(product: product) - AppStorePaymentManager.shared.addPayment(payment, for: accountData.number) + StorePaymentManager.shared.addPayment(payment, for: accountData.number) setPaymentState(.makingPayment(payment), animated: true) } @@ -212,7 +212,7 @@ private extension OutOfTimeViewController { setPaymentState(.restoringPurchases, animated: true) - _ = AppStorePaymentManager.shared.restorePurchases(for: accountData.number) { completion in + _ = StorePaymentManager.shared.restorePurchases(for: accountData.number) { completion in switch completion { case let .success(response): self.showAlertIfNoTimeAdded(with: response, context: .restoration) @@ -253,7 +253,7 @@ private extension OutOfTimeViewController { alertPresenter.enqueue(alertController, presentingController: self) } - func showRestorePurchasesErrorAlert(error: AppStorePaymentManager.Error) { + func showRestorePurchasesErrorAlert(error: StorePaymentManagerError) { let alertController = UIAlertController( title: NSLocalizedString( "RESTORE_PURCHASES_FAILURE_ALERT_TITLE", @@ -275,7 +275,7 @@ private extension OutOfTimeViewController { alertPresenter.enqueue(alertController, presentingController: self) } - func showPaymentErrorAlert(error: AppStorePaymentManager.Error) { + func showPaymentErrorAlert(error: StorePaymentManagerError) { let alertController = UIAlertController( title: NSLocalizedString( "CANNOT_COMPLETE_PURCHASE_ALERT_TITLE", @@ -300,43 +300,33 @@ private extension OutOfTimeViewController { alertPresenter.enqueue(alertController, presentingController: self) } - - func didProcessPayment(_ payment: SKPayment) { - guard case let .makingPayment(pendingPayment) = paymentState, - pendingPayment == payment else { return } - - setPaymentState(.none, animated: true) - } } -// MARK: - AppStorePaymentObserver +// MARK: - StorePaymentObserver -extension OutOfTimeViewController: AppStorePaymentObserver { - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction?, - payment: SKPayment, - accountToken: String?, - didFailWithError error: AppStorePaymentManager.Error +extension OutOfTimeViewController: StorePaymentObserver { + func storePaymentManager( + _ manager: StorePaymentManager, + didReceiveEvent event: StorePaymentEvent ) { - switch error { - case .storePayment(SKError.paymentCancelled): + guard case let .makingPayment(payment) = paymentState, + payment == event.payment else { return } + + switch event { + case .finished: break - default: - showPaymentErrorAlert(error: error) - } + case let .failure(paymentFailure): + switch paymentFailure.error { + case .storePayment(SKError.paymentCancelled): + break - didProcessPayment(payment) - } + default: + showPaymentErrorAlert(error: paymentFailure.error) + } + } - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction, - accountToken: String, - didFinishWithResponse response: REST.CreateApplePaymentResponse - ) { - didProcessPayment(transaction.payment) + setPaymentState(.none, animated: true) } } @@ -394,7 +384,7 @@ private extension OutOfTimeViewController { enum ProductState { case none - case fetching(AppStoreSubscription) + case fetching(StoreSubscription) case received(SKProduct) case failed case cannotMakePurchases diff --git a/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift index 860111e6f0..0b360a7184 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift +++ b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift @@ -1,5 +1,5 @@ // -// SendAppStoreReceiptOperation.swift +// SendStoreReceiptOperation.swift // MullvadVPN // // Created by pronebird on 29/03/2022. @@ -12,9 +12,9 @@ import MullvadREST import MullvadTypes import Operations -class SendAppStoreReceiptOperation: ResultOperation< +class SendStoreReceiptOperation: ResultOperation< REST.CreateApplePaymentResponse, - AppStorePaymentManager.Error + StorePaymentManagerError > { private let apiProxy: REST.APIProxy private let accountToken: String @@ -23,7 +23,7 @@ class SendAppStoreReceiptOperation: ResultOperation< private var fetchReceiptTask: Cancellable? private var submitReceiptTask: Cancellable? - private let logger = Logger(label: "SendAppStoreReceiptOperation") + private let logger = Logger(label: "SendStoreReceiptOperation") init( apiProxy: REST.APIProxy, @@ -53,7 +53,7 @@ class SendAppStoreReceiptOperation: ResultOperation< } override func main() { - fetchReceiptTask = AppStoreReceipt.fetch( + fetchReceiptTask = StoreReceipt.fetch( forceRefresh: forceRefresh, receiptProperties: receiptProperties ) { completion in @@ -82,10 +82,13 @@ class SendAppStoreReceiptOperation: ResultOperation< ) { result in switch result { case let .success(response): - self.logger - .info( - "AppStore receipt was processed. Time added: \(response.timeAdded), New expiry: \(response.newExpiry.logFormatDate())" - ) + self.logger.info( + """ + AppStore receipt was processed. \ + Time added: \(response.timeAdded), \ + New expiry: \(response.newExpiry.logFormatDate()) + """ + ) self.finish(completion: .success(response)) case let .failure(error): diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift new file mode 100644 index 0000000000..ce3fd61915 --- /dev/null +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentBlockObserver.swift @@ -0,0 +1,26 @@ +// +// StorePaymentBlockObserver.swift +// MullvadVPN +// +// Created by pronebird on 26/10/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +final class StorePaymentBlockObserver: StorePaymentObserver { + typealias BlockHandler = (StorePaymentManager, StorePaymentEvent) -> Void + + private let blockHandler: BlockHandler + + init(_ blockHandler: @escaping BlockHandler) { + self.blockHandler = blockHandler + } + + func storePaymentManager( + _ manager: StorePaymentManager, + didReceiveEvent event: StorePaymentEvent + ) { + blockHandler(manager, event) + } +} diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift new file mode 100644 index 0000000000..ed03a59121 --- /dev/null +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentEvent.swift @@ -0,0 +1,38 @@ +// +// StorePaymentEvent.swift +// MullvadVPN +// +// Created by pronebird on 26/10/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadREST +import StoreKit + +enum StorePaymentEvent { + case finished(StorePaymentCompletion) + case failure(StorePaymentFailure) + + var payment: SKPayment { + switch self { + case let .finished(completion): + return completion.transaction.payment + case let .failure(failure): + return failure.payment + } + } +} + +struct StorePaymentCompletion { + let transaction: SKPaymentTransaction + let accountNumber: String + let serverResponse: REST.CreateApplePaymentResponse +} + +struct StorePaymentFailure { + let transaction: SKPaymentTransaction? + let payment: SKPayment + let accountNumber: String? + let error: StorePaymentManagerError +} diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManager.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift index fcd41595df..61b626cd33 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManager.swift +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift @@ -1,5 +1,5 @@ // -// AppStorePaymentManager.swift +// StorePaymentManager.swift // MullvadVPN // // Created by pronebird on 10/03/2020. @@ -13,31 +13,34 @@ import MullvadTypes import Operations import StoreKit -class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { +class StorePaymentManager: NSObject, SKPaymentTransactionObserver { private enum OperationCategory { - static let sendAppStoreReceipt = "AppStorePaymentManager.sendAppStoreReceipt" - static let productsRequest = "AppStorePaymentManager.productsRequest" + static let sendStoreReceipt = "StorePaymentManager.sendStoreReceipt" + static let productsRequest = "StorePaymentManager.productsRequest" } /// A shared instance of `AppStorePaymentManager` - static let shared = AppStorePaymentManager(queue: SKPaymentQueue.default()) + static let shared = StorePaymentManager( + queue: SKPaymentQueue.default(), + apiProxy: REST.ProxyFactory.shared.createAPIProxy(), + accountsProxy: REST.ProxyFactory.shared.createAccountsProxy() + ) - private let logger = Logger(label: "AppStorePaymentManager") + private let logger = Logger(label: "StorePaymentManager") private let operationQueue: OperationQueue = { let queue = AsyncOperationQueue() - queue.name = "AppStorePaymentManagerQueue" + queue.name = "StorePaymentManagerQueue" return queue }() - private let apiProxy = REST.ProxyFactory.shared.createAPIProxy() - private let accountsProxy = REST.ProxyFactory.shared.createAccountsProxy() - private let paymentQueue: SKPaymentQueue - private var observerList = ObserverList<AppStorePaymentObserver>() + private let apiProxy: REST.APIProxy + private let accountsProxy: REST.AccountsProxy + private var observerList = ObserverList<StorePaymentObserver>() - private weak var classDelegate: AppStorePaymentManagerDelegate? - weak var delegate: AppStorePaymentManagerDelegate? { + private weak var classDelegate: StorePaymentManagerDelegate? + weak var delegate: StorePaymentManagerDelegate? { get { if Thread.isMainThread { return classDelegate @@ -66,8 +69,10 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { return SKPaymentQueue.canMakePayments() } - init(queue: SKPaymentQueue) { + init(queue: SKPaymentQueue, apiProxy: REST.APIProxy, accountsProxy: REST.AccountsProxy) { paymentQueue = queue + self.apiProxy = apiProxy + self.accountsProxy = accountsProxy } func startPaymentQueueMonitoring() { @@ -93,18 +98,18 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { // MARK: - Payment observation - func addPaymentObserver(_ observer: AppStorePaymentObserver) { + func addPaymentObserver(_ observer: StorePaymentObserver) { observerList.append(observer) } - func removePaymentObserver(_ observer: AppStorePaymentObserver) { + func removePaymentObserver(_ observer: StorePaymentObserver) { observerList.remove(observer) } // MARK: - Products and payments func requestProducts( - with productIdentifiers: Set<AppStoreSubscription>, + with productIdentifiers: Set<StoreSubscription>, completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void ) -> Cancellable { let productIdentifiers = productIdentifiers.productIdentifiersSet @@ -139,25 +144,31 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { self.paymentQueue.add(payment) case let .failure(error): - self.observerList.forEach { observer in - observer.appStorePaymentManager( - self, + let event = StorePaymentEvent.failure( + StorePaymentFailure( transaction: nil, payment: payment, - accountToken: accountToken, - didFailWithError: .validateAccount(error) + accountNumber: accountToken, + error: .validateAccount(error) ) + ) + + self.observerList.forEach { observer in + observer.storePaymentManager(self, didReceiveEvent: event) } case .cancelled: - self.observerList.forEach { observer in - observer.appStorePaymentManager( - self, + let event = StorePaymentEvent.failure( + StorePaymentFailure( transaction: nil, payment: payment, - accountToken: accountToken, - didFailWithError: .validateAccount(.network(URLError(.cancelled))) + accountNumber: accountToken, + error: .validateAccount(.network(URLError(.cancelled))) ) + ) + + self.observerList.forEach { observer in + observer.storePaymentManager(self, didReceiveEvent: event) } } @@ -169,10 +180,10 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { for accountToken: String, completionHandler: @escaping (OperationCompletion< REST.CreateApplePaymentResponse, - AppStorePaymentManager.Error + StorePaymentManagerError >) -> Void ) -> Cancellable { - return sendAppStoreReceipt( + return sendStoreReceipt( accountToken: accountToken, forceRefresh: true, completionHandler: completionHandler @@ -194,17 +205,20 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { paymentToAccountToken.removeValue(forKey: payment) return accountToken } else { - return classDelegate?.appStorePaymentManager(self, didRequestAccountTokenFor: payment) + return classDelegate?.storePaymentManager(self, didRequestAccountTokenFor: payment) } } - private func sendAppStoreReceipt( + private func sendStoreReceipt( accountToken: String, forceRefresh: Bool, - completionHandler: @escaping (OperationCompletion<REST.CreateApplePaymentResponse, Error>) + completionHandler: @escaping (OperationCompletion< + REST.CreateApplePaymentResponse, + StorePaymentManagerError + >) -> Void ) -> Cancellable { - let operation = SendAppStoreReceiptOperation( + let operation = SendStoreReceiptOperation( apiProxy: apiProxy, accountToken: accountToken, forceRefresh: forceRefresh, @@ -221,7 +235,7 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { ) operation.addCondition( - MutuallyExclusive(category: OperationCategory.sendAppStoreReceipt) + MutuallyExclusive(category: OperationCategory.sendStoreReceipt) ) operationQueue.addOperation(operation) @@ -270,65 +284,76 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { paymentQueue.finishTransaction(transaction) if let accountToken = deassociateAccountToken(transaction.payment) { - observerList.forEach { observer in - observer.appStorePaymentManager( - self, + let event = StorePaymentEvent.failure( + StorePaymentFailure( transaction: transaction, payment: transaction.payment, - accountToken: accountToken, - didFailWithError: .storePayment(transaction.error!) + accountNumber: accountToken, + error: .storePayment(transaction.error!) ) + ) + + observerList.forEach { observer in + observer.storePaymentManager(self, didReceiveEvent: event) } } else { - observerList.forEach { observer in - observer.appStorePaymentManager( - self, + let event = StorePaymentEvent.failure( + StorePaymentFailure( transaction: transaction, payment: transaction.payment, - accountToken: nil, - didFailWithError: .noAccountSet + accountNumber: nil, + error: .noAccountSet ) + ) + + observerList.forEach { observer in + observer.storePaymentManager(self, didReceiveEvent: event) } } } private func didFinishOrRestorePurchase(transaction: SKPaymentTransaction) { guard let accountToken = deassociateAccountToken(transaction.payment) else { - observerList.forEach { observer in - observer.appStorePaymentManager( - self, + let event = StorePaymentEvent.failure( + StorePaymentFailure( transaction: transaction, payment: transaction.payment, - accountToken: nil, - didFailWithError: .noAccountSet + accountNumber: nil, + error: .noAccountSet ) + ) + + observerList.forEach { observer in + observer.storePaymentManager(self, didReceiveEvent: event) } return } - _ = sendAppStoreReceipt(accountToken: accountToken, forceRefresh: false) { completion in + _ = sendStoreReceipt(accountToken: accountToken, forceRefresh: false) { completion in switch completion { case let .success(response): self.paymentQueue.finishTransaction(transaction) + let event = StorePaymentEvent.finished(StorePaymentCompletion( + transaction: transaction, + accountNumber: accountToken, + serverResponse: response + )) + self.observerList.forEach { observer in - observer.appStorePaymentManager( - self, - transaction: transaction, - accountToken: accountToken, - didFinishWithResponse: response - ) + observer.storePaymentManager(self, didReceiveEvent: event) } case let .failure(error): + let event = StorePaymentEvent.failure(StorePaymentFailure( + transaction: transaction, + payment: transaction.payment, + accountNumber: accountToken, + error: error + )) + self.observerList.forEach { observer in - observer.appStorePaymentManager( - self, - transaction: transaction, - payment: transaction.payment, - accountToken: accountToken, - didFailWithError: error - ) + observer.storePaymentManager(self, didReceiveEvent: event) } case .cancelled: diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerDelegate.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift index 8dc848c854..a98a37e8da 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerDelegate.swift +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerDelegate.swift @@ -1,6 +1,6 @@ // -// AppStorePaymentManagerDelegate.swift -// AppStorePaymentManagerDelegate +// StorePaymentManagerDelegate.swift +// MullvadVPN // // Created by pronebird on 03/09/2021. // Copyright © 2021 Mullvad VPN AB. All rights reserved. @@ -9,11 +9,11 @@ import Foundation import StoreKit -protocol AppStorePaymentManagerDelegate: AnyObject { +protocol StorePaymentManagerDelegate: AnyObject { /// Return the account token associated with the payment. /// Usually called for unfinished transactions coming back after the app was restarted. - func appStorePaymentManager( - _ manager: AppStorePaymentManager, + func storePaymentManager( + _ manager: StorePaymentManager, didRequestAccountTokenFor payment: SKPayment ) -> String? } diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift new file mode 100644 index 0000000000..83a5508e77 --- /dev/null +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift @@ -0,0 +1,59 @@ +// +// StorePaymentManagerError.swift +// MullvadVPN +// +// Created by pronebird on 08/09/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadREST +import MullvadTypes + +/// An error type emitted by `StorePaymentManager`. +enum StorePaymentManagerError: LocalizedError, WrappingError { + /// Failure to find the account token associated with the transaction. + case noAccountSet + + /// Failure to validate the account token. + case validateAccount(REST.Error) + + /// Failure to handle payment transaction. Contains error returned by StoreKit. + case storePayment(Swift.Error) + + /// Failure to read the AppStore receipt. + case readReceipt(Swift.Error) + + /// Failure to send the AppStore receipt to backend. + case sendReceipt(REST.Error) + + var errorDescription: String? { + switch self { + case .noAccountSet: + return "Account is not set." + case .validateAccount: + return "Account validation error." + case .storePayment: + return "Store payment error." + case .readReceipt: + return "Read recept error." + case .sendReceipt: + return "Send receipt error." + } + } + + var underlyingError: Swift.Error? { + switch self { + case .noAccountSet: + return nil + case let .sendReceipt(error): + return error + case let .validateAccount(error): + return error + case let .readReceipt(error): + return error + case let .storePayment(error): + return error + } + } +} diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift new file mode 100644 index 0000000000..0d6e3584fc --- /dev/null +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentObserver.swift @@ -0,0 +1,16 @@ +// +// StorePaymentObserver.swift +// MullvadVPN +// +// Created by pronebird on 03/09/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +protocol StorePaymentObserver: AnyObject { + func storePaymentManager( + _ manager: StorePaymentManager, + didReceiveEvent event: StorePaymentEvent + ) +} diff --git a/ios/MullvadVPN/AppStoreReceipt.swift b/ios/MullvadVPN/StorePaymentManager/StoreReceipt.swift index edc20573d8..93731ba7f9 100644 --- a/ios/MullvadVPN/AppStoreReceipt.swift +++ b/ios/MullvadVPN/StorePaymentManager/StoreReceipt.swift @@ -1,5 +1,5 @@ // -// AppStoreReceipt.swift +// StoreReceipt.swift // MullvadVPN // // Created by pronebird on 11/03/2020. @@ -12,17 +12,17 @@ import MullvadTypes import Operations import StoreKit -struct AppStoreReceiptNotFound: LocalizedError { +struct StoreReceiptNotFound: LocalizedError { var errorDescription: String? { return "AppStore receipt file does not exist on disk." } } -enum AppStoreReceipt { +enum StoreReceipt { /// Internal operation queue. private static let operationQueue: OperationQueue = { let queue = AsyncOperationQueue() - queue.name = "AppStoreReceiptQueue" + queue.name = "StoreReceiptQueue" queue.maxConcurrentOperationCount = 1 return queue }() @@ -86,7 +86,7 @@ private class FetchAppStoreReceiptOperation: ResultOperation<Data, Error>, SKReq let data = try readReceiptFromDisk() finish(completion: .success(data)) - } catch is AppStoreReceiptNotFound { + } catch is StoreReceiptNotFound { // Pull receipt from AppStore if it's not cached locally. startRefreshRequest() } catch { @@ -139,7 +139,7 @@ private class FetchAppStoreReceiptOperation: ResultOperation<Data, Error>, SKReq private func readReceiptFromDisk() throws -> Data { guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else { - throw AppStoreReceiptNotFound() + throw StoreReceiptNotFound() } do { @@ -147,7 +147,7 @@ private class FetchAppStoreReceiptOperation: ResultOperation<Data, Error>, SKReq } catch let error as CocoaError where error.code == .fileReadNoSuchFile || error.code == .fileNoSuchFile { - throw AppStoreReceiptNotFound() + throw StoreReceiptNotFound() } catch { throw error } diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStoreSubscription.swift b/ios/MullvadVPN/StorePaymentManager/StoreSubscription.swift index 37fa3342cb..105ae4425f 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStoreSubscription.swift +++ b/ios/MullvadVPN/StorePaymentManager/StoreSubscription.swift @@ -1,6 +1,6 @@ // -// AppStoreSubscription.swift -// AppStoreSubscription +// StoreSubscription.swift +// MullvadVPN // // Created by pronebird on 03/09/2021. // Copyright © 2021 Mullvad VPN AB. All rights reserved. @@ -9,7 +9,7 @@ import Foundation import StoreKit -enum AppStoreSubscription: String { +enum StoreSubscription: String { /// Thirty days non-renewable subscription case thirtyDays = "net.mullvad.MullvadVPN.subscription.30days" @@ -17,8 +17,8 @@ enum AppStoreSubscription: String { switch self { case .thirtyDays: return NSLocalizedString( - "APPSTORE_SUBSCRIPTION_TITLE_ADD_30_DAYS", - tableName: "AppStoreSubscriptions", + "STORE_SUBSCRIPTION_TITLE_ADD_30_DAYS", + tableName: "StoreSubscriptions", value: "Add 30 days time", comment: "" ) @@ -28,12 +28,12 @@ enum AppStoreSubscription: String { extension SKProduct { var customLocalizedTitle: String? { - return AppStoreSubscription(rawValue: productIdentifier)?.localizedTitle + return StoreSubscription(rawValue: productIdentifier)?.localizedTitle } } -extension Set where Element == AppStoreSubscription { +extension Set where Element == StoreSubscription { var productIdentifiersSet: Set<String> { - Set<String>(map { $0.rawValue }) + return Set<String>(map { $0.rawValue }) } } diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index 28ab12b8ad..9aa731551f 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -960,30 +960,22 @@ final class TunnelManager { // MARK: - AppStore payment observer -extension TunnelManager: AppStorePaymentObserver { - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction?, - payment: SKPayment, - accountToken: String?, - didFailWithError error: AppStorePaymentManager.Error +extension TunnelManager: StorePaymentObserver { + func storePaymentManager( + _ manager: StorePaymentManager, + didReceiveEvent event: StorePaymentEvent ) { - // no-op - } + guard case let .finished(paymentCompletion) = event else { + return + } - func appStorePaymentManager( - _ manager: AppStorePaymentManager, - transaction: SKPaymentTransaction, - accountToken: String, - didFinishWithResponse response: REST.CreateApplePaymentResponse - ) { scheduleDeviceStateUpdate( taskName: "Update account expiry after in-app purchase", modificationBlock: { deviceState in switch deviceState { case .loggedIn(var accountData, let deviceData): - if accountData.number == accountToken { - accountData.expiry = response.newExpiry + if accountData.number == paymentCompletion.accountNumber { + accountData.expiry = paymentCompletion.serverResponse.newExpiry deviceState = .loggedIn(accountData, deviceData) } |
