From a25f64b6afea5208980ac2a99d5e5289c9a3f1cc Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Wed, 16 Aug 2023 16:04:42 -0700 Subject: [PATCH] `StoreKit 2`: only listen to `StoreKit.Transaction.updates` when SK2 is enabled (#3032) --- .../Logging/Strings/ConfigureStrings.swift | 7 ++++++ .../Purchases/PurchasesOrchestrator.swift | 4 +++- .../StoreKit1/PaymentQueueWrapper.swift | 2 +- Sources/Support/PaywallExtensions.swift | 4 ++++ ...StoreKitObserverModeIntegrationTests.swift | 22 ------------------- .../PurchasesOrchestratorTests.swift | 13 ++++++----- 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Sources/Logging/Strings/ConfigureStrings.swift b/Sources/Logging/Strings/ConfigureStrings.swift index aea5a97836..c3ec5b426a 100644 --- a/Sources/Logging/Strings/ConfigureStrings.swift +++ b/Sources/Logging/Strings/ConfigureStrings.swift @@ -71,6 +71,8 @@ enum ConfigureStrings { case timeout_lower_than_minimum(timeout: TimeInterval, minimum: TimeInterval) + case sk2_required_for_swiftui_paywalls + } extension ConfigureStrings: LogMessage { @@ -176,6 +178,11 @@ extension ConfigureStrings: LogMessage { Timeout value: \(timeout) is lower than the minimum, setting it to the mimimum: (\(minimum)) """ + + case .sk2_required_for_swiftui_paywalls: + return "Purchases is not configured with StoreKit 2 enabled. This is required in order to detect " + + "transactions coming from SwiftUI paywalls. You must use `.with(usesStoreKit2IfAvailable: true)` " + + "when configuring the SDK." } } diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 857e6dd6e4..e2cd8c89be 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -134,7 +134,9 @@ final class PurchasesOrchestrator { Task { await storeKit2TransactionListener.set(delegate: self) - await storeKit2TransactionListener.listenForTransactions() + if systemInfo.storeKit2Setting == .enabledForCompatibleDevices { + await storeKit2TransactionListener.listenForTransactions() + } } } diff --git a/Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift b/Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift index 603c4b7f1e..9b90852f6b 100644 --- a/Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift +++ b/Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift @@ -129,7 +129,7 @@ extension PaymentQueueWrapper: SKPaymentQueueDelegate { extension PaymentQueueWrapper: SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { - // Ignored. Either `StoreKit1Wrapper` or `StoreKit2TransactionListener` will handle this. + // Ignored. Either `StoreKit1Wrapper` will handle this, or `StoreKit2TransactionListener` if `SK2` is enabled. } #if !os(watchOS) diff --git a/Sources/Support/PaywallExtensions.swift b/Sources/Support/PaywallExtensions.swift index da74858061..13a222f541 100644 --- a/Sources/Support/PaywallExtensions.swift +++ b/Sources/Support/PaywallExtensions.swift @@ -108,6 +108,10 @@ private extension View { .onInAppPurchaseStart { product in guard Purchases.isConfigured else { return } + if !Purchases.shared.storeKit2Setting.isEnabledAndAvailable { + Logger.appleWarning(Strings.configure.sk2_required_for_swiftui_paywalls) + } + Purchases.shared.cachePresentedOfferingIdentifier( offering.identifier, productIdentifier: product.id diff --git a/Tests/BackendIntegrationTests/StoreKitObserverModeIntegrationTests.swift b/Tests/BackendIntegrationTests/StoreKitObserverModeIntegrationTests.swift index 4c1ed72787..4d6c5b31d0 100644 --- a/Tests/BackendIntegrationTests/StoreKitObserverModeIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/StoreKitObserverModeIntegrationTests.swift @@ -111,28 +111,6 @@ class StoreKit1ObserverModeIntegrationTests: BaseStoreKitObserverModeIntegration try await self.verifyEntitlementWentThrough(customerInfo) } - @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) - func testSK2RenewalsPostReceiptOnlyOnceWhenSK1IsEnabled() async throws { - try XCTSkipIf(Self.storeKit2Setting.isEnabledAndAvailable, "Test only for SK1") - - // `StoreKit2TransactionListener` is always enabled even in SK1 mode. - // This test ensures that we don't end up posting receipts multiple times when renewals come through. - - self.testSession.timeRate = .realTime - - let productID = Self.monthlyNoIntroProductID - - try await self.manager.purchaseProductFromStoreKit2(productIdentifier: productID) - - try? self.testSession.forceRenewalOfSubscription(productIdentifier: productID) - - try await self.logger.verifyMessageIsEventuallyLogged( - "Network operation 'PostReceiptDataOperation' found with the same cache key", - timeout: .seconds(4), - pollInterval: .milliseconds(100) - ) - } - } @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) diff --git a/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift b/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift index 430201260f..fd6c3c357f 100644 --- a/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift +++ b/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift @@ -1064,7 +1064,7 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { } @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) - func testListensForSK2TransactionsWithSK2Disabled() throws { + func testDoesNotListenForSK2TransactionsWithSK2Disabled() throws { try AvailabilityChecks.iOS15APIAvailableOrSkipTest() let transactionListener = MockStoreKit2TransactionListener() @@ -1074,11 +1074,12 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { self.setUpOrchestrator(storeKit2TransactionListener: transactionListener, storeKit2StorefrontListener: StoreKit2StorefrontListener(delegate: nil)) - expect(transactionListener.invokedListenForTransactions).toEventually(beTrue()) + expect(transactionListener.invokedDelegateSetter).toEventually(beTrue()) + expect(transactionListener.invokedListenForTransactions) == false } @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) - func testListensForSK2TransactionsWithSK2EnabledOnlyForOptimizations() throws { + func testDoesNotListenForSK2TransactionsWithSK2EnabledOnlyForOptimizations() throws { try AvailabilityChecks.iOS15APIAvailableOrSkipTest() let transactionListener = MockStoreKit2TransactionListener() @@ -1088,7 +1089,8 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { self.setUpOrchestrator(storeKit2TransactionListener: transactionListener, storeKit2StorefrontListener: StoreKit2StorefrontListener(delegate: nil)) - expect(transactionListener.invokedListenForTransactions).toEventually(beTrue()) + expect(transactionListener.invokedDelegateSetter).toEventually(beTrue()) + expect(transactionListener.invokedListenForTransactions) == false } @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) @@ -1102,7 +1104,8 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { self.setUpOrchestrator(storeKit2TransactionListener: transactionListener, storeKit2StorefrontListener: StoreKit2StorefrontListener(delegate: nil)) - expect(transactionListener.invokedListenForTransactions).toEventually(beTrue()) + expect(transactionListener.invokedDelegateSetter).toEventually(beTrue()) + expect(transactionListener.invokedListenForTransactions) == true expect(transactionListener.invokedListenForTransactionsCount) == 1 }