Files
lockdown-iOS-mirror/LockdowniOS/VPNSubscription.swift
Alexander Parshakov 2bc6adf847 Release 1.6.1
2023-01-10 21:17:38 +05:00

270 lines
13 KiB
Swift

//
// VPNSubscription.swift
// Lockdown
//
// Copyright © 2018 Confirmed, Inc. All rights reserved.
//
import UIKit
import SwiftyStoreKit
import PromiseKit
import CocoaLumberjackSwift
enum SubscriptionState: Int {
case Uninitialized = 1, Subscribed, NotSubscribed
}
class VPNSubscription: NSObject {
static let productIdMonthly = "LockdowniOSVpnMonthly"
static let productIdAnnual = "LockdowniOSVpnAnnual"
static let productIdMonthlyPro = "LockdowniOSVpnMonthlyPro"
static let productIdAnnualPro = "LockdowniOSVpnAnnualPro"
static let productIdAnnualProLTO = "LockdownProAnnualLTO"
static let productIds: Set = [productIdMonthly, productIdAnnual, productIdMonthlyPro, productIdAnnualPro, productIdAnnualProLTO]
static var selectedProductId = productIdMonthly
static var defaultPriceStringMonthly = "$7.99 per month after"
static var defaultPriceStringMonthlyPro = "$11.99 per month after"
static var defaultPriceStringAnnual = "$49.99/year after (~$4.17/month)"
static var defaultPriceStringAnnualPro = "$99.99/year after (~$8.33/month)"
static var defaultPriceStringAnnualProLTO = "$59.99/year after (~$8.33/month)"
static var defaultUpgradePriceStringMonthly = "$7.99 per month"
static var defaultUpgradePriceStringMonthlyPro = "$11.99 per month"
static var defaultUpgradePriceStringAnnual = "$49.99/year (~$4.17/month)"
static var defaultUpgradePriceStringAnnualPro = "$99.99/year (~$8.33/month)"
static var defaultUpgradePriceStringAnnualProLTO = "$59.99/year (~$4.99/month)"
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 { _ in
try Client.getKey()
}
.done { (getKey: GetKey) in
try setVPNCredentials(id: getKey.id, keyBase64: getKey.b64)
succeeded()
}
.catch { error in
errored(error)
}
case .error(let error):
DDLogError("purchase error: \(error)")
errored(error)
}
}
}
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")
}
enum SubscriptionContext {
case new
case upgrade
}
static func getProductIdPrice(productId: String, for context: SubscriptionContext) -> String {
switch context {
case .new:
return getProductIdPrice(productId: productId)
case .upgrade:
return getProductIdUpgradePrice(productId: productId)
}
}
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 productIdMonthly:
return defaultPriceStringMonthly
case productIdMonthlyPro:
return defaultPriceStringMonthlyPro
case productIdAnnual:
return defaultPriceStringAnnual
case productIdAnnualPro:
return defaultPriceStringAnnualPro
case productIdAnnualProLTO:
return defaultPriceStringAnnualProLTO
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 productIdMonthly:
return defaultUpgradePriceStringMonthly
case productIdMonthlyPro:
return defaultUpgradePriceStringMonthlyPro
case productIdAnnual:
return defaultUpgradePriceStringAnnual
case productIdAnnualPro:
return defaultUpgradePriceStringAnnualPro
case productIdAnnualProLTO:
return defaultUpgradePriceStringAnnualProLTO
default:
DDLogError("Invalid product Id: \(productId)")
return "Invalid Upgrade Price"
}
}
}
static func cacheLocalizedPrices() {
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 == productIdMonthly {
if product.localizedPrice != nil {
DDLogInfo("setting monthly display price = " + product.localizedPrice!)
setProductIdPrice(productId: productIdMonthly, price: "\(product.localizedPrice!)/" + .localized("month_after_slash"))
setProductIdUpgradePrice(productId: productIdMonthly,
upgradePrice: "\(product.localizedPrice!)/" + .localized("month_after_slash"))
} 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 monthlyPro display price = " + product.localizedPrice!)
setProductIdPrice(productId: productIdMonthlyPro, price: "\(product.localizedPrice!)/" + .localized("month_after_slash"))
setProductIdUpgradePrice(productId: productIdMonthlyPro,
upgradePrice: "\(product.localizedPrice!)/" + .localized("month_after_slash"))
} else {
DDLogError("monthlyPro nil localizedPrice, setting default")
setProductIdPrice(productId: productIdMonthlyPro, price: defaultPriceStringMonthlyPro)
setProductIdUpgradePrice(productId: productIdMonthlyPro, upgradePrice: defaultUpgradePriceStringMonthlyPro)
}
} else if product.productIdentifier == productIdAnnual {
currencyFormatter.locale = product.priceLocale
DDLogInfo("annual price = \(product.price)")
if let priceString = currencyFormatter.string(from: product.price) {
DDLogInfo("setting annual display price = annual product price / 12 = " + priceString)
setProductIdPrice(productId: productIdAnnual, price: "\(priceString)/" + .localized("year_after_slash"))
setProductIdUpgradePrice(productId: productIdAnnual, upgradePrice: "\(priceString)/" + .localized("year_after_slash"))
} else {
DDLogError("unable to format price with currencyformatter: " + product.price.stringValue)
setProductIdPrice(productId: productIdAnnual, price: defaultPriceStringAnnual)
setProductIdUpgradePrice(productId: productIdAnnual, upgradePrice: defaultUpgradePriceStringAnnual)
}
} else if product.productIdentifier == productIdAnnualPro {
currencyFormatter.locale = product.priceLocale
DDLogInfo("annualPro price = \(product.price)")
if let priceString = currencyFormatter.string(from: product.price) {
DDLogInfo("setting annualPro display price = annualPro product price / 12 = " + priceString)
setProductIdPrice(productId: productIdAnnualPro, price: "\(priceString)/" + .localized("year_after_slash"))
setProductIdUpgradePrice(productId: productIdAnnualPro, upgradePrice: "\(priceString)/" + .localized("year_after_slash"))
} else {
DDLogError("unable to format price with currencyformatter: " + product.price.stringValue)
setProductIdPrice(productId: productIdAnnualPro, price: defaultPriceStringAnnualPro)
setProductIdUpgradePrice(productId: productIdAnnualPro, upgradePrice: defaultUpgradePriceStringAnnualPro)
}
} else if product.productIdentifier == productIdAnnualProLTO {
currencyFormatter.locale = product.priceLocale
DDLogInfo("annualPro price = \(product.price)")
if let priceString = currencyFormatter.string(from: product.price) {
DDLogInfo("setting annualPro LTO display price = annualPro product price / 12 = " + priceString)
setProductIdPrice(productId: productIdAnnualProLTO, price: "\(priceString)/" + .localized("year_after_slash"))
setProductIdUpgradePrice(productId: productIdAnnualProLTO, upgradePrice: "\(priceString)/" + .localized("year_after_slash"))
} else {
DDLogError("unable to format price with currencyformatter: " + product.price.stringValue)
setProductIdPrice(productId: productIdAnnualProLTO, price: defaultPriceStringAnnualProLTO)
setProductIdUpgradePrice(productId: productIdAnnualProLTO, upgradePrice: defaultUpgradePriceStringAnnualProLTO)
}
}
}
for invalidProductId in result.invalidProductIDs {
DDLogError("invalid product id: \(invalidProductId)")
}
}
}
}
extension Subscription.PlanType {
var productId: String? {
switch self {
case .monthly:
return VPNSubscription.productIdMonthly
case .annual:
return VPNSubscription.productIdAnnual
case .proMonthly:
return VPNSubscription.productIdMonthlyPro
case .proAnnual:
return VPNSubscription.productIdAnnualPro
case .proAnnualLTO:
return VPNSubscription.productIdAnnualProLTO
default:
return nil
}
}
static var supported: [Subscription.PlanType] {
return [.monthly, .annual, .proMonthly, .proAnnual, .proAnnualLTO]
}
var availableUpgrades: [Subscription.PlanType]? {
switch self {
case .monthly:
return [.annual, .proMonthly, .proAnnual]
case .annual:
return [.proAnnual]
case .proMonthly:
return [.proAnnual]
case .proAnnual:
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
}
}