mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
Similarly to #2262, test execution ordering was leading to [flaky code coverage results](https://app.codecov.io/gh/RevenueCat/purchases-ios/pull/2262) with this.  Because we didn't have any test coverage, only if some other test changed the `Storefront`, this test was calling the delegate code. This adds explicit test coverage using a `MockAsyncSequence`. ### Other changes: - Added `StorefrontType` to `StoreKit2StorefrontListenerDelegate` method - `Storefront` initializers are no longer `Optional` `init?` - Simplified `StoreKit2StorefrontListenerTests` by moving `AvailabilityChecks` to `setUp`
152 lines
5.1 KiB
Swift
152 lines
5.1 KiB
Swift
//
|
|
// 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
|
|
//
|
|
// Storefront.swift
|
|
//
|
|
// Created by Nacho Soto on 4/13/22.
|
|
|
|
import Foundation
|
|
import StoreKit
|
|
|
|
/// An object containing the location and unique identifier of an Apple App Store storefront.
|
|
///
|
|
/// - Note: Don't save the storefront information with your user information; storefront information can change
|
|
/// at any time. Always get the storefront identifier immediately before you display product information or availability
|
|
/// to the user in your app. Storefront information may not be used to develop or enhance a user profile,
|
|
/// or track customers for advertising or marketing purposes.
|
|
@objc(RCStorefront)
|
|
public final class Storefront: NSObject, StorefrontType {
|
|
|
|
private let storefront: StorefrontType
|
|
|
|
init(_ storefront: StorefrontType) {
|
|
self.storefront = storefront
|
|
|
|
super.init()
|
|
}
|
|
|
|
// Note: this class inherits its docs from `StorefrontType`
|
|
// swiftlint:disable missing_docs
|
|
|
|
@objc public var countryCode: String { self.storefront.countryCode }
|
|
@objc public var identifier: String { self.storefront.identifier }
|
|
|
|
// swiftlint:enable missing_docs
|
|
|
|
// MARK: -
|
|
|
|
/// Creates an instance from any `StorefrontType`.
|
|
/// If `storefront` is already a wrapped `Storefront` then this returns it instead.
|
|
static func from(storefront: StorefrontType) -> Storefront {
|
|
return storefront as? Storefront
|
|
?? Storefront(storefront)
|
|
}
|
|
|
|
public override func isEqual(_ object: Any?) -> Bool {
|
|
guard let other = object as? StorefrontType else { return false }
|
|
|
|
return self.identifier == other.identifier
|
|
}
|
|
|
|
public override var hash: Int {
|
|
return self.identifier.hashValue
|
|
}
|
|
|
|
public override var description: String {
|
|
return """
|
|
<\(String(describing: Storefront.self)):
|
|
identifier=\(self.identifier),
|
|
countryCode=\(countryCode)
|
|
>
|
|
"""
|
|
}
|
|
|
|
}
|
|
|
|
extension Storefront: Sendable {}
|
|
|
|
public extension Storefront {
|
|
|
|
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, macCatalyst 13.1, *)
|
|
private static var currentStorefrontType: StorefrontType? {
|
|
get async {
|
|
if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) {
|
|
let sk2Storefront = await StoreKit.Storefront.current
|
|
return sk2Storefront.map(SK2Storefront.init)
|
|
} else {
|
|
return Self.sk1CurrentStorefrontType
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, macCatalyst 13.1, *)
|
|
private static var sk1CurrentStorefrontType: StorefrontType? {
|
|
return SKPaymentQueue.default().storefront.map(SK1Storefront.init)
|
|
}
|
|
|
|
/// The current App Store storefront for the device.
|
|
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, macCatalyst 13.1, *)
|
|
static var currentStorefront: Storefront? {
|
|
get async {
|
|
return await self.currentStorefrontType.map(Storefront.from(storefront: ))
|
|
}
|
|
}
|
|
|
|
/// The current App Store storefront for the device obtained from StoreKit 1 only.
|
|
@available(swift, obsoleted: 0.0.1, renamed: "currentStorefront")
|
|
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, macCatalyst 13.1, *)
|
|
@objc static var sk1CurrentStorefront: Storefront? {
|
|
return self.sk1CurrentStorefrontType.map(Storefront.from(storefront: ))
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
/// A type containing the location and unique identifier of an Apple App Store storefront.
|
|
internal protocol StorefrontType: Sendable {
|
|
|
|
/// The three-letter code representing the country or region
|
|
/// associated with the App Store storefront.
|
|
/// - Note: This property uses the ISO 3166-1 Alpha-3 country code representation.
|
|
var countryCode: String { get }
|
|
|
|
/// A value defined by Apple that uniquely identifies an App Store storefront.
|
|
var identifier: String { get }
|
|
|
|
}
|
|
|
|
// MARK: - Wrapper constructors / getters
|
|
|
|
extension Storefront {
|
|
|
|
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, macCatalyst 13.1, *)
|
|
internal convenience init(sk1Storefront: SKStorefront) {
|
|
self.init(SK1Storefront(sk1Storefront))
|
|
}
|
|
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
internal convenience init(sk2Storefront: StoreKit.Storefront) {
|
|
self.init(SK2Storefront(sk2Storefront))
|
|
}
|
|
|
|
/// Returns the `SKStorefront` if this `Storefront` represents an `SKStorefront`.
|
|
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, macCatalyst 13.1, *)
|
|
@objc public var sk1Storefront: SKStorefront? {
|
|
return (self.storefront as? SK1Storefront)?.underlyingSK1Storefront
|
|
}
|
|
|
|
/// Returns the `StoreKit.Storefront` if this `Storefront` represents a `StoreKit.Storefront`.
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
public var sk2Storefront: StoreKit.Storefront? {
|
|
return (self.storefront as? SK2Storefront)?.underlyingSK2Storefront
|
|
}
|
|
|
|
}
|