Skip to content

Commit

Permalink
Add Configuration.with(storeKitVersion:) to select the version of Sto…
Browse files Browse the repository at this point in the history
…reKit to use
  • Loading branch information
MarkVillacampa committed Dec 13, 2023
1 parent 32c63cd commit 4886d77
Show file tree
Hide file tree
Showing 599 changed files with 867 additions and 51 deletions.
4 changes: 4 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
4D6ABB0E2AF13FB100BB2A08 /* StoreEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6ABB0D2AF13FB100BB2A08 /* StoreEnvironment.swift */; };
4D6ABB102AF13FBD00BB2A08 /* SK2AppTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6ABB0F2AF13FBD00BB2A08 /* SK2AppTransaction.swift */; };
4D72E8622B221EA600BF9EFE /* StoreEnvironmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D24EF3F2B04EA6000E586D2 /* StoreEnvironmentTests.swift */; };
4DBC30962B1DFA97001D33C7 /* StoreKitVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DBC30952B1DFA97001D33C7 /* StoreKitVersion.swift */; };
4DC546272AD44BBE005CDB35 /* EncodedAppleReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC546262AD44BBE005CDB35 /* EncodedAppleReceipt.swift */; };
4F0201C42A13C85500091612 /* Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0201C32A13C85500091612 /* Assertions.swift */; };
4F05876F2A5DE03F00E9A834 /* PaywallDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F05876E2A5DE03F00E9A834 /* PaywallDataTests.swift */; };
Expand Down Expand Up @@ -998,6 +999,7 @@
4D6ABB0B2AF13F9400BB2A08 /* StoreKit2Receipt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreKit2Receipt.swift; sourceTree = "<group>"; };
4D6ABB0D2AF13FB100BB2A08 /* StoreEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreEnvironment.swift; sourceTree = "<group>"; };
4D6ABB0F2AF13FBD00BB2A08 /* SK2AppTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SK2AppTransaction.swift; sourceTree = "<group>"; };
4DBC30952B1DFA97001D33C7 /* StoreKitVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitVersion.swift; sourceTree = "<group>"; };
4DC546262AD44BBE005CDB35 /* EncodedAppleReceipt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncodedAppleReceipt.swift; sourceTree = "<group>"; };
4F0201C32A13C85500091612 /* Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assertions.swift; sourceTree = "<group>"; };
4F05876E2A5DE03F00E9A834 /* PaywallDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallDataTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1788,6 +1790,7 @@
57ABA76C28F08DDA003D9181 /* Either.swift */,
F530E4FE275646EF001AF6BD /* MacDevice.swift */,
57EAE52A274332830060EB74 /* Obsoletions.swift */,
4DBC30952B1DFA97001D33C7 /* StoreKitVersion.swift */,
2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */,
F5FCD3E927DA0D0B003BDC04 /* PriceFormatterProvider.swift */,
57FDAAB9284937A0009A48F1 /* SandboxEnvironmentDetector.swift */,
Expand Down Expand Up @@ -3611,6 +3614,7 @@
57C2931528BFEF4F0054EDFC /* PurchasesError.swift in Sources */,
57FD7B1528DA4037009CA4E4 /* PurchasesType.swift in Sources */,
57C381DA2796153D009E3940 /* SK1StoreProductDiscount.swift in Sources */,
4DBC30962B1DFA97001D33C7 /* StoreKitVersion.swift in Sources */,
57DE807328074C76008D6C6F /* SK2Storefront.swift in Sources */,
57A17727276A721D0052D3A8 /* Set+Extensions.swift in Sources */,
4DC546272AD44BBE005CDB35 /* EncodedAppleReceipt.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Logging/Strings/ConfigureStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ enum ConfigureStrings {

case response_verification_mode(Signing.ResponseVerificationMode)

case storekit_version(String)

case delegate_set

case purchase_instance_already_set
Expand Down Expand Up @@ -111,6 +113,8 @@ extension ConfigureStrings: LogMessage {
case .enforced:
return "Purchases is configured with enforced response verification"
}
case let .storekit_version(version):
return "Purchases is configured with StoreKit version \(version)"
case .delegate_set:
return "Delegate set"
case .purchase_instance_already_set:
Expand Down
13 changes: 1 addition & 12 deletions Sources/Misc/DangerousSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import Foundation
internal struct Internal: InternalDangerousSettingsType {

let enableReceiptFetchRetry: Bool
let usesStoreKit2JWS: Bool

#if DEBUG
let forceServerErrors: Bool
Expand All @@ -25,24 +24,20 @@ import Foundation

init(
enableReceiptFetchRetry: Bool = false,
usesStoreKit2JWS: Bool = false,
forceServerErrors: Bool = false,
forceSignatureFailures: Bool = false,
testReceiptIdentifier: String? = nil
) {
self.enableReceiptFetchRetry = enableReceiptFetchRetry
self.usesStoreKit2JWS = usesStoreKit2JWS
self.forceServerErrors = forceServerErrors
self.forceSignatureFailures = forceSignatureFailures
self.testReceiptIdentifier = testReceiptIdentifier
}
#else
init(
enableReceiptFetchRetry: Bool = false,
usesStoreKit2JWS: Bool = false
enableReceiptFetchRetry: Bool = false
) {
self.enableReceiptFetchRetry = enableReceiptFetchRetry
self.usesStoreKit2JWS = usesStoreKit2JWS
}
#endif

Expand Down Expand Up @@ -121,12 +116,6 @@ internal protocol InternalDangerousSettingsType: Sendable {
/// Whether `ReceiptFetcher` can retry fetching receipts.
var enableReceiptFetchRetry: Bool { get }

/**
* Controls whether StoreKit 2 JWS tokens are sent to RevenueCat instead of StoreKit 1 receipts.
* Must be used in conjunction with the `usesStoreKit2IfAvailable configuration` option.
*/
var usesStoreKit2JWS: Bool { get }

