Skip to content

Commit

Permalink
StoreKit 2: only listen to StoreKit.Transaction.updates when SK2 …
Browse files Browse the repository at this point in the history
…is enabled (#3032)
  • Loading branch information
NachoSoto authored and MarkVillacampa committed Sep 6, 2023
1 parent 326f326 commit a25f64b
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 29 deletions.
7 changes: 7 additions & 0 deletions Sources/Logging/Strings/ConfigureStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ enum ConfigureStrings {

case timeout_lower_than_minimum(timeout: TimeInterval, minimum: TimeInterval)

case sk2_required_for_swiftui_paywalls

}

extension ConfigureStrings: LogMessage {
Expand Down Expand Up @@ -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."
}
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/Purchasing/Purchases/PurchasesOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ final class PurchasesOrchestrator {

Task {
await storeKit2TransactionListener.set(delegate: self)
await storeKit2TransactionListener.listenForTransactions()
if systemInfo.storeKit2Setting == .enabledForCompatibleDevices {
await storeKit2TransactionListener.listenForTransactions()
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Support/PaywallExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, *)
Expand Down
13 changes: 8 additions & 5 deletions Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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, *)
Expand All @@ -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
}

Expand Down

0 comments on commit a25f64b

Please sign in to comment.