From b283a38460b5da3dc27629826dc1c6a109ce032e Mon Sep 17 00:00:00 2001 From: Will Taylor Date: Tue, 6 Aug 2024 13:39:06 -0500 Subject: [PATCH 1/3] fix paymentQueueWrapper logic on iOS15 observer mode --- Sources/Purchasing/Purchases/Purchases.swift | 36 ++++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index b4cef05fbb..7dfb35349a 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -332,13 +332,10 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void diagnosticsTracker: diagnosticsTracker ) - let paymentQueueWrapper: EitherPaymentQueueWrapper = systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailable - ? .right(.init()) - : .left(.init( - operationDispatcher: operationDispatcher, - observerMode: observerMode, - sandboxEnvironmentDetector: systemInfo - )) + let paymentQueueWrapper = Purchases.buildPaymentQueueWrapper( + systemInfo: systemInfo, + operationDispatcher: operationDispatcher + ) let offeringsFactory = OfferingsFactory() let receiptParser = PurchasesReceiptParser.default @@ -685,6 +682,31 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void } } + private static func buildPaymentQueueWrapper( + systemInfo: SystemInfo, + operationDispatcher: OperationDispatcher + ) -> EitherPaymentQueueWrapper { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), + systemInfo.observerMode && systemInfo.storeKitVersion == .storeKit2 + { + // On iOS 15, we allow developers to make purchases with SK2 when PurchasesAreCompletedBy=.myApp. + // We need to specifically check for this case because otherwise we use SK1 on iOS 15, and if + // We are on iOS 15 with PurchasesAreCompletedBy==.myApp and SK2, we need to only listen to purchases + // via SK2. + return .right(PaymentQueueWrapper()) + } + + let paymentQueueWrapper: EitherPaymentQueueWrapper = systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailable + ? .right(.init()) + : .left(.init( + operationDispatcher: operationDispatcher, + observerMode: systemInfo.observerMode, + sandboxEnvironmentDetector: systemInfo + )) + + return paymentQueueWrapper + } + /// - Parameter purchases: this is an `@autoclosure` to be able to clear the previous instance /// from memory before creating the new one. @discardableResult From 81771bca64118e6840cd655eb93c0a470fc7fea9 Mon Sep 17 00:00:00 2001 From: Will Taylor Date: Tue, 6 Aug 2024 13:49:38 -0500 Subject: [PATCH 2/3] POST SK2 receipt if on iOS15 + observer mode --- Sources/Purchasing/Purchases/Purchases.swift | 7 ++- .../Purchases/TransactionPoster.swift | 48 +++++++++++++------ 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 7dfb35349a..8cc7a98507 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -686,10 +686,9 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void systemInfo: SystemInfo, operationDispatcher: OperationDispatcher ) -> EitherPaymentQueueWrapper { - if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), - systemInfo.observerMode && systemInfo.storeKitVersion == .storeKit2 - { - // On iOS 15, we allow developers to make purchases with SK2 when PurchasesAreCompletedBy=.myApp. + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), + systemInfo.observerMode && systemInfo.storeKitVersion == .storeKit2 { + // On iOS 15, we allow developers to make purchases with SK2 when PurchasesAreCompletedBy==.myApp. // We need to specifically check for this case because otherwise we use SK1 on iOS 15, and if // We are on iOS 15 with PurchasesAreCompletedBy==.myApp and SK2, we need to only listen to purchases // via SK2. diff --git a/Sources/Purchasing/Purchases/TransactionPoster.swift b/Sources/Purchasing/Purchases/TransactionPoster.swift index 478dde946a..4e5d63dee3 100644 --- a/Sources/Purchasing/Purchases/TransactionPoster.swift +++ b/Sources/Purchasing/Purchases/TransactionPoster.swift @@ -257,30 +257,48 @@ private extension TransactionPoster { func fetchEncodedReceipt(transaction: StoreTransactionType, completion: @escaping (Result) -> Void) { - if systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailable, - let jwsRepresentation = transaction.jwsRepresentation { - if transaction.environment == .xcode, #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { + + func handleTransactionWithJWSRepresentation(jwsRepresentation: String) { + if transaction.environment == .xcode, #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { _ = Task { completion(.success( - .sk2receipt(await self.transactionFetcher.fetchReceipt(containing: transaction)) - )) + .sk2receipt(await self.transactionFetcher.fetchReceipt(containing: transaction))) + ) } } else { completion(.success(.jws(jwsRepresentation))) } - } else { - self.receiptFetcher.receiptData( - refreshPolicy: self.refreshRequestPolicy(forProductIdentifier: transaction.productIdentifier) - ) { receiptData, receiptURL in - if let receiptData = receiptData, !receiptData.isEmpty { - completion(.success(.receipt(receiptData))) - } else { - completion(.failure(BackendError.missingReceiptFile(receiptURL))) - } + } + + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), + systemInfo.observerMode && systemInfo.storeKitVersion == .storeKit2, + let jwsRepresentation = transaction.jwsRepresentation { + // On iOS 15, we allow developers to make purchases with SK2 when PurchasesAreCompletedBy==.myApp. + // In this case, we should POST the SK2 receipt despite running on iOS 15, where we would normally + // POST a SK1 receipt if PurchasesAreCompletedBy==.revenueCat + + handleTransactionWithJWSRepresentation(jwsRepresentation: jwsRepresentation) + return + } + + if systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailable, + let jwsRepresentation = transaction.jwsRepresentation { + + handleTransactionWithJWSRepresentation(jwsRepresentation: jwsRepresentation) + return + } + + // Fetch a SK1 receipt + self.receiptFetcher.receiptData( + refreshPolicy: self.refreshRequestPolicy(forProductIdentifier: transaction.productIdentifier) + ) { receiptData, receiptURL in + if let receiptData = receiptData, !receiptData.isEmpty { + completion(.success(.receipt(receiptData))) + } else { + completion(.failure(BackendError.missingReceiptFile(receiptURL))) } } } - } // MARK: - Properties From 307d5097e0afdd4edf930439eecff0471e09ffa7 Mon Sep 17 00:00:00 2001 From: Will Taylor Date: Tue, 6 Aug 2024 15:45:32 -0500 Subject: [PATCH 3/3] abstract logic to isStoreKit2EnabledAndAvailableWhenPurchasesAreCompletedByMyApp --- Sources/Misc/StoreKitVersion.swift | 15 +++++++++++++++ Sources/Purchasing/Purchases/Purchases.swift | 4 ++-- .../Purchasing/Purchases/TransactionPoster.swift | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Sources/Misc/StoreKitVersion.swift b/Sources/Misc/StoreKitVersion.swift index c985082768..f5bf8a6195 100644 --- a/Sources/Misc/StoreKitVersion.swift +++ b/Sources/Misc/StoreKitVersion.swift @@ -59,6 +59,21 @@ extension StoreKitVersion { } } + /// - Returns: `true` if SK2 is available and usable when PurchasesAreCompleted==.myApp on this device + // swiftlint:disable:next identifier_name + var isStoreKit2EnabledAndAvailableWhenPurchasesAreCompletedByMyApp: Bool { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { + switch self { + case .storeKit1: + return false + case .storeKit2: + return true + } + } else { + return false + } + } + /// - Returns: `true` if and only if SK2 is enabled and it's available. var isStoreKit2EnabledAndAvailable: Bool { switch self { diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 8cc7a98507..82fb8c1bd1 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -686,8 +686,8 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void systemInfo: SystemInfo, operationDispatcher: OperationDispatcher ) -> EitherPaymentQueueWrapper { - if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), - systemInfo.observerMode && systemInfo.storeKitVersion == .storeKit2 { + if systemInfo.observerMode && + systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailableWhenPurchasesAreCompletedByMyApp { // On iOS 15, we allow developers to make purchases with SK2 when PurchasesAreCompletedBy==.myApp. // We need to specifically check for this case because otherwise we use SK1 on iOS 15, and if // We are on iOS 15 with PurchasesAreCompletedBy==.myApp and SK2, we need to only listen to purchases diff --git a/Sources/Purchasing/Purchases/TransactionPoster.swift b/Sources/Purchasing/Purchases/TransactionPoster.swift index 4e5d63dee3..739418f1fc 100644 --- a/Sources/Purchasing/Purchases/TransactionPoster.swift +++ b/Sources/Purchasing/Purchases/TransactionPoster.swift @@ -270,8 +270,8 @@ private extension TransactionPoster { } } - if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *), - systemInfo.observerMode && systemInfo.storeKitVersion == .storeKit2, + if systemInfo.observerMode && + systemInfo.storeKitVersion.isStoreKit2EnabledAndAvailableWhenPurchasesAreCompletedByMyApp, let jwsRepresentation = transaction.jwsRepresentation { // On iOS 15, we allow developers to make purchases with SK2 when PurchasesAreCompletedBy==.myApp. // In this case, we should POST the SK2 receipt despite running on iOS 15, where we would normally