diff --git a/Sources/Misc/Deprecations.swift b/Sources/Misc/Deprecations.swift index 86d7f61958..a559c43483 100644 --- a/Sources/Misc/Deprecations.swift +++ b/Sources/Misc/Deprecations.swift @@ -400,7 +400,7 @@ extension CustomerInfo { public extension Configuration.Builder { - /// Set `usesStoreKit2IfAvailable`. If `true`, the SDK will use StoreKit 2 APIs internally. If disabled, it will use StoreKit 1 APIs instead. + /// Set `storeKit2Setting`. If `true`, the SDK will use StoreKit 2 APIs internally. If disabled, it will use StoreKit 1 APIs instead. /// - Parameter usesStoreKit2IfAvailable: enable StoreKit 2 on devices that support it. /// Defaults to `false`. /// - Important: This configuration flag has been deprecated, and will be replaced by automatic remote configuration in the future. @@ -420,17 +420,40 @@ public extension Configuration.Builder { return self } + /// Set `storeKit2Setting`. If `true`, the SDK will use StoreKit 2 APIs internally. If disabled, it will use StoreKit 1 APIs instead. + /// - Parameter storeKitVersion: Set this to the StoreKit implementation your app uses for purchases. + /// I.e.: if your app uses `StoreKit 1`, set it to ``Configuration/StoreKitVersion/storeKit1`` + /// and if your app uses `StoreKit 2`, set it to ``Configuration/StoreKitVersion/storeKit2`` + /// Apps using `StoreKit 1` use `SKPaymentQueue` for transactions, + /// whereas `StoreKit 2` uses `StoreKit.Product.Purchase`. + /// - Important: This configuration flag has been deprecated, and will be replaced by automatic remote configuration in the future. + /// However, apps using it should work correctly. + /// + @available(*, deprecated, message: """ + RevenueCat currently uses StoreKit 1 for purchases, as its stability in production scenarios has + proven to be more performant than StoreKit 2. + + We're collecting more data on the best approach, but StoreKit 1 vs StoreKit 2 is an implementation detail + that you shouldn't need to care about. + + Simply remove this method call to let RevenueCat decide for you which StoreKit implementation to use. + """) + @objc func with(storeKitVersion: Configuration.StoreKitVersion) -> Configuration.Builder { + self.storeKit2Setting = .init(version: storeKitVersion) + return self + } + /** * Set `observerMode`. * - Parameter observerMode: Set this to `true` if you have your own IAP implementation and want to use only * RevenueCat's backend. Default is `false`. * * - Warning: This assumes your IAP implementation uses StoreKit 1. - * If you use StoreKit 2, use ``with(observerMode:storeKit2:)`` instead. + * If you use StoreKit 2, use ``with(observerMode:storeKitVersion:)`` instead. */ @available(*, deprecated, message: "", renamed: "with(observerMode:storeKit2:)") @objc func with(observerMode: Bool) -> Configuration.Builder { - return self.with(observerMode: observerMode, storeKit2: false) + return self.with(observerMode: observerMode, storeKitVersion: .storeKit1) } } diff --git a/Sources/Misc/StoreKit2Setting.swift b/Sources/Misc/StoreKit2Setting.swift index 8025adbded..dbfe111673 100644 --- a/Sources/Misc/StoreKit2Setting.swift +++ b/Sources/Misc/StoreKit2Setting.swift @@ -34,6 +34,15 @@ extension StoreKit2Setting { : .enabledOnlyForOptimizations } + init(version: Configuration.StoreKitVersion) { + switch version { + case .storeKit1: + self = .enabledOnlyForOptimizations + case .storeKit2: + self = .enabledForCompatibleDevices + } + } + static let `default`: Self = .enabledOnlyForOptimizations } diff --git a/Sources/Purchasing/Configuration.swift b/Sources/Purchasing/Configuration.swift index 73b699b3be..251167ddd2 100644 --- a/Sources/Purchasing/Configuration.swift +++ b/Sources/Purchasing/Configuration.swift @@ -37,6 +37,17 @@ import Foundation */ @objc(RCConfiguration) public final class Configuration: NSObject { + /// The StoreKit version that the SDK is setup with. + @objc(RCConfigurationStoreKitVersion) + public enum StoreKitVersion: Int { + + // swiftlint:disable missing_docs + case storeKit1 = 1 + case storeKit2 = 2 + // swiftlint:enable missing_docs + + } + static let storeKitRequestTimeoutDefault: TimeInterval = 30 static let networkTimeoutDefault: TimeInterval = 60 @@ -126,14 +137,18 @@ import Foundation } /** - * Set `observerMode` with a corresponding `StoreKit 2` setting. + * Set `observerMode` with a corresponding `StoreKit` implementation. * - Parameter observerMode: Set this to `true` if you have your own IAP implementation and want to use only * RevenueCat's backend. Default is `false`. - * - Parameter storeKit2: Set this to `true` if your own IAP implementation uses StoreKit 2. Default is `false`. + * - Parameter storeKitVersion: Set this to the StoreKit implementation your app uses for purchases. + * I.e.: if your app uses `StoreKit 1`, set it to ``Configuration/StoreKitVersion/storeKit1`` + * and if your app uses `StoreKit 2`, set it to ``Configuration/StoreKitVersion/storeKit2`` + * Apps using `StoreKit 1` use `SKPaymentQueue` for transactions, + * whereas `StoreKit 2` uses `StoreKit.Product.Purchase`. */ - @objc public func with(observerMode: Bool, storeKit2: Bool) -> Builder { + @objc public func with(observerMode: Bool, storeKitVersion: StoreKitVersion) -> Builder { self.observerMode = observerMode - self.storeKit2Setting = .init(useStoreKit2IfAvailable: storeKit2) + self.storeKit2Setting = .init(version: storeKitVersion) return self } diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 6d75d7323c..fe8514674c 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -1140,7 +1140,7 @@ public extension Purchases { * - Returns: An instantiated ``Purchases`` object that has been set as a singleton. * * - Note: This assumes your IAP implementation uses StoreKit 1. - * If you use StoreKit 2, use ``Configuration/Builder/with(observerMode:storeKit2:)`` instead. + * If you use StoreKit 2, use ``Configuration/Builder/with(observerMode:storeKitVersion:)`` instead. */ @objc(configureWithAPIKey:appUserID:observerMode:) @discardableResult static func configure(withAPIKey apiKey: String, @@ -1150,7 +1150,7 @@ public extension Purchases { with: Configuration .builder(withAPIKey: apiKey) .with(appUserID: appUserID) - .with(observerMode: observerMode, storeKit2: false) + .with(observerMode: observerMode, storeKitVersion: .storeKit1) .build() ) } diff --git a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m index 4e787350f8..a41468e137 100644 --- a/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m +++ b/Tests/APITesters/ObjCAPITester/ObjCAPITester/RCConfigurationAPI.m @@ -13,17 +13,19 @@ @implementation RCConfigurationAPI + (void)checkAPI { RCConfigurationBuilder *builder = [RCConfiguration builderWithAPIKey:@""]; - RCConfiguration *config __unused = [[[[[[[[[[[[builder withApiKey:@""] - withObserverMode:false] - withObserverMode:true storeKit2:true] - withUserDefaults:NSUserDefaults.standardUserDefaults] - withAppUserID:@""] - withAppUserID:nil] - withDangerousSettings:[[RCDangerousSettings alloc] init]] - withNetworkTimeout:1] - withStoreKit1Timeout: 1] - withPlatformInfo:[[RCPlatformInfo alloc] initWithFlavor:@"" version:@""]] - withUsesStoreKit2IfAvailable:false] build]; + RCConfiguration *config __unused = [[[[[[[[[[[[[builder withApiKey:@""] + withObserverMode:false] + withObserverMode:true storeKitVersion:RCConfigurationStoreKitVersionStoreKit1] + withUserDefaults:NSUserDefaults.standardUserDefaults] + withAppUserID:@""] + withAppUserID:nil] + withDangerousSettings:[[RCDangerousSettings alloc] init]] + withNetworkTimeout:1] + withStoreKit1Timeout: 1] + withPlatformInfo:[[RCPlatformInfo alloc] initWithFlavor:@"" version:@""]] + withUsesStoreKit2IfAvailable:false] + withStoreKitVersion:RCConfigurationStoreKitVersionStoreKit2] + build]; if (@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)) { RCConfiguration *config __unused = [[builder @@ -31,4 +33,12 @@ + (void)checkAPI { } } ++ (void)checkStoreKitVersion:(RCConfigurationStoreKitVersion)version { + switch (version) { + case RCConfigurationStoreKitVersionStoreKit1: + case RCConfigurationStoreKitVersionStoreKit2: + break; + } +} + @end diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift index 119725bc40..982c5ca0df 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/ConfigurationAPI.swift @@ -14,7 +14,8 @@ func checkConfigurationAPI() { .with(apiKey: "") .with(appUserID: "") .with(appUserID: nil) - .with(observerMode: false, storeKit2: true) + .with(observerMode: false, storeKitVersion: .storeKit1) + .with(observerMode: true, storeKitVersion: .storeKit2) .with(userDefaults: UserDefaults.standard) .with(dangerousSettings: DangerousSettings()) .with(dangerousSettings: DangerousSettings(autoSyncPurchases: true)) @@ -31,9 +32,19 @@ func checkConfigurationAPI() { } } +func checkStoreKitVersion(_ version: Configuration.StoreKitVersion) { + switch version { + case .storeKit1: break + case .storeKit2: break + @unknown default: break + } +} + @available(*, deprecated) func checkDeprecatedConfiguration(_ builder: Configuration.Builder) { _ = builder .with(usesStoreKit2IfAvailable: false) + .with(storeKitVersion: .storeKit1) + .with(storeKitVersion: .storeKit2) .with(observerMode: true) } diff --git a/Tests/UnitTests/Misc/StoreKit2SettingTests.swift b/Tests/UnitTests/Misc/StoreKit2SettingTests.swift index 7382d3b179..d43bdd6b39 100644 --- a/Tests/UnitTests/Misc/StoreKit2SettingTests.swift +++ b/Tests/UnitTests/Misc/StoreKit2SettingTests.swift @@ -28,6 +28,14 @@ class StoreKit2SettingTests: TestCase { expect(StoreKit2Setting(useStoreKit2IfAvailable: false).usesStoreKit2IfAvailable) == false } + func testInitWithStoreKit1() { + expect(StoreKit2Setting(version: .storeKit1)) == .enabledOnlyForOptimizations + } + + func testInitWithStoreKit2() { + expect(StoreKit2Setting(version: .storeKit2)) == .enabledForCompatibleDevices + } + func testStoreKit2NotAvailableWhenDisabled() { expect(StoreKit2Setting.disabled.shouldOnlyUseStoreKit2) == false } diff --git a/Tests/UnitTests/Purchasing/ConfigurationTests.swift b/Tests/UnitTests/Purchasing/ConfigurationTests.swift index d4cefb8b28..4ae9b4d642 100644 --- a/Tests/UnitTests/Purchasing/ConfigurationTests.swift +++ b/Tests/UnitTests/Purchasing/ConfigurationTests.swift @@ -43,18 +43,18 @@ class ConfigurationTests: TestCase { expect(configuration.storeKit2Setting) == .enabledOnlyForOptimizations } - func testObserverModeWithStoreKit2Disabled() { + func testObserverModeWithStoreKit1() { let configuration = Configuration.Builder(withAPIKey: "test") - .with(observerMode: true, storeKit2: false) + .with(observerMode: true, storeKitVersion: .storeKit1) .build() expect(configuration.observerMode) == true expect(configuration.storeKit2Setting) == .enabledOnlyForOptimizations } - func testObserverModeWithStoreKit2Enabled() { + func testObserverModeWithStoreKit2() { let configuration = Configuration.Builder(withAPIKey: "test") - .with(observerMode: true, storeKit2: true) + .with(observerMode: true, storeKitVersion: .storeKit2) .build() expect(configuration.observerMode) == true diff --git a/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift b/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift index 94bff208a5..934ec76469 100644 --- a/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift +++ b/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift @@ -505,7 +505,7 @@ class PurchasesConfiguringTests: BasePurchasesTests { private static func create(observerMode: Bool) -> Purchases { return Purchases.configure( with: .init(withAPIKey: "") - .with(observerMode: observerMode, storeKit2: false) + .with(observerMode: observerMode, storeKitVersion: .storeKit1) ) }