mirror of
https://github.com/confirmedcode/Lockdown-iOS.git
synced 2025-12-21 12:14:02 +01:00
532 lines
24 KiB
Swift
532 lines
24 KiB
Swift
//
|
|
// VPNSubscription.swift
|
|
// Lockdown
|
|
//
|
|
// Copyright © 2018 Confirmed, Inc. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import SwiftyStoreKit
|
|
import PromiseKit
|
|
import CocoaLumberjackSwift
|
|
import StoreKit
|
|
|
|
struct OneTimeProducts: ToList {
|
|
let weekly: String
|
|
let weeklyTrial: String
|
|
let yearly: String
|
|
let yearlyTrial: String
|
|
}
|
|
|
|
struct SpecialOfferProducts: ToList {
|
|
let yearly: String
|
|
}
|
|
|
|
struct FeedbackProducts: ToList {
|
|
let weekly: String
|
|
let yearly: String
|
|
}
|
|
|
|
protocol ToList {
|
|
func toList() -> [String]
|
|
}
|
|
|
|
extension ToList {
|
|
func toList() -> [String] {
|
|
let otherSelf = Mirror(reflecting: self)
|
|
return otherSelf.children.compactMap {
|
|
$0.value as? String
|
|
}
|
|
}
|
|
}
|
|
|
|
struct InternalSubscription: Hashable {
|
|
let productId: String
|
|
let period: SKProduct.PeriodUnit
|
|
let trialDuration: String?
|
|
let priceLocale: Locale
|
|
let price: NSDecimalNumber
|
|
let offer: NSDecimalNumber?
|
|
|
|
static var mockWeekly: InternalSubscription {
|
|
InternalSubscription(productId: "lockdown.weekly.1.202408.no_trial.4hrs_offer", period: .week, trialDuration: nil, priceLocale: .current, price: 0.99, offer: nil)
|
|
}
|
|
|
|
static var mockWeeklyTrial: InternalSubscription {
|
|
InternalSubscription(productId: "lockdown.weekly.1.202408.3_days_free_trial.4hrs_offer", period: .week, trialDuration: "3 days", priceLocale: .current, price: 0.99, offer: nil)
|
|
}
|
|
|
|
static var mockYearly: InternalSubscription {
|
|
InternalSubscription(productId: "lockdown.yearly.40.202408.no_trial.4hrs_offer_", period: .year, trialDuration: nil, priceLocale: .current, price: 39.99, offer: nil)
|
|
}
|
|
|
|
static var mockYearlTrial: InternalSubscription {
|
|
InternalSubscription(productId: "lockdown.yearly.40.202408.3_days_free_trial.4hrs_offer", period: .year, trialDuration: "3 days", priceLocale: .current, price: 39.99, offer: nil)
|
|
}
|
|
|
|
static var mockYearlyBF: InternalSubscription {
|
|
InternalSubscription(productId: "lockdown.yearly.30.202412.1_year_no_trial.4h_screen_holiday", period: .year, trialDuration: nil, priceLocale: .current, price: 99.99, offer: 29.99)
|
|
}
|
|
}
|
|
|
|
enum SubscriptionState: Int {
|
|
case Uninitialized = 1, Subscribed, NotSubscribed
|
|
}
|
|
|
|
actor VPNSubscription: NSObject {
|
|
|
|
enum SubscriptionType {
|
|
case oneTime
|
|
case feedback
|
|
case specialOffer
|
|
|
|
var productIds: [String] {
|
|
switch self {
|
|
case .oneTime: VPNSubscription.oneTimeProducts.toList()
|
|
case .feedback: VPNSubscription.feedbackProducts.toList()
|
|
case .specialOffer: VPNSubscription.specialOfferProducts.toList()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var cachedSubscriptions: [SubscriptionType: [InternalSubscription]] = [:]
|
|
|
|
static var shared = VPNSubscription()
|
|
|
|
static let productIdAdvancedMonthly = "LockdowniOSFirewallMonthly"
|
|
static let productIdAdvancedYearly = "LockdowniOSFirewallAnnual"
|
|
static let productIdMonthly = "LockdowniOSVpnMonthly"
|
|
static let productIdAnnual = "LockdowniOSVpnAnnual"
|
|
static let productIdMonthlyPro = "LockdowniOSVpnMonthlyPro"
|
|
static let productIdAnnualPro = "LockdowniOSVpnAnnualPro"
|
|
static let productIds: Set = [productIdAdvancedMonthly, productIdAdvancedYearly, productIdMonthly, productIdAnnual, productIdMonthlyPro, productIdAnnualPro]
|
|
static let oneTimeProducts = OneTimeProducts(weekly: "lockdown.weekly.1.202408.no_trial.4hrs_offer",
|
|
weeklyTrial: "lockdown.weekly.1.202408.3_days_free_trial.4hrs_offer",
|
|
yearly: "lockdown.yearly.40.202408.no_trial.4hrs_offer_",
|
|
yearlyTrial: "lockdown.yearly.40.202408.3_days_free_trial.4hrs_offer")
|
|
static let feedbackProducts = FeedbackProducts(weekly: "lockdown.weekly.1.202409.no_trial.feedback",
|
|
yearly: "lockdown.yearly.40.202409.no_trial.feedback")
|
|
static let specialOfferProducts = SpecialOfferProducts(yearly: "lockdown.yearly.30.202412.1_year_no_trial.4h_screen_holiday")
|
|
static var selectedProductId = productIdAdvancedMonthly
|
|
|
|
// Advanced Level
|
|
static var defaultPriceStringAdvancedMonthly = "$4.99"
|
|
static var defaultPriceStringAdvancedYearly = "$29.99"
|
|
static var defaultPriceSubStringAdvancedYearly = "$2.49"
|
|
static var defaultUpgradePriceStringAdvancedMonthly = "$4.99"
|
|
static var defaultUpgradePriceStringAdvancedYearly = "$29.99"
|
|
|
|
// Anonymous Level
|
|
static var defaultPriceStringMonthly = "$8.99"
|
|
static var defaultPriceStringAnnual = "$59.99"
|
|
static var defaultPriceSubStringAnnual = "$4.99"
|
|
static var defaultUpgradePriceStringAnnual = "$59.99"
|
|
static var defaultUpgradePriceStringMonthly = "$8.99"
|
|
|
|
|
|
// Universal Level
|
|
static var defaultPriceStringMonthlyPro = "$11.99"
|
|
static var defaultPriceStringAnnualPro = "$99.99"
|
|
static var defaultPriceStringAnnualPro70Off = "$29.99"
|
|
static var defaultPriceSubStringAnnualPro = "$8.33"
|
|
static var defaultUpgradePriceStringAnnualPro = "$99.99"
|
|
static var defaultUpgradePriceStringMonthlyPro = "$11.99"
|
|
|
|
@discardableResult
|
|
func loadSubscriptions(type: SubscriptionType) async -> [InternalSubscription]? {
|
|
if let subscriptions = cachedSubscriptions[type], !subscriptions.isEmpty {
|
|
return subscriptions
|
|
}
|
|
cachedSubscriptions[type] = await _loadSubscriptions(productIds: Set(type.productIds))
|
|
return cachedSubscriptions[type]
|
|
}
|
|
|
|
private func _loadSubscriptions(productIds: Set<String>) async -> [InternalSubscription]? {
|
|
DDLogInfo("cache localized price for productIds: \(productIds)")
|
|
let currencyFormatter = NumberFormatter()
|
|
currencyFormatter.usesGroupingSeparator = true
|
|
currencyFormatter.numberStyle = .currency
|
|
|
|
return await withCheckedContinuation { continuation in
|
|
SwiftyStoreKit.retrieveProductsInfo(productIds) { result in
|
|
DDLogInfo("retrieve products results: \(result)")
|
|
var subs = [InternalSubscription]()
|
|
for product in result.retrievedProducts {
|
|
|
|
let period = Self.subscriptionPeriod(product: product)
|
|
let trialDuration = Self.trialDuraion(for: product.introductoryPrice)
|
|
currencyFormatter.locale = product.priceLocale
|
|
if let period {
|
|
let ip = InternalSubscription(productId: product.productIdentifier,
|
|
period: period,
|
|
trialDuration: trialDuration,
|
|
priceLocale: product.priceLocale,
|
|
price: product.price,
|
|
offer: product.price)
|
|
subs.append(ip)
|
|
}
|
|
}
|
|
continuation.resume(returning: subs)
|
|
}
|
|
}
|
|
}
|
|
private override init() {
|
|
super.init()
|
|
}
|
|
|
|
static func purchase(succeeded: @escaping () -> Void, errored: @escaping (Error) -> Void) {
|
|
DDLogInfo("purchase")
|
|
SwiftyStoreKit.purchaseProduct(selectedProductId, atomically: true) { result in
|
|
switch result {
|
|
case .success:
|
|
firstly {
|
|
try Client.signIn()
|
|
}
|
|
.then { (signin: SignIn) -> Promise<GetKey> in
|
|
try Client.getKey()
|
|
}
|
|
.done { (getKey: GetKey) in
|
|
try setVPNCredentials(id: getKey.id, keyBase64: getKey.b64)
|
|
BaseUserService.shared.user.resetCache()
|
|
succeeded()
|
|
}
|
|
.catch { error in
|
|
errored(error)
|
|
}
|
|
case .error(let error):
|
|
DDLogError("purchase error: \(error)")
|
|
errored(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
static func subscriptionPeriod(product: SKProduct) -> SKProduct.PeriodUnit? {
|
|
guard let subscriptionPeriod = product.subscriptionPeriod else {
|
|
return nil
|
|
}
|
|
|
|
return subscriptionPeriod.unit
|
|
}
|
|
|
|
static func setTrialDuration(productId: String, duration: String?) {
|
|
let trialKey = productId + "Trial"
|
|
|
|
guard let duration else {
|
|
UserDefaults.standard.removeObject(forKey: trialKey)
|
|
DDLogInfo("Unavaible trial for \(productId)")
|
|
return
|
|
}
|
|
DDLogInfo("Setting trial a duration \(duration) for \(productId)")
|
|
UserDefaults.standard.set(duration, forKey: trialKey)
|
|
}
|
|
|
|
static func setProductIdPrice(productId: String, price: String) {
|
|
DDLogInfo("Setting product id price \(price) for \(productId)")
|
|
UserDefaults.standard.set(price, forKey: productId + "Price")
|
|
}
|
|
|
|
static func setProductIdUpgradePrice(productId: String, upgradePrice: String) {
|
|
DDLogInfo("Setting product id upgrade price \(upgradePrice) for \(productId)")
|
|
UserDefaults.standard.set(upgradePrice, forKey: productId + "UpgradePrice")
|
|
}
|
|
|
|
static func setProductIdPriceAnnualMonthly(productId: String, price: String) {
|
|
DDLogInfo("Setting product id price yearly per month \(price) for \(productId)")
|
|
UserDefaults.standard.set(price, forKey: productId + "MonthlyPrice")
|
|
}
|
|
|
|
static func setProductIdUpgradePriceAnnualMonthly(productId: String, price: String) {
|
|
DDLogInfo("Setting product id upgrade price yearly per month\(price) for \(productId)")
|
|
UserDefaults.standard.set(price, forKey: productId + "MonthlyUpgradePrice")
|
|
}
|
|
|
|
enum SubscriptionContext {
|
|
case new
|
|
case upgrade
|
|
case monthlyNew
|
|
case monthlyUpgrade
|
|
}
|
|
|
|
static func getProductIdPrice(productId: String, for context: SubscriptionContext) -> String {
|
|
switch context {
|
|
case .new:
|
|
return getProductIdPrice(productId: productId)
|
|
case .upgrade:
|
|
return getProductIdUpgradePrice(productId: productId)
|
|
case .monthlyNew:
|
|
return getProductIdPrice(productId: productId)
|
|
case .monthlyUpgrade:
|
|
return getProductIdUpgradePrice(productId: productId)
|
|
}
|
|
}
|
|
|
|
static func getProductIdPriceMonthly(productId: String) -> String {
|
|
DDLogInfo("Getting product id price yearly per month \(productId)")
|
|
if let price = UserDefaults.standard.string(forKey: productId + "MonthlyPrice") {
|
|
DDLogInfo("Got product id price yearly per month for \(productId): \(price)")
|
|
return price
|
|
}
|
|
else {
|
|
DDLogError("Found no cached price yearly per month for productId \(productId), returning default")
|
|
switch productId {
|
|
case productIdAdvancedYearly:
|
|
return defaultPriceSubStringAdvancedYearly
|
|
case productIdAnnual:
|
|
return defaultPriceSubStringAnnual
|
|
case productIdAnnualPro:
|
|
return defaultPriceSubStringAnnualPro
|
|
default:
|
|
DDLogError("Invalid product Id: \(productId)")
|
|
return "Invalid Price"
|
|
}
|
|
}
|
|
}
|
|
|
|
static func trialDuration(productId: String) -> String? {
|
|
UserDefaults.standard.string(forKey: productId + "Trial")
|
|
}
|
|
|
|
static func getProductIdPrice(productId: String) -> String {
|
|
DDLogInfo("Getting product id price for \(productId)")
|
|
if let price = UserDefaults.standard.string(forKey: productId + "Price") {
|
|
DDLogInfo("Got product id price for \(productId): \(price)")
|
|
return price
|
|
}
|
|
else {
|
|
DDLogError("Found no cached price for productId \(productId), returning default")
|
|
switch productId {
|
|
case productIdAdvancedMonthly:
|
|
return defaultPriceStringAdvancedMonthly
|
|
case productIdAdvancedYearly:
|
|
return defaultPriceStringAdvancedYearly
|
|
case productIdMonthly:
|
|
return defaultPriceStringMonthly
|
|
case productIdMonthlyPro:
|
|
return defaultPriceStringMonthlyPro
|
|
case productIdAnnual:
|
|
return defaultPriceStringAnnual
|
|
case productIdAnnualPro:
|
|
return defaultPriceStringAnnualPro
|
|
default:
|
|
DDLogError("Invalid product Id: \(productId)")
|
|
return "Invalid Price"
|
|
}
|
|
}
|
|
}
|
|
|
|
static func getProductIdUpgradePrice(productId: String) -> String {
|
|
DDLogInfo("Getting product id upgrade price for \(productId)")
|
|
if let upgradePrice = UserDefaults.standard.string(forKey: productId + "UpgradePrice") {
|
|
DDLogInfo("Got product id upgrade price for \(productId): \(upgradePrice)")
|
|
return upgradePrice
|
|
}
|
|
else {
|
|
DDLogError("Found no cached upgrade price for productId \(productId), returning default")
|
|
switch productId {
|
|
case productIdAdvancedMonthly:
|
|
return defaultUpgradePriceStringAdvancedMonthly
|
|
case productIdAdvancedYearly:
|
|
return defaultUpgradePriceStringAdvancedYearly
|
|
case productIdMonthly:
|
|
return defaultUpgradePriceStringMonthly
|
|
case productIdMonthlyPro:
|
|
return defaultUpgradePriceStringMonthlyPro
|
|
case productIdAnnual:
|
|
return defaultUpgradePriceStringAnnual
|
|
case productIdAnnualPro:
|
|
return defaultUpgradePriceStringAnnualPro
|
|
default:
|
|
DDLogError("Invalid product Id: \(productId)")
|
|
return "Invalid Upgrade Price"
|
|
}
|
|
}
|
|
}
|
|
|
|
static func cacheLocalizedPrices() -> Void {
|
|
|
|
let currencyFormatter = NumberFormatter()
|
|
currencyFormatter.usesGroupingSeparator = true
|
|
currencyFormatter.numberStyle = .currency
|
|
|
|
DDLogInfo("cache localized price for productIds: \(productIds)")
|
|
SwiftyStoreKit.retrieveProductsInfo(productIds) { result in
|
|
DDLogInfo("retrieve products results: \(result)")
|
|
for product in result.retrievedProducts {
|
|
DDLogInfo("product locale: \(product.priceLocale)")
|
|
DDLogInfo("productprice: \(product.localizedPrice ?? "n/a")")
|
|
|
|
if product.productIdentifier == productIdAdvancedMonthly {
|
|
if product.localizedPrice != nil {
|
|
DDLogInfo("setting productIdAdvancedMonthly display price = " + product.localizedPrice!)
|
|
setProductIdPrice(productId: productIdAdvancedMonthly, price: "\(product.localizedPrice!)")
|
|
setProductIdUpgradePrice(productId: productIdAdvancedMonthly, upgradePrice: "\(product.localizedPrice!)")
|
|
}
|
|
else {
|
|
DDLogError("monthly nil localizedPrice, setting default")
|
|
setProductIdPrice(productId: productIdAdvancedMonthly, price: defaultPriceStringAdvancedMonthly)
|
|
setProductIdUpgradePrice(productId: productIdAdvancedMonthly, upgradePrice: defaultUpgradePriceStringAdvancedMonthly)
|
|
}
|
|
}
|
|
else if product.productIdentifier == productIdAdvancedYearly {
|
|
if product.localizedPrice != nil {
|
|
currencyFormatter.locale = product.priceLocale
|
|
let priceMonthly = product.price.dividing(by: 12)
|
|
if let priceString = currencyFormatter.string(from: priceMonthly) {
|
|
setProductIdPriceAnnualMonthly(productId: productIdAdvancedYearly, price: priceString)
|
|
DDLogInfo("setting productIdAdvancedAnnualMonthly display price = " + priceString)
|
|
}
|
|
DDLogInfo("setting productIdAdvancedYearly display price = " + product.localizedPrice!)
|
|
setProductIdPrice(productId: productIdAdvancedYearly, price: "\(product.localizedPrice!)")
|
|
setProductIdUpgradePrice(productId: productIdAdvancedYearly, upgradePrice: "\(product.localizedPrice!)")
|
|
}
|
|
else {
|
|
DDLogError("monthly nil localizedPrice, setting default")
|
|
setProductIdPrice(productId: productIdAdvancedYearly, price: defaultPriceStringAdvancedYearly)
|
|
setProductIdUpgradePrice(productId: productIdAdvancedYearly, upgradePrice: defaultUpgradePriceStringAdvancedYearly)
|
|
setProductIdPriceAnnualMonthly(productId: productIdAdvancedYearly, price: defaultPriceSubStringAdvancedYearly)
|
|
}
|
|
}
|
|
else if product.productIdentifier == productIdAnnual {
|
|
if product.localizedPrice != nil {
|
|
currencyFormatter.locale = product.priceLocale
|
|
let priceMonthly = product.price.dividing(by: 12)
|
|
if let priceString = currencyFormatter.string(from: priceMonthly) {
|
|
setProductIdPriceAnnualMonthly(productId: productIdAnnual, price: priceString)
|
|
DDLogInfo("setting productIdAnnualAnnualMonthly display price = " + priceString)
|
|
}
|
|
DDLogInfo("setting productIdAnnualAnnual display price = annual product price / 12 = " + product.localizedPrice!)
|
|
setProductIdPrice(productId: productIdAnnual, price: "\(product.localizedPrice!)")
|
|
setProductIdUpgradePrice(productId: productIdAnnual, upgradePrice: "\(product.localizedPrice!)")
|
|
}
|
|
|
|
else {
|
|
DDLogError("unable to format price with currencyformatter: " + product.price.stringValue)
|
|
setProductIdPrice(productId: productIdAnnual, price: defaultPriceStringAnnual)
|
|
setProductIdUpgradePrice(productId: productIdAnnual, upgradePrice: defaultUpgradePriceStringAnnual)
|
|
setProductIdPriceAnnualMonthly(productId: productIdAnnual, price: defaultPriceSubStringAnnual)
|
|
}
|
|
}
|
|
else if product.productIdentifier == productIdMonthly {
|
|
if product.localizedPrice != nil {
|
|
DDLogInfo("setting productIdMonthly display price = " + product.localizedPrice!)
|
|
setProductIdPrice(productId: productIdMonthly, price: "\(product.localizedPrice!)")
|
|
setProductIdUpgradePrice(productId: productIdMonthly, upgradePrice: "\(product.localizedPrice!)")
|
|
}
|
|
else {
|
|
DDLogError("monthly nil localizedPrice, setting default")
|
|
setProductIdPrice(productId: productIdMonthly, price: defaultPriceStringMonthly)
|
|
setProductIdUpgradePrice(productId: productIdMonthly, upgradePrice: defaultUpgradePriceStringMonthly)
|
|
}
|
|
}
|
|
else if product.productIdentifier == productIdMonthlyPro {
|
|
if product.localizedPrice != nil {
|
|
DDLogInfo("setting productIdMonthlyPro display price = " + product.localizedPrice!)
|
|
setProductIdPrice(productId: productIdMonthlyPro, price: "\(product.localizedPrice!)")
|
|
setProductIdUpgradePrice(productId: productIdMonthlyPro, upgradePrice: "\(product.localizedPrice!)")
|
|
}
|
|
else {
|
|
DDLogError("monthlyPro nil localizedPrice, setting default")
|
|
setProductIdPrice(productId: productIdMonthlyPro, price: defaultPriceStringMonthlyPro)
|
|
setProductIdUpgradePrice(productId: productIdMonthlyPro, upgradePrice: defaultUpgradePriceStringMonthlyPro)
|
|
}
|
|
}
|
|
else if product.productIdentifier == productIdAnnualPro {
|
|
if product.localizedPrice != nil {
|
|
currencyFormatter.locale = product.priceLocale
|
|
let priceMonthly = product.price.dividing(by: 12)
|
|
if let priceString = currencyFormatter.string(from: priceMonthly) {
|
|
DDLogInfo("setting productIdAnnualPro display price = annualPro product price / 12 = " + priceString)
|
|
setProductIdPriceAnnualMonthly(productId: productIdAnnualPro, price: priceString)
|
|
}
|
|
DDLogInfo("setting productIdAnnualPro display price = " + product.localizedPrice!)
|
|
setProductIdPrice(productId: productIdAnnualPro, price: "\(product.localizedPrice!)")
|
|
setProductIdUpgradePrice(productId: productIdAnnualPro, upgradePrice: "\(product.localizedPrice!)")
|
|
}
|
|
else {
|
|
DDLogError("unable to format price with currencyformatter: " + product.price.stringValue)
|
|
setProductIdPrice(productId: productIdAnnualPro, price: defaultPriceStringAnnualPro)
|
|
setProductIdUpgradePrice(productId: productIdAnnualPro, upgradePrice: defaultUpgradePriceStringAnnualPro)
|
|
setProductIdPriceAnnualMonthly(productId: productIdAnnualPro, price: defaultPriceSubStringAnnualPro)
|
|
}
|
|
}
|
|
setTrialDuration(productId: product.productIdentifier, duration: trialDuraion(for: product.introductoryPrice))
|
|
}
|
|
for invalidProductId in result.invalidProductIDs {
|
|
DDLogError("invalid product id: \(invalidProductId)");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func trialDuraion(for trial: SKProductDiscount?) -> String? {
|
|
guard let trial,
|
|
trial.paymentMode == .freeTrial else {
|
|
return nil
|
|
}
|
|
let unit = switch trial.subscriptionPeriod.unit {
|
|
case .day: NSLocalizedString("day", comment: "day")
|
|
case .week: NSLocalizedString("week", comment: "week")
|
|
case .month: NSLocalizedString("month", comment: "month")
|
|
case .year: NSLocalizedString("year", comment: "year")
|
|
}
|
|
return "\(trial.subscriptionPeriod.numberOfUnits)" + "-" + unit
|
|
}
|
|
}
|
|
|
|
extension Subscription.PlanType {
|
|
var productId: String? {
|
|
switch self {
|
|
case .advancedMonthly:
|
|
return VPNSubscription.productIdAdvancedMonthly
|
|
case .advancedAnnual:
|
|
return VPNSubscription.productIdAdvancedYearly
|
|
case .anonymousMonthly:
|
|
return VPNSubscription.productIdMonthly
|
|
case .anonymousAnnual:
|
|
return VPNSubscription.productIdAnnual
|
|
case .universalMonthly:
|
|
return VPNSubscription.productIdMonthlyPro
|
|
case .universalAnnual:
|
|
return VPNSubscription.productIdAnnualPro
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static var supported: [Subscription.PlanType] {
|
|
return [.advancedMonthly, .advancedAnnual, .anonymousMonthly, .anonymousAnnual, .universalMonthly, .universalAnnual]
|
|
}
|
|
|
|
var availableUpgrades: [Subscription.PlanType]? {
|
|
switch self {
|
|
case .advancedMonthly:
|
|
return [.advancedAnnual, .anonymousMonthly, .anonymousAnnual, .universalMonthly, .universalAnnual]
|
|
case .advancedAnnual:
|
|
return [.anonymousMonthly, .anonymousAnnual, .universalMonthly, .universalAnnual]
|
|
case .anonymousMonthly:
|
|
return [.anonymousAnnual, .universalMonthly, .universalAnnual]
|
|
case .anonymousAnnual:
|
|
return [.universalMonthly, .universalAnnual]
|
|
case .universalMonthly:
|
|
return [.universalAnnual]
|
|
case .universalAnnual:
|
|
return []
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var unavailableToUpgrade: [Subscription.PlanType]? {
|
|
guard let upgrades = availableUpgrades else {
|
|
return nil
|
|
}
|
|
|
|
var candidates = Subscription.PlanType.supported
|
|
candidates.removeAll(where: { upgrades.contains($0) })
|
|
return candidates
|
|
}
|
|
|
|
func canUpgrade(to newPlan: Subscription.PlanType) -> Bool {
|
|
return availableUpgrades?.contains(newPlan) == true
|
|
}
|
|
}
|