You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
PadelClub/PadelClub/Subscription/StoreManager.swift

140 lines
4.2 KiB

//
// Store.swift
// Poker Analytics 6
//
// Created by Laurent Morvillier on 20/04/2022.
//
import Foundation
import StoreKit
import LeStorage
public enum StoreManagerError: Error {
case failedVerification
case missingPlan
}
protocol StoreDelegate {
func productsReceived(products: [Product])
func errorDidOccur(error: Error)
}
extension Notification.Name {
static let StoreEventHappened = Notification.Name("storePurchaseSucceeded")
}
class StoreManager {
@Published private(set) var purchasedTransactions = Set<StoreKit.Transaction>()
var delegate: StoreDelegate? = nil
var updateListenerTask: Task<Void, Error>? = nil
init(delegate: StoreDelegate?) {
self.delegate = delegate
self.updateListenerTask = listenForTransactions()
Task {
//Initialize the store by starting a product request.
await self.requestProducts()
}
}
deinit {
self.updateListenerTask?.cancel()
}
@MainActor
func requestProducts() async {
do {
// let identifiers: [String] = StoreItem.allCases.map { $0.rawValue }
var products: [Product] = try await Product.products(for: self._productIdentifiers())
products = products.sorted { p1, p2 in
return p2.price > p1.price
}
Logger.log("products = \(products.count)")
self.delegate?.productsReceived(products: products)
} catch {
self.delegate?.errorDidOccur(error: error)
Logger.error(error)
}
}
fileprivate func _productIdentifiers() -> [String] {
var items: [StoreItem] = []
switch Guard.main.currentPlan {
case .fivePerMonth:
items = [StoreItem.unit, StoreItem.monthlyUnlimited]
case .monthlyUnlimited:
break
default:
items = StoreItem.allCases
}
return items.map { $0.rawValue }
}
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
//Iterate through any transactions which didn't come from a direct call to `purchase()`.
for await result in Transaction.updates {
do {
let transaction = try await Guard.main.processTransactionResult(result)
//Always finish a transaction.
await transaction.finish()
} catch {
self.delegate?.errorDidOccur(error: error)
//StoreKit has a receipt it can read but it failed verification. Don't deliver content to the user.
print("Transaction failed verification")
}
}
}
}
func purchase(_ product: Product, quantity: Int? = nil) async throws -> StoreKit.Transaction? {
Logger.log("Store purchase started...")
guard let userId = StoreCenter.main.userId, let uuid: UUID = UUID(uuidString: userId) else {
throw StoreError.missingUserId
}
var options: Set<Product.PurchaseOption> = []
let tokenOption = Product.PurchaseOption.appAccountToken(uuid)
options.insert(tokenOption)
if let quantity = quantity {
let quantityOption = Product.PurchaseOption.quantity(quantity)
options.insert(quantityOption)
}
let result = try await product.purchase(options: options)
Logger.log("Store purchase ended with result: \(result)")
switch result {
case .success(let verificationResult):
let transaction = try await Guard.main.processTransactionResult(verificationResult)
// Always finish a transaction.
await transaction.finish()
DispatchQueue.main.asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 100000), execute: {
NotificationCenter.default.post(name: Notification.Name.StoreEventHappened, object: nil)
})
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}
}