|
|
|
|
@ -20,6 +20,8 @@ import LeStorage |
|
|
|
|
|
|
|
|
|
var updateListenerTask: Task<Void, Error>? = nil |
|
|
|
|
|
|
|
|
|
fileprivate var _userPurchases: [Purchase] = [] |
|
|
|
|
|
|
|
|
|
override init() { |
|
|
|
|
|
|
|
|
|
super.init() |
|
|
|
|
@ -28,14 +30,32 @@ import LeStorage |
|
|
|
|
|
|
|
|
|
Task { |
|
|
|
|
do { |
|
|
|
|
try await self.refreshPurchasedProducts() |
|
|
|
|
try await self._retrievePrivateServerPurchases() |
|
|
|
|
try await self.refreshPurchasedAppleProducts() |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func refreshPurchasedProducts() async throws { |
|
|
|
|
fileprivate func _uploadTransaction(_ transaction: StoreKit.Transaction) async throws { |
|
|
|
|
|
|
|
|
|
let service = try Store.main.service() |
|
|
|
|
var purchase = try transaction.purchase() |
|
|
|
|
|
|
|
|
|
// if let apiCall = try self._callForInstance(purchase, method: Method.post) { |
|
|
|
|
// |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _retrievePrivateServerPurchases() async throws { |
|
|
|
|
let service = try Store.main.service() |
|
|
|
|
self._userPurchases = try await service.get() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func refreshPurchasedAppleProducts() async throws { |
|
|
|
|
|
|
|
|
|
// Iterate through the user's purchased products. |
|
|
|
|
for await verificationResult in Transaction.currentEntitlements { |
|
|
|
|
@ -73,7 +93,7 @@ import LeStorage |
|
|
|
|
switch result { |
|
|
|
|
case .unverified: |
|
|
|
|
//StoreKit has parsed the JWS but failed verification. Don't deliver content to the user. |
|
|
|
|
throw StoreError.failedVerification |
|
|
|
|
throw StoreManagerError.failedVerification |
|
|
|
|
case .verified(let safe): |
|
|
|
|
//If the transaction is verified, unwrap and return it. |
|
|
|
|
return safe |
|
|
|
|
@ -85,11 +105,15 @@ import LeStorage |
|
|
|
|
if transaction.revocationDate == nil { |
|
|
|
|
// If the App Store has not revoked the transaction, add it to the list of `purchasedIdentifiers`. |
|
|
|
|
purchasedTransactions.insert(transaction) |
|
|
|
|
do { |
|
|
|
|
try await self._uploadTransaction(transaction) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// If the App Store has revoked this transaction, remove it from the list of `purchasedIdentifiers`. |
|
|
|
|
purchasedTransactions.remove(transaction) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self._updateBestPlan() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -106,7 +130,7 @@ import LeStorage |
|
|
|
|
var currentPlan: StoreItem? { |
|
|
|
|
|
|
|
|
|
#if DEBUG |
|
|
|
|
return .unlimited |
|
|
|
|
return .monthlyUnlimited |
|
|
|
|
#else |
|
|
|
|
if let currentBestPlan = self.currentBestPlan, let plan = StorePlan(rawValue: currentBestPlan.productID) { |
|
|
|
|
return plan |
|
|
|
|
@ -120,14 +144,74 @@ import LeStorage |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _userFilteredPurchases() -> [StoreKit.Transaction] { |
|
|
|
|
return self.purchasedTransactions.filter { transaction in |
|
|
|
|
return self._userPurchases.contains(where: { $0.identifier == transaction.id } ) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Update best plan by filtering Apple purchases with registered purchases by the user |
|
|
|
|
fileprivate func _updateBestPlan() { |
|
|
|
|
|
|
|
|
|
if let monthly = self.purchasedTransactions.first(where: { $0.productID == StoreItem.unlimited.rawValue }) { |
|
|
|
|
self.currentBestPlan = monthly |
|
|
|
|
// Make sure the purchase has been done with the logged user |
|
|
|
|
let privatePurchases = self._userFilteredPurchases() |
|
|
|
|
|
|
|
|
|
if let unlimited = privatePurchases.first(where: { $0.productID == StoreItem.monthlyUnlimited.rawValue }) { |
|
|
|
|
self.currentBestPlan = unlimited |
|
|
|
|
} else if let fivePerMonth = privatePurchases.first(where: { $0.productID == StoreItem.fivePerMonth.rawValue }) { |
|
|
|
|
self.currentBestPlan = fivePerMonth |
|
|
|
|
} else { |
|
|
|
|
self.currentBestPlan = nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _purchasedTournamentCount() -> Int { |
|
|
|
|
let units = self._userFilteredPurchases().filter { $0.productID == StoreItem.unit.rawValue } |
|
|
|
|
return units.reduce(0) { $0 + $1.purchasedQuantity } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func canAddTournament() -> Bool { |
|
|
|
|
if self.currentPlan == .monthlyUnlimited { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
let tournamentCount = DataStore.shared.tournaments.count |
|
|
|
|
let tournamentCreditCount = self._purchasedTournamentCount() |
|
|
|
|
return tournamentCreditCount > tournamentCount |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func paymentForNewTournament() -> Tournament.TournamentPayment? { |
|
|
|
|
if self.currentPlan == .monthlyUnlimited { |
|
|
|
|
return Tournament.TournamentPayment.unlimited |
|
|
|
|
} else if self.currentPlan == .fivePerMonth, let purchaseDate = self.currentBestPlan?.originalPurchaseDate { |
|
|
|
|
let tournaments = DataStore.shared.tournaments.filter { $0.creationDate > purchaseDate } |
|
|
|
|
if tournaments.count < 5 { |
|
|
|
|
return Tournament.TournamentPayment.subscriptionUnit |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let tournamentCount = DataStore.shared.tournaments.count |
|
|
|
|
if tournamentCount == 0 { |
|
|
|
|
return Tournament.TournamentPayment.free |
|
|
|
|
} |
|
|
|
|
let tournamentCreditCount = self._purchasedTournamentCount() |
|
|
|
|
if tournamentCreditCount > tournamentCount { |
|
|
|
|
return Tournament.TournamentPayment.unit |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate extension StoreKit.Transaction { |
|
|
|
|
|
|
|
|
|
func purchase() throws -> Purchase { |
|
|
|
|
let userId = try Store.main.currentUserUUID().uuidString |
|
|
|
|
return Purchase(user: userId, |
|
|
|
|
identifier: self.id, |
|
|
|
|
purchaseDate: self.purchaseDate, |
|
|
|
|
productId: self.productID) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|