From 11bfffbaf893918e3d5e85a22a522654ed647bba Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Mon, 14 Aug 2023 11:16:43 -0700 Subject: [PATCH] `CustomerInfoManager`: allow disabling post-transaction behavior See https://github.com/RevenueCat/purchases-ios/pull/2914#discussion_r1291605284 This will be used by that PR as a fallback whenever we try to post a transaction that had already been posted. --- Sources/Identity/CustomerInfoManager.swift | 35 +++++++++++------ .../CustomerInfoManagerPostReceiptTests.swift | 14 +++++++ .../Mocks/MockCustomerInfoManager.swift | 39 ++++++++++++++----- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/Sources/Identity/CustomerInfoManager.swift b/Sources/Identity/CustomerInfoManager.swift index 741bd1c508..16a250676d 100644 --- a/Sources/Identity/CustomerInfoManager.swift +++ b/Sources/Identity/CustomerInfoManager.swift @@ -51,9 +51,11 @@ class CustomerInfoManager { func fetchAndCacheCustomerInfo(appUserID: String, isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool = true, completion: CustomerInfoCompletion?) { - self.getCustomerInfo(appUserID: appUserID, - isAppBackgrounded: isAppBackgrounded) { result in + self.getCustomerInfoOrPostTransactions(appUserID: appUserID, + isAppBackgrounded: isAppBackgrounded, + postTransactionsIfNeeded: postTransactionsIfNeeded) { result in switch result { case let .failure(error): self.withData { $0.deviceCache.clearCustomerInfoCacheTimestamp(appUserID: appUserID) } @@ -78,6 +80,7 @@ class CustomerInfoManager { func fetchAndCacheCustomerInfoIfStale(appUserID: String, isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool = true, completion: CustomerInfoCompletion?) { let isCacheStale = self.withData { $0.deviceCache.isCustomerInfoCacheStale(appUserID: appUserID, isAppBackgrounded: isAppBackgrounded) @@ -89,6 +92,7 @@ class CustomerInfoManager { : Strings.customerInfo.customerinfo_stale_updating_in_foreground) self.fetchAndCacheCustomerInfo(appUserID: appUserID, isAppBackgrounded: isAppBackgrounded, + postTransactionsIfNeeded: postTransactionsIfNeeded, completion: completion) return } @@ -101,17 +105,16 @@ class CustomerInfoManager { } func sendCachedCustomerInfoIfAvailable(appUserID: String) { - guard let info = self.cachedCustomerInfo(appUserID: appUserID) else { - return + if let info = self.cachedCustomerInfo(appUserID: appUserID) { + self.sendUpdateIfChanged(customerInfo: info) } - - self.sendUpdateIfChanged(customerInfo: info) } // swiftlint:disable:next function_body_length func customerInfo( appUserID: String, fetchPolicy: CacheFetchPolicy, + postTransactionsIfNeeded: Bool = true, completion: CustomerInfoCompletion? ) { switch fetchPolicy { @@ -126,6 +129,7 @@ class CustomerInfoManager { self.systemInfo.isApplicationBackgrounded { isAppBackgrounded in self.fetchAndCacheCustomerInfo(appUserID: appUserID, isAppBackgrounded: isAppBackgrounded, + postTransactionsIfNeeded: postTransactionsIfNeeded, completion: completion) } @@ -149,6 +153,7 @@ class CustomerInfoManager { self.systemInfo.isApplicationBackgrounded { isAppBackgrounded in self.fetchAndCacheCustomerInfoIfStale(appUserID: appUserID, isAppBackgrounded: isAppBackgrounded, + postTransactionsIfNeeded: postTransactionsIfNeeded, completion: completionIfNotCalledAlready) } @@ -170,6 +175,7 @@ class CustomerInfoManager { } else { self.fetchAndCacheCustomerInfo(appUserID: appUserID, isAppBackgrounded: isAppBackgrounded, + postTransactionsIfNeeded: postTransactionsIfNeeded, completion: completion) } } @@ -289,10 +295,15 @@ class CustomerInfoManager { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) extension CustomerInfoManager { - func fetchAndCacheCustomerInfo(appUserID: String, isAppBackgrounded: Bool) async throws -> CustomerInfo { + func fetchAndCacheCustomerInfo( + appUserID: String, + isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool = true + ) async throws -> CustomerInfo { return try await Async.call { completion in return self.fetchAndCacheCustomerInfo(appUserID: appUserID, isAppBackgrounded: isAppBackgrounded, + postTransactionsIfNeeded: postTransactionsIfNeeded, completion: completion) } } @@ -314,10 +325,12 @@ extension CustomerInfoManager { private extension CustomerInfoManager { - func getCustomerInfo(appUserID: String, - isAppBackgrounded: Bool, - completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { - if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { + func getCustomerInfoOrPostTransactions(appUserID: String, + isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool = true, + completion: @escaping CustomerAPI.CustomerInfoResponseHandler) { + if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *), + postTransactionsIfNeeded { _ = Task { let transactions = await self.transactionFetcher.unfinishedVerifiedTransactions diff --git a/Tests/UnitTests/Identity/CustomerInfoManagerPostReceiptTests.swift b/Tests/UnitTests/Identity/CustomerInfoManagerPostReceiptTests.swift index 093c7cacb2..d395cbcfe3 100644 --- a/Tests/UnitTests/Identity/CustomerInfoManagerPostReceiptTests.swift +++ b/Tests/UnitTests/Identity/CustomerInfoManagerPostReceiptTests.swift @@ -39,6 +39,20 @@ class CustomerInfoManagerPostReceiptTests: BaseCustomerInfoManagerTests { expect(self.mockTransactionPoster.invokedHandlePurchasedTransaction.value) == false } + func testDoesNotTryToPostUnfinishedTransactionsIfDisabled() async throws { + self.mockTransationFetcher.stubbedUnfinishedTransactions = [Self.createTransaction()] + self.mockBackend.stubbedGetCustomerInfoResult = .success(self.mockCustomerInfo) + + let result = try await self.customerInfoManager.fetchAndCacheCustomerInfo(appUserID: Self.userID, + isAppBackgrounded: false, + postTransactionsIfNeeded: false) + expect(result) === self.mockCustomerInfo + + expect(self.mockBackend.invokedGetSubscriberDataCount) == 1 + expect(self.mockBackend.invokedGetSubscriberDataParameters?.randomDelay) == false + expect(self.mockTransactionPoster.invokedHandlePurchasedTransaction.value) == false + } + func testReturnsFailureIfPostingReceiptFails() async throws { self.mockTransationFetcher.stubbedUnfinishedTransactions = [Self.createTransaction()] self.mockTransactionPoster.stubbedHandlePurchasedTransactionResult.value = .failure( diff --git a/Tests/UnitTests/Mocks/MockCustomerInfoManager.swift b/Tests/UnitTests/Mocks/MockCustomerInfoManager.swift index f693c620e5..e46d5f78c1 100644 --- a/Tests/UnitTests/Mocks/MockCustomerInfoManager.swift +++ b/Tests/UnitTests/Mocks/MockCustomerInfoManager.swift @@ -16,34 +16,50 @@ class MockCustomerInfoManager: CustomerInfoManager { var invokedFetchAndCacheCustomerInfo = false var invokedFetchAndCacheCustomerInfoCount = 0 - var invokedFetchAndCacheCustomerInfoParameters: (appUserID: String, isAppBackgrounded: Bool, completion: CustomerInfoCompletion?)? + var invokedFetchAndCacheCustomerInfoParameters: (appUserID: String, + isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool, + completion: CustomerInfoCompletion?)? var invokedFetchAndCacheCustomerInfoParametersList = [(appUserID: String, isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool, completion: CustomerInfoCompletion?)]() override func fetchAndCacheCustomerInfo(appUserID: String, isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool = true, completion: CustomerInfoCompletion?) { - invokedFetchAndCacheCustomerInfo = true - invokedFetchAndCacheCustomerInfoCount += 1 - invokedFetchAndCacheCustomerInfoParameters = (appUserID, isAppBackgrounded, completion) - invokedFetchAndCacheCustomerInfoParametersList.append((appUserID, isAppBackgrounded, completion)) + self.invokedFetchAndCacheCustomerInfo = true + self.invokedFetchAndCacheCustomerInfoCount += 1 + self.invokedFetchAndCacheCustomerInfoParameters = (appUserID, isAppBackgrounded, postTransactionsIfNeeded, completion) + self.invokedFetchAndCacheCustomerInfoParametersList.append((appUserID, + isAppBackgrounded, + postTransactionsIfNeeded, + completion)) } var invokedFetchAndCacheCustomerInfoIfStale = false var invokedFetchAndCacheCustomerInfoIfStaleCount = 0 - var invokedFetchAndCacheCustomerInfoIfStaleParameters: (appUserID: String, isAppBackgrounded: Bool, completion: CustomerInfoCompletion?)? + var invokedFetchAndCacheCustomerInfoIfStaleParameters: (appUserID: String, + isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool, + completion: CustomerInfoCompletion?)? var invokedFetchAndCacheCustomerInfoIfStaleParametersList = [(appUserID: String, isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool, completion: CustomerInfoCompletion?)]() override func fetchAndCacheCustomerInfoIfStale(appUserID: String, isAppBackgrounded: Bool, + postTransactionsIfNeeded: Bool = true, completion: CustomerInfoCompletion?) { self.invokedFetchAndCacheCustomerInfoIfStale = true self.invokedFetchAndCacheCustomerInfoIfStaleCount += 1 - self.invokedFetchAndCacheCustomerInfoIfStaleParameters = (appUserID, isAppBackgrounded, completion) - self.invokedFetchAndCacheCustomerInfoIfStaleParametersList.append((appUserID, isAppBackgrounded, completion)) + self.invokedFetchAndCacheCustomerInfoIfStaleParameters = (appUserID, + isAppBackgrounded, + postTransactionsIfNeeded, + completion) + self.invokedFetchAndCacheCustomerInfoIfStaleParametersList.append((appUserID, isAppBackgrounded, postTransactionsIfNeeded, completion)) } var invokedSendCachedCustomerInfoIfAvailable = false @@ -62,20 +78,23 @@ class MockCustomerInfoManager: CustomerInfoManager { var invokedCustomerInfoCount = 0 var invokedCustomerInfoParameters: (appUserID: String, fetchPolicy: CacheFetchPolicy, + postTransactionsIfNeeded: Bool, completion: CustomerInfoCompletion?)? var invokedCustomerInfoParametersList: [(appUserID: String, fetchPolicy: CacheFetchPolicy, + postTransactionsIfNeeded: Bool, completion: CustomerInfoCompletion?)] = [] var stubbedCustomerInfoResult: Result = .failure(.missingAppUserID()) override func customerInfo(appUserID: String, fetchPolicy: CacheFetchPolicy, + postTransactionsIfNeeded: Bool = true, completion: CustomerInfoCompletion?) { self.invokedCustomerInfo = true self.invokedCustomerInfoCount += 1 - self.invokedCustomerInfoParameters = (appUserID, fetchPolicy, completion) - self.invokedCustomerInfoParametersList.append((appUserID, fetchPolicy, completion)) + self.invokedCustomerInfoParameters = (appUserID, fetchPolicy, postTransactionsIfNeeded, completion) + self.invokedCustomerInfoParametersList.append((appUserID, fetchPolicy, postTransactionsIfNeeded, completion)) OperationDispatcher.dispatchOnMainActor { completion?(self.stubbedCustomerInfoResult)