#if DEBUG
/// Whether `HTTPClient` will fake server errors
var forceServerErrors: Bool { get }
Expand Down
1 change: 1 addition & 0 deletions Sources/Misc/Deprecations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public extension Purchases {
platformInfo: nil,
responseVerificationMode: .default,
storeKit2Setting: .init(useStoreKit2IfAvailable: useStoreKit2IfAvailable),
storeKitVersion: .default,
storeKitTimeout: Configuration.storeKitRequestTimeoutDefault,
networkTimeout: Configuration.networkTimeoutDefault,
dangerousSettings: dangerousSettings,
Expand Down
63 changes: 63 additions & 0 deletions Sources/Misc/StoreKitVersion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// StoreKitVersion.swift
//
// Created by Mark Villacampa on 4/13/23.

import Foundation

/// Defines which version of StoreKit may be used
@objc(RCStoreKitVersion)
public enum StoreKitVersion: Int {

/// Always use StoreKit 1. StoreKit 2 will be used (if available in the current device) only for certain APIs
/// that provide a better implementation. For example: intro eligibility, determining if a receipt has
/// purchases, managing subscriptions.
case storeKit1

/// Always use StoreKit 2.
case storeKit2

/// Let RevenueCat use the most appropiate version of StoreKit
case `default`
}

extension StoreKitVersion {

/// - Returns: `true` if SK2 is available in this device.
static var isStoreKit2Available: Bool {
if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
return true
} else {
return false
}
}

/// - Returns: `true` if and only if SK2 is enabled and it's available.
var isStoreKit2EnabledAndAvailable: Bool {
switch self {
case .storeKit1, .default: return false
case .storeKit2: return Self.isStoreKit2Available
}
}

}

extension StoreKitVersion {

var versionString: String {
if self.isStoreKit2EnabledAndAvailable {
return "2"
} else {
return "1"
}
}

}
3 changes: 3 additions & 0 deletions Sources/Misc/SystemInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SystemInfo {
}

