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/Views/Subscription/StoreManager.swift

126 lines
3.8 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")
}
@available(iOS 15, *)
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 }
Logger.log("Request products: \(identifiers)")
var products: [Product] = try await Product.products(for: identifiers)
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)
}
}
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...")
var options: Set<Product.PurchaseOption> = []
let uuid: UUID = Store.main.currentUserUUID()
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
}
}
}