From 0936f4e632415bf3527195471deda17fa8a80908 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 30 Sep 2022 10:47:49 -0700 Subject: [PATCH] Disabled retry by default --- Sources/Misc/DangerousSettings.swift | 19 +++++- .../Purchases/PurchasesOrchestrator.swift | 2 +- .../BaseBackendIntegrationTests.swift | 7 +- .../PurchasesOrchestratorTests.swift | 68 ++++++------------- Tests/UnitTests/Misc/SystemInfoTests.swift | 7 ++ Tests/UnitTests/Mocks/MockSystemInfo.swift | 3 +- 6 files changed, 55 insertions(+), 51 deletions(-) diff --git a/Sources/Misc/DangerousSettings.swift b/Sources/Misc/DangerousSettings.swift index 944c056255..211fde6d24 100644 --- a/Sources/Misc/DangerousSettings.swift +++ b/Sources/Misc/DangerousSettings.swift @@ -13,6 +13,15 @@ import Foundation */ @objc(RCDangerousSettings) public final class DangerousSettings: NSObject { + /// Dangerous settings not exposed outside of the SDK. + internal struct InternalSettings { + + /// Whether `ReceiptFetcher` can retry fetching receipts. + let enableReceiptFetchRetry: Bool + + static let `default`: Self = .init(enableReceiptFetchRetry: false) + } + /** * Disable or enable subscribing to the StoreKit queue. If this is disabled, RevenueCat won't observe * the StoreKit queue, and it will not sync any purchase automatically. @@ -23,6 +32,8 @@ import Foundation */ @objc public let autoSyncPurchases: Bool + internal let internalSettings: InternalSettings + @objc public override convenience init() { self.init(autoSyncPurchases: true) } @@ -34,8 +45,14 @@ import Foundation * If this is disabled, RevenueCat won't observe the StoreKit queue, and it will not sync any purchase * automatically. */ - @objc public init(autoSyncPurchases: Bool) { + @objc public convenience init(autoSyncPurchases: Bool) { + self.init(autoSyncPurchases: autoSyncPurchases, internalSettings: .default) + } + + /// Designated initializer + internal init(autoSyncPurchases: Bool, internalSettings: InternalSettings) { self.autoSyncPurchases = autoSyncPurchases + self.internalSettings = internalSettings } } diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 780072468b..3957b82d2a 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -745,7 +745,7 @@ private extension PurchasesOrchestrator { } private func refreshRequestPolicy(forProductIdentifier productIdentifier: String) -> ReceiptRefreshPolicy { - if self.systemInfo.isSandbox { + if self.systemInfo.dangerousSettings.internalSettings.enableReceiptFetchRetry { return .retryUntilProductIsFound(productIdentifier: productIdentifier, maximumRetries: Self.receiptRetryCount, sleepDuration: Self.receiptRetrySleepDuration) diff --git a/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift b/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift index 7b73df64ca..abe6d62bec 100644 --- a/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift @@ -97,11 +97,16 @@ private extension BaseBackendIntegrationTests { storeKit2Setting: Self.storeKit2Setting, storeKitTimeout: Configuration.storeKitRequestTimeoutDefault, networkTimeout: Configuration.networkTimeoutDefault, - dangerousSettings: nil) + dangerousSettings: self.dangerousSettings) Purchases.logLevel = .debug Purchases.shared.delegate = self.purchasesDelegate } + private var dangerousSettings: DangerousSettings { + return .init(autoSyncPurchases: true, + internalSettings: .init(enableReceiptFetchRetry: true)) + } + } private final class MockSandboxEnvironmentDetector: SandboxEnvironmentDetector { diff --git a/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift b/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift index 43f2ee0646..caeb2ac1d0 100644 --- a/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift +++ b/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift @@ -88,9 +88,9 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { mockBeginRefundRequestHelper = MockBeginRefundRequestHelper(systemInfo: systemInfo, customerInfoManager: customerInfoManager, currentUserProvider: currentUserProvider) - setupStoreKit1Wrapper() - setUpOrchestrator() - setUpStoreKit2Listener() + self.setupStoreKit1Wrapper() + self.setUpOrchestrator() + self.setUpStoreKit2Listener() } fileprivate func setUpStoreKit2Listener() { @@ -188,42 +188,6 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { } } - expect(self.receiptFetcher.receiptDataCalled) == true - expect(self.receiptFetcher.receiptDataReceivedRefreshPolicy) == .retryUntilProductIsFound( - productIdentifier: product.productIdentifier, - maximumRetries: PurchasesOrchestrator.receiptRetryCount, - sleepDuration: PurchasesOrchestrator.receiptRetrySleepDuration - ) - - expect(self.backend.invokedPostReceiptDataCount) == 1 - expect(self.backend.invokedPostReceiptDataParameters?.productData).toNot(beNil()) - } - - func testPurchaseDoesNotRetryReceiptFetchIfNotInSandbox() async throws { - self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo - self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) - self.systemInfo.stubbedIsSandbox = false - - let product = try await self.fetchSk1Product() - let storeProduct = try await self.fetchSk1StoreProduct() - let package = Package(identifier: "package", - packageType: .monthly, - storeProduct: storeProduct, - offeringIdentifier: "offering") - - let payment = self.storeKit1Wrapper.payment(with: product) - - _ = await withCheckedContinuation { continuation in - self.orchestrator.purchase( - sk1Product: product, - payment: payment, - package: package, - wrapper: self.storeKit1Wrapper - ) { transaction, customerInfo, error, userCancelled in - continuation.resume(returning: (transaction, customerInfo, error, userCancelled)) - } - } - expect(self.receiptFetcher.receiptDataCalled) == true expect(self.receiptFetcher.receiptDataReceivedRefreshPolicy) == .always @@ -394,27 +358,33 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { _ = try await orchestrator.purchase(sk2Product: product, promotionalOffer: nil) expect(self.receiptFetcher.receiptDataCalled) == true - expect(self.receiptFetcher.receiptDataReceivedRefreshPolicy) == .retryUntilProductIsFound( - productIdentifier: product.id, - maximumRetries: PurchasesOrchestrator.receiptRetryCount, - sleepDuration: PurchasesOrchestrator.receiptRetrySleepDuration - ) + expect(self.receiptFetcher.receiptDataReceivedRefreshPolicy) == .always expect(self.backend.invokedPostReceiptDataCount) == 1 expect(self.backend.invokedPostReceiptDataParameters?.productData).toNot(beNil()) } @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) - func testPurchaseSK2PackageDoesNotRetryReceiptFetchIfNotInSandbox() async throws { + func testPurchaseSK2PackageRetriesReceiptFetchIfEnabled() async throws { try AvailabilityChecks.iOS15APIAvailableOrSkipTest() + self.systemInfo = try .init( + platformInfo: nil, + finishTransactions: false, + storeKit2Setting: .enabledForCompatibleDevices, + dangerousSettings: .init(autoSyncPurchases: true, + internalSettings: .init(enableReceiptFetchRetry: true)) + ) + + self.setUpOrchestrator() + self.setUpStoreKit2Listener() + let mockListener = try XCTUnwrap( self.orchestrator.storeKit2TransactionListener as? MockStoreKit2TransactionListener ) self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) - self.systemInfo.stubbedIsSandbox = false mockListener.mockTransaction.value = try await self.createTransactionWithPurchase() @@ -423,7 +393,11 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { _ = try await orchestrator.purchase(sk2Product: product, promotionalOffer: nil) expect(self.receiptFetcher.receiptDataCalled) == true - expect(self.receiptFetcher.receiptDataReceivedRefreshPolicy) == .always + expect(self.receiptFetcher.receiptDataReceivedRefreshPolicy) == .retryUntilProductIsFound( + productIdentifier: product.id, + maximumRetries: PurchasesOrchestrator.receiptRetryCount, + sleepDuration: PurchasesOrchestrator.receiptRetrySleepDuration + ) expect(self.backend.invokedPostReceiptDataCount) == 1 expect(self.backend.invokedPostReceiptDataParameters?.productData).toNot(beNil()) diff --git a/Tests/UnitTests/Misc/SystemInfoTests.swift b/Tests/UnitTests/Misc/SystemInfoTests.swift index d466d71e99..d92fa6b00e 100644 --- a/Tests/UnitTests/Misc/SystemInfoTests.swift +++ b/Tests/UnitTests/Misc/SystemInfoTests.swift @@ -81,6 +81,13 @@ class SystemInfoTests: TestCase { )) == false } + func testReceiptFetchRetryIsDisabledByDefault() throws { + let systemInfo = try SystemInfo(platformInfo: nil, finishTransactions: false) + let settings = systemInfo.dangerousSettings.internalSettings + + expect(settings.enableReceiptFetchRetry) == false + } + } private extension SystemInfo { diff --git a/Tests/UnitTests/Mocks/MockSystemInfo.swift b/Tests/UnitTests/Mocks/MockSystemInfo.swift index 00687dd950..4682dae0d9 100644 --- a/Tests/UnitTests/Mocks/MockSystemInfo.swift +++ b/Tests/UnitTests/Mocks/MockSystemInfo.swift @@ -15,7 +15,8 @@ class MockSystemInfo: SystemInfo { var stubbedIsApplicationBackgrounded: Bool? var stubbedIsSandbox: Bool? - convenience init(finishTransactions: Bool, storeKit2Setting: StoreKit2Setting = .default) { + convenience init(finishTransactions: Bool, + storeKit2Setting: StoreKit2Setting = .default) { // swiftlint:disable:next force_try try! self.init(platformInfo: nil, finishTransactions: finishTransactions,