let storeKit2Setting: StoreKit2Setting
let storeKitVersion: StoreKitVersion
let operationDispatcher: OperationDispatcher
let platformFlavor: String
let platformFlavorVersion: String?
Expand Down Expand Up @@ -125,6 +126,7 @@ class SystemInfo {
sandboxEnvironmentDetector: SandboxEnvironmentDetector = BundleSandboxEnvironmentDetector.default,
storefrontProvider: StorefrontProviderType = DefaultStorefrontProvider(),
storeKit2Setting: StoreKit2Setting = .default,
storeKitVersion: StoreKitVersion = .default,
responseVerificationMode: Signing.ResponseVerificationMode = .default,
dangerousSettings: DangerousSettings? = nil,
clock: ClockType = Clock.default) {
Expand All @@ -135,6 +137,7 @@ class SystemInfo {
self._finishTransactions = .init(finishTransactions)
self.operationDispatcher = operationDispatcher
self.storeKit2Setting = storeKit2Setting
self.storeKitVersion = storeKitVersion
self.sandboxEnvironmentDetector = sandboxEnvironmentDetector
self.storefrontProvider = storefrontProvider
self.responseVerificationMode = responseVerificationMode
Expand Down
1 change: 1 addition & 0 deletions Sources/Networking/HTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class HTTPClient {
"X-Client-Build-Version": SystemInfo.buildVersion,
"X-Client-Bundle-ID": SystemInfo.bundleIdentifier,
"X-StoreKit2-Enabled": "\(self.systemInfo.storeKit2Setting.isEnabledAndAvailable)",
"X-StoreKit-Version": "\(self.systemInfo.storeKitVersion.versionString)",
"X-Observer-Mode-Enabled": "\(self.systemInfo.observerMode)",
RequestHeader.sandbox.rawValue: "\(self.systemInfo.isSandbox)"
]
Expand Down
24 changes: 24 additions & 0 deletions Sources/Purchasing/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import Foundation
let observerMode: Bool
let userDefaults: UserDefaults?
let storeKit2Setting: StoreKit2Setting
let storeKitVersion: StoreKitVersion
let dangerousSettings: DangerousSettings?
let networkTimeout: TimeInterval
let storeKit1Timeout: TimeInterval
Expand All @@ -61,6 +62,7 @@ import Foundation
self.observerMode = builder.observerMode
self.userDefaults = builder.userDefaults
self.storeKit2Setting = builder.storeKit2Setting
self.storeKitVersion = builder.storeKitVersion
self.dangerousSettings = builder.dangerousSettings
self.storeKit1Timeout = builder.storeKit1Timeout
self.networkTimeout = builder.networkTimeout
Expand Down Expand Up @@ -92,6 +94,7 @@ import Foundation
private(set) var platformInfo: Purchases.PlatformInfo?
private(set) var responseVerificationMode: Signing.ResponseVerificationMode = .default
private(set) var showStoreMessagesAutomatically: Bool = true
private(set) var storeKitVersion: StoreKitVersion = .default

/**
* Create a new builder with your API key.
Expand Down Expand Up @@ -218,6 +221,27 @@ import Foundation
return self
}

/// Set ``StoreKitVersion``.
///
/// Defaults to ``StoreKitVersion/default`` which lets the SDK select
/// the most appropriate version of StoreKit. Currently defaults to StoreKit 1.
///
/// - Note: StoreKit 2 is only available on iOS 15+. StoreKit 1 will be used for previous iOS versions
/// regardless of this setting.
///
/// ### Related Symbols
/// - ``StoreKitVersion``
@objc public func with(storeKitVersion version: StoreKitVersion) -> Builder {
self.storeKitVersion = version
switch version {
case .storeKit1, .default:
self.storeKit2Setting = .init(useStoreKit2IfAvailable: false)
case .storeKit2:
self.storeKit2Setting = .init(useStoreKit2IfAvailable: true)
}
return self
}

/// Generate a ``Configuration`` object given the values configured by this builder.
@objc public func build() -> Configuration {
return Configuration(with: self)
Expand Down
6 changes: 6 additions & 0 deletions Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
platformInfo: PlatformInfo? = Purchases.platformInfo,
responseVerificationMode: Signing.ResponseVerificationMode,
storeKit2Setting: StoreKit2Setting = .default,
storeKitVersion: StoreKitVersion = .default,
storeKitTimeout: TimeInterval = Configuration.storeKitRequestTimeoutDefault,
networkTimeout: TimeInterval = Configuration.networkTimeoutDefault,
dangerousSettings: DangerousSettings? = nil,
Expand All @@ -282,6 +283,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
finishTransactions: !observerMode,
operationDispatcher: operationDispatcher,
storeKit2Setting: storeKit2Setting,
storeKitVersion: storeKitVersion,
responseVerificationMode: responseVerificationMode,
dangerousSettings: dangerousSettings)

Expand Down Expand Up @@ -560,6 +562,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
Logger.debug(Strings.configure.is_simulator(SystemInfo.isRunningInSimulator), fileName: nil)
Logger.user(Strings.configure.initial_app_user_id(isSet: appUserID != nil), fileName: nil)
Logger.debug(Strings.configure.response_verification_mode(systemInfo.responseVerificationMode), fileName: nil)
Logger.debug(Strings.configure.storekit_version(systemInfo.storeKitVersion.versionString), fileName: nil)

self.requestFetcher = requestFetcher
self.receiptFetcher = receiptFetcher
Expand Down Expand Up @@ -1161,6 +1164,7 @@ public extension Purchases {
platformInfo: configuration.platformInfo,
responseVerificationMode: configuration.responseVerificationMode,
storeKit2Setting: configuration.storeKit2Setting,
storeKitVersion: configuration.storeKitVersion,
storeKitTimeout: configuration.storeKit1Timeout,
networkTimeout: configuration.networkTimeout,
dangerousSettings: configuration.dangerousSettings,
Expand Down Expand Up @@ -1332,6 +1336,7 @@ public extension Purchases {
platformInfo: PlatformInfo?,
responseVerificationMode: Signing.ResponseVerificationMode,
storeKit2Setting: StoreKit2Setting,
storeKitVersion: StoreKitVersion,
storeKitTimeout: TimeInterval,
networkTimeout: TimeInterval,
dangerousSettings: DangerousSettings?,
Expand All @@ -1346,6 +1351,7 @@ public extension Purchases {
platformInfo: platformInfo,
responseVerificationMode: responseVerificationMode,
storeKit2Setting: storeKit2Setting,
storeKitVersion: storeKitVersion,
storeKitTimeout: storeKitTimeout,
networkTimeout: networkTimeout,
dangerousSettings: dangerousSettings,
Expand Down
4 changes: 2 additions & 2 deletions Sources/Purchasing/Purchases/PurchasesOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ final class PurchasesOrchestrator {
return
}

if self.systemInfo.dangerousSettings.internalSettings.usesStoreKit2JWS,
if self.systemInfo.storeKitVersion == .storeKit2,
#available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) {
self.sk2PromotionalOffer(forProductDiscount: productDiscount,
discountIdentifier: discountIdentifier,
Expand Down Expand Up @@ -1002,7 +1002,7 @@ private extension PurchasesOrchestrator {
Logger.warn(Strings.purchase.restorepurchases_called_with_allow_sharing_appstore_account_false)
}

if self.systemInfo.dangerousSettings.internalSettings.usesStoreKit2JWS,
if self.systemInfo.storeKitVersion == .storeKit2,
#available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) {
self.syncPurchasesSK2(isRestore: isRestore,
initiationSource: initiationSource,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Purchasing/Purchases/TransactionPoster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ private extension TransactionPoster {

func fetchEncodedReceipt(transaction: StoreTransactionType,
completion: @escaping (Result<EncodedAppleReceipt, BackendError>) -> Void) {
if systemInfo.dangerousSettings.internalSettings.usesStoreKit2JWS,
if systemInfo.storeKitVersion == .storeKit2,
let jwsRepresentation = transaction.jwsRepresentation {
if transaction.environment == .xcode, #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
_ = Task<Void, Never> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ @implementation RCConfigurationAPI

+ (void)checkAPI {
RCConfigurationBuilder *builder = [RCConfiguration builderWithAPIKey:@""];
RCConfiguration *config __unused = [[[[[[[[[[[builder withApiKey:@""]
RCConfiguration *config __unused = [[[[[[[[[[[[builder withApiKey:@""]
withObserverMode:false]
withUserDefaults:NSUserDefaults.standardUserDefaults]
withAppUserID:@""]
Expand All @@ -23,6 +23,7 @@ + (void)checkAPI {
withStoreKit1Timeout: 1]
withPlatformInfo:[[RCPlatformInfo alloc] initWithFlavor:@"" version:@""]]
withUsesStoreKit2IfAvailable:false]
withStoreKitVersion:RCStoreKitVersionStoreKit2]
build];

if (@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func checkConfigurationAPI() {
.with(networkTimeout: 1)
.with(storeKit1Timeout: 1)
.with(platformInfo: Purchases.PlatformInfo(flavor: "", version: ""))
.with(storeKitVersion: .default)

let _: Configuration = builder.build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class BaseBackendIntegrationTests: TestCase {
// MARK: - Overridable configuration

class var storeKit2Setting: StoreKit2Setting { return .default }
class var storeKitVersion: StoreKitVersion { return .default }
class var observerMode: Bool { return false }
class var responseVerificationMode: Signing.ResponseVerificationMode {
return .enforced(Signing.loadPublicKey())
Expand All @@ -79,6 +80,7 @@ class BaseBackendIntegrationTests: TestCase {
platformInfo: nil,
responseVerificationMode: Self.responseVerificationMode,
storeKit2Setting: Self.storeKit2Setting,
storeKitVersion: Self.storeKitVersion,
storeKitTimeout: Configuration.storeKitRequestTimeoutDefault,
networkTimeout: Configuration.networkTimeoutDefault,
dangerousSettings: self.dangerousSettings,
Expand Down Expand Up @@ -237,7 +239,6 @@ private extension BaseBackendIntegrationTests {

extension BaseBackendIntegrationTests: InternalDangerousSettingsType {

var usesStoreKit2JWS: Bool { false }
var forceServerErrors: Bool { return self.serverIsDown }
var forceSignatureFailures: Bool { return false }
var testReceiptIdentifier: String? { return self.testUUID.uuidString }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class LoadShedderStoreKit2IntegrationTests: LoadShedderStoreKit1IntegrationTests

class LoadShedderStoreKit2JWSIntegrationTests: LoadShedderStoreKit1IntegrationTests {

override var usesStoreKit2JWS: Bool { return true }
override class var storeKitVersion: StoreKitVersion { .storeKit2 }

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class OfflineStoreKit2IntegrationTests: OfflineStoreKit1IntegrationTests {

class OfflineStoreKit2JWSIntegrationTests: OfflineStoreKit1IntegrationTests {

override var usesStoreKit2JWS: Bool { return true }
override class var storeKitVersion: StoreKitVersion { .storeKit2 }

}

Expand Down
Loading

0 comments on commit 4886d77

Please sign in to comment.