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

Remove presentCodeRedemptionSheet from PaymentQueueWrapper & Use SK2 APIs #4378

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
10 changes: 10 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,9 @@
F5FCD3EA27DA0D0B003BDC04 /* PriceFormatterProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5FCD3E927DA0D0B003BDC04 /* PriceFormatterProvider.swift */; };
F5FCD3FC27DA2034003BDC04 /* PriceFormatterProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5FCD3FB27DA2034003BDC04 /* PriceFormatterProviderTests.swift */; };
FD18ED4E2837F89200C5AA4F /* StoreKitWorkaroundsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD18ED4D2837F89200C5AA4F /* StoreKitWorkaroundsTests.swift */; };
FD20472A2CC19DCD00166727 /* OfferCodeRedemptionSheetPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2047292CC19DCD00166727 /* OfferCodeRedemptionSheetPresenter.swift */; };
FD2047462CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2047452CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift */; };
FD2047472CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2047452CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift */; };
FD2E6C9F2C480FF000CB4BD7 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = FD2E6C9E2C480FF000CB4BD7 /* OHHTTPStubs */; };
FD2E6CA12C48100900CB4BD7 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD2E6CA02C48100900CB4BD7 /* OHHTTPStubsSwift */; };
FD43D2FC2C41864000077235 /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43D2FA2C4185B700077235 /* TimeInterval+Extensions.swift */; };
Expand Down Expand Up @@ -2202,6 +2205,8 @@
F5FCD3E927DA0D0B003BDC04 /* PriceFormatterProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceFormatterProvider.swift; sourceTree = "<group>"; };
F5FCD3FB27DA2034003BDC04 /* PriceFormatterProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceFormatterProviderTests.swift; sourceTree = "<group>"; };
FD18ED4D2837F89200C5AA4F /* StoreKitWorkaroundsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitWorkaroundsTests.swift; sourceTree = "<group>"; };
FD2047292CC19DCD00166727 /* OfferCodeRedemptionSheetPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfferCodeRedemptionSheetPresenter.swift; sourceTree = "<group>"; };
FD2047452CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOfferCodeSheetPresenter.swift; sourceTree = "<group>"; };
FD43D2FA2C4185B700077235 /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = "<group>"; };
FD43D2FD2C41867600077235 /* TimeInterval+ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+ExtensionsTests.swift"; sourceTree = "<group>"; };
FDAADFCA2BE2A5BF00BD1659 /* MockAllTransactionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAllTransactionsProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2472,6 +2477,7 @@
2D1015D9275959840086173F /* StoreTransaction.swift */,
2D1015DD275A57FC0086173F /* SubscriptionPeriod.swift */,
4F174F462B07EA7E00FE538E /* StorefrontProvider.swift */,
FD2047292CC19DCD00166727 /* OfferCodeRedemptionSheetPresenter.swift */,
);
path = StoreKitAbstractions;
sourceTree = "<group>";
Expand Down Expand Up @@ -3199,6 +3205,7 @@
35316DA82BD14BFD00E4A970 /* MockDiagnosticsSynchronizer.swift */,
FDAADFCA2BE2A5BF00BD1659 /* MockAllTransactionsProvider.swift */,
FDAADFD22BE2B99900BD1659 /* MockStoreKit2ObserverModePurchaseDetector.swift */,
FD2047452CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -5448,6 +5455,7 @@
A563F589271E1DAD00246E0C /* MockBeginRefundRequestHelper.swift in Sources */,
57F3C10C29B7EAE90004FD7E /* MockUserDefaults.swift in Sources */,
35316DAA2BD14BFD00E4A970 /* MockDiagnosticsSynchronizer.swift in Sources */,
FD2047472CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift in Sources */,
2D90F8B426FD208B009B9142 /* MockStoreKit1Wrapper.swift in Sources */,
FD9F982D2BE28A7F0091A5BF /* MockNotificationCenter.swift in Sources */,
2D90F8BB26FD20BD009B9142 /* MockIdentityManager.swift in Sources */,
Expand Down Expand Up @@ -5552,6 +5560,7 @@
4FBBC5682A61E42F0077281F /* NonEmptyStringDecodable.swift in Sources */,
2DDF41A324F6F331005BC22D /* PurchasesReceiptParser.swift in Sources */,
2CB8CF9327BF538F00C34DE3 /* PlatformInfo.swift in Sources */,
FD20472A2CC19DCD00166727 /* OfferCodeRedemptionSheetPresenter.swift in Sources */,
35D832F4262E606500E60AC5 /* HTTPResponse.swift in Sources */,
352B7D7927BD919B002A47DD /* DangerousSettings.swift in Sources */,
A56F9AB126990E9200AFC48F /* CustomerInfo.swift in Sources */,
Expand Down Expand Up @@ -5921,6 +5930,7 @@
351B51C226D450E800BD2BD7 /* ProductRequestDataTests.swift in Sources */,
4F54DF422A1D8D0700FD72BF /* MockTransactionPoster.swift in Sources */,
4FFFE6C62AA9465000B2955C /* MockPaywallEventsManager.swift in Sources */,
FD2047462CC2D62C00166727 /* MockOfferCodeSheetPresenter.swift in Sources */,
351B51BE26D450E800BD2BD7 /* CustomerInfoTests.swift in Sources */,
35272E1B26D0029300F22C3B /* DeviceCacheSubscriberAttributesTests.swift in Sources */,
5796A39627D6BDAB00653165 /* BackendPostOfferForSigningTests.swift in Sources */,
Expand Down
24 changes: 24 additions & 0 deletions Sources/Logging/Strings/StoreKitStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ enum StoreKitStrings {

case error_displaying_store_message(Error)

case error_displaying_offer_code_redemption_sheet(Error)

case not_displaying_offer_code_redemption_sheet_because_ios_app_on_macos

case error_displaying_offer_code_redemption_sheet_no_window_scene

case error_displaying_offer_code_redemption_sheet_unavailable_in_app_extension

}

