Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS15/SK2/ObserverMode Improvements #4148

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Sources/Misc/StoreKitVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
35 changes: 28 additions & 7 deletions Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -685,6 +682,30 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
}
}

private static func buildPaymentQueueWrapper(
systemInfo: SystemInfo,
operationDispatcher: OperationDispatcher
) -> EitherPaymentQueueWrapper {
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
// 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
Expand Down
48 changes: 33 additions & 15 deletions Sources/Purchasing/Purchases/TransactionPoster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,30 +257,48 @@ private extension TransactionPoster {

func fetchEncodedReceipt(transaction: StoreTransactionType,
completion: @escaping (Result<EncodedAppleReceipt, BackendError>) -> 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<Void, Never> {
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 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
// 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
Expand Down