extension StoreKitStrings: LogMessage {
Expand Down Expand Up @@ -200,6 +208,22 @@ extension StoreKitStrings: LogMessage {

case let .error_displaying_store_message(error):
return "Error displaying StoreKit message: '\(error)'"

case let .error_displaying_offer_code_redemption_sheet(error):
return "Error displaying Offer Code redemption sheet: '\(error)'"

case .error_displaying_offer_code_redemption_sheet_no_window_scene:
return "Could not display the Offer Code redemption sheet: could not " +
"determine the UIWindowScene to present the sheet over. Please try " +
"passing in the UIWindowScene that you'd like to present the sheet over."

case .error_displaying_offer_code_redemption_sheet_unavailable_in_app_extension:
return "Could not display the Offer Code redemption sheet: unavailable in iOS and Mac Catalyst" +
"App Extensions below iOS/Mac Catalyst 16."
fire-at-will marked this conversation as resolved.
Show resolved Hide resolved

case .not_displaying_offer_code_redemption_sheet_because_ios_app_on_macos:
return "Not displaying the Offer Code redemption sheet because this is an iOS app running on macOS," +
"and displaying an Offer Code redemption sheet as a 'Designed for iPad' app on macOS throws an error."
fire-at-will marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
23 changes: 23 additions & 0 deletions Sources/Misc/Deprecations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,29 @@ public extension Purchases {

}

extension Purchases {
#if os(iOS) || VISION_OS
@available(
iOS,
introduced: 14.0,
deprecated,
renamed: "presentCodeRedemptionSheet(_:)",
message: "Use async/throwing version instead"
)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
@available(macOS, unavailable)
@available(macCatalyst, unavailable)
@objc public func presentCodeRedemptionSheet() {
Task {
#if !targetEnvironment(macCatalyst)
try await self.presentOfferCodeRedemptionSheet(uiWindowScene: nil)
#endif
}
}
#endif
}

public extension StoreProduct {

@available(iOS, introduced: 13.0, deprecated, renamed: "eligiblePromotionalOffers()")
Expand Down
44 changes: 39 additions & 5 deletions Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
fileprivate let systemInfo: SystemInfo
private let storeMessagesHelper: StoreMessagesHelperType?
private var customerInfoObservationDisposable: (() -> Void)?
private let offerCodeRedemptionSheetPresenter: OfferCodeRedemptionSheetPresenterType

private let syncAttributesAndOfferingsIfNeededRateLimiter = RateLimiter(maxCalls: 5, period: 60)

Expand Down Expand Up @@ -307,6 +308,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void

let purchasedProductsFetcher = OfflineCustomerInfoCreator.createPurchasedProductsFetcherIfAvailable()
let transactionFetcher = StoreKit2TransactionFetcher()
let offerCodeRedemptionSheetPresenter = OfferCodeRedemptionSheetPresenter()

let diagnosticsFileHandler: DiagnosticsFileHandlerType? = {
guard diagnosticsEnabled, #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) else { return nil }
Expand Down Expand Up @@ -570,7 +572,8 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
purchasesOrchestrator: purchasesOrchestrator,
purchasedProductsFetcher: purchasedProductsFetcher,
trialOrIntroPriceEligibilityChecker: trialOrIntroPriceChecker,
storeMessagesHelper: storeMessagesHelper
storeMessagesHelper: storeMessagesHelper,
offerCodeRedemptionSheetPresenter: offerCodeRedemptionSheetPresenter
)
}

Expand Down Expand Up @@ -599,7 +602,8 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
purchasesOrchestrator: PurchasesOrchestrator,
purchasedProductsFetcher: PurchasedProductsFetcherType?,
trialOrIntroPriceEligibilityChecker: CachingTrialOrIntroPriceEligibilityChecker,
storeMessagesHelper: StoreMessagesHelperType?
storeMessagesHelper: StoreMessagesHelperType?,
offerCodeRedemptionSheetPresenter: OfferCodeRedemptionSheetPresenterType
) {

if systemInfo.dangerousSettings.customEntitlementComputation {
Expand Down Expand Up @@ -647,6 +651,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
self.purchasedProductsFetcher = purchasedProductsFetcher
self.trialOrIntroPriceEligibilityChecker = trialOrIntroPriceEligibilityChecker
self.storeMessagesHelper = storeMessagesHelper
self.offerCodeRedemptionSheetPresenter = offerCodeRedemptionSheetPresenter

super.init()

Expand Down Expand Up @@ -1062,15 +1067,44 @@ public extension Purchases {
}
#endif

#if os(iOS) || VISION_OS
#if (os(iOS) || VISION_OS) && !targetEnvironment(macCatalyst)

@available(iOS 14.0, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
@available(macOS, unavailable)
@available(macCatalyst 16.0, *)
@objc func presentCodeRedemptionSheet(
uiWindowScene: UIWindowScene? = nil
) async throws {
try await self.presentOfferCodeRedemptionSheet(uiWindowScene: uiWindowScene)
}

@available(iOS 14.0, *)
@available(macCatalyst, unavailable)
@objc func presentCodeRedemptionSheet() {
self.paymentQueueWrapper.paymentQueueWrapperType.presentCodeRedemptionSheet()
@available(watchOS, unavailable)
@available(tvOS, unavailable)
@available(macOS, unavailable)
internal func presentOfferCodeRedemptionSheet(uiWindowScene: UIWindowScene?) async throws {
var windowScene = uiWindowScene
if windowScene == nil {
windowScene = try await systemInfo.currentWindowScene
}

guard let windowScene else {
Logger.error(Strings.storeKit.error_displaying_offer_code_redemption_sheet_no_window_scene)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a little awkward that this is possible, but I thought it was important to keep the provided window scene optional for two reasons:

  1. So that developers using SwiftUI can use easily the API
  2. To keep the new Purchases.presentCodeRedemptionSheet API as similar as possible to the deprecated one

return
}

do {
try await self.offerCodeRedemptionSheetPresenter.presentCodeRedemptionSheet(
windowScene: windowScene,
storeKitVersion: self.systemInfo.storeKitVersion
)
} catch {
Logger.error(Strings.storeKit.error_displaying_offer_code_redemption_sheet(error))
throw error
}
}
#endif

Expand Down
56 changes: 40 additions & 16 deletions Sources/Purchasing/Purchases/PurchasesType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,28 @@ public protocol PurchasesType: AnyObject {

#if os(iOS) || VISION_OS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the presentCodeRedemptionSheet function moved inside this conditional intentionally? I think this might be a breaking change since it changes where the function is compiled (even if unavailable) so you cannot use runtime availability checks with it.


/**
* Displays a sheet that enables users to redeem subscription offer codes that you generated in App Store Connect.
*
* - Important: Even though the docs in `SKPaymentQueue.presentCodeRedemptionSheet`
* say that it's available on Catalyst 14.0, there is a note:
* "`This function doesn’t affect Mac apps built with Mac Catalyst.`"
* when, in fact, it crashes when called both from Catalyst and also when running as "Designed for iPad".
* This is why RevenueCat's SDK makes it unavailable in mac catalyst.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean calling this function of the SDK has been crashing on Catalyst and iOS apps running on macOS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Catalyst, nope! Luckily the old version of presentCodeRedemptionSheet() was marked as unavailable on Catalyst (see the definition in the current main branch here.

For "Designed for iPad" on macOS, I haven't tested it myself, but I wouldn't be surprised if the existing SDK function is crashing, since this PR introduces the isiOSAppOnMac check for the first time in the code redemption sheet flow

@available(
iOS,
introduced: 14.0,
deprecated,
renamed: "presentCodeRedemptionSheet(_:)",
message: "Use async/throwing version instead"
)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
@available(macOS, unavailable)
@available(macCatalyst, unavailable)
func presentCodeRedemptionSheet()

/**
* Presents a refund request sheet in the current window scene for
* the latest transaction associated with the `productID`
Expand Down Expand Up @@ -736,22 +758,6 @@ public protocol PurchasesType: AnyObject {

#endif

/**
* Displays a sheet that enables users to redeem subscription offer codes that you generated in App Store Connect.
*
* - Important: Even though the docs in `SKPaymentQueue.presentCodeRedemptionSheet`
* say that it's available on Catalyst 14.0, there is a note:
* "`This function doesn’t affect Mac apps built with Mac Catalyst.`"
* when, in fact, it crashes when called both from Catalyst and also when running as "Designed for iPad".
* This is why RevenueCat's SDK makes it unavailable in mac catalyst.
*/
@available(iOS 14.0, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
@available(macOS, unavailable)
@available(macCatalyst, unavailable)
func presentCodeRedemptionSheet()

#if os(iOS) || targetEnvironment(macCatalyst) || VISION_OS
/**
* Displays price consent sheet if needed. You only need to call this manually if you implement
Expand All @@ -771,6 +777,24 @@ public protocol PurchasesType: AnyObject {
*/
@available(iOS 13.4, macCatalyst 13.4, *)
@objc func showPriceConsentIfNeeded()

/**
* Displays a sheet that enables users to redeem subscription offer codes that you generated in App Store Connect.
*
* - Important: Even though the docs in `AppStore.presentOfferCodeRedeemSheet(in:)`
* say that it's available on Catalyst 16.0+, there is a note:
* "`In Mac apps built with Mac Catalyst, this method throws a StoreKitError.unknown error.`".
* The function also throws a StoreKitError.unknown error when running as "Designed for iPad" on macOS.
*
* For these reasons, RevenueCat's SDK makes this function unavailable on Mac Catalyst, and it no-ops
* for iOS apps running as a "Designed for iPad" app on macOS.
*/
@available(iOS 14.0, *)
@available(macCatalyst, unavailable)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
@available(macOS, unavailable)
func presentCodeRedemptionSheet(uiWindowScene: UIWindowScene?) async throws
#endif

#if os(iOS) || os(macOS) || VISION_OS
Expand Down
14 changes: 0 additions & 14 deletions Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ protocol PaymentQueueWrapperType: AnyObject {
func showPriceConsentIfNeeded()
#endif

@available(iOS 14.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(macCatalyst, unavailable)
func presentCodeRedemptionSheet()

}

/// The choice between SK1's `StoreKit1Wrapper` or `PaymentQueueWrapper` when SK2 is enabled.
Expand Down Expand Up @@ -92,13 +85,6 @@ class PaymentQueueWrapper: NSObject, PaymentQueueWrapperType {
}
#endif

#if (os(iOS) && !targetEnvironment(macCatalyst)) || VISION_OS
@available(iOS 14.0, *)
func presentCodeRedemptionSheet() {
self.paymentQueue.presentCodeRedemptionSheetIfAvailable()
}
#endif

}

extension PaymentQueueWrapper: SKPaymentQueueDelegate {
Expand Down
Loading