Untitled
unknown
swift
a year ago
8.3 kB
9
Indexable
// // SubscriptionsManager.swift // YinYang (iOS) // // Created by Zeng on 3/28/24. // import Foundation import StoreKit import Amplify struct MembershipInfo: Decodable { let MembershipTypeId: String let MembershipTypeName: String let ChangeType: String let PreviousMembershipType: String? let NeedsUpgrade: Bool let UserSessionsForTheMonth: Int let CreationDate: Int? } @MainActor class SubscriptionsManager: NSObject, ObservableObject { // MARK: - Properties let productIDs = ["com.zeng.YinYang.growth", "com.zeng.YinYang.premium"] @Published var products: [Product] = [] @Published var currentSubscriptionTier = "Starter" @Published var membershipInfo: MembershipInfo? let subscriptionNameToIDMapping: [String: String] = [ "Growth": "com.zeng.YinYang.growth", "Premium": "com.zeng.YinYang.premium" ] var transactionListener: Task<Void, Error>? = nil override init() { super.init() SKPaymentQueue.default().add(self) transactionListener = listenForTransactions() print("SubscriptionsManager initialized") Task { await loadProducts() await fetchLatestMembershipInfo() await updateCurrentEntitlements() } } deinit { transactionListener?.cancel() SKPaymentQueue.default().remove(self) } func listenForTransactions() -> Task<Void, Error> { return Task.detached { for await result in Transaction.updates { await self.handle(transactionVerification: result) } } } private func updateCurrentEntitlements() async { for await result in Transaction.currentEntitlements { await self.handle(transactionVerification: result) } } @MainActor private func handle(transactionVerification result: VerificationResult <Transaction> ) async { do { // Log the receipt of a transaction update print("Received a transaction update: \(result)") let transaction = try self.verifyPurchase(result) // Log the verified transaction print("Verified transaction: \(transaction)") let productId = transaction.productID let newSubscriptionTier = self.subscriptionName(for: productId) let action = self.determinePlanChangeAction(from: self.currentSubscriptionTier, to: newSubscriptionTier) // Log the determined action print("Determined action from \(self.currentSubscriptionTier) to \(newSubscriptionTier): \(action)") await self.changeMembershipTo(newMembershipTypeId: productId, action: action) await transaction.finish() // Log the completion of transaction processing print("Processed and finished transaction for product ID: \(productId)") } catch { // Log any errors during transaction processing print("Transaction didn't pass verification - ignoring purchase. Error: \(error)") } } private func verifyPurchase<T>(_ result: VerificationResult<T>) throws -> T { switch result { case .unverified: throw SubscriptionsManagerError.failedVerification case .verified(let safe): return safe } } enum SubscriptionsManagerError: Error { case failedVerification } func fetchLatestMembershipInfo() async { let userId = UserManager.shared.userSub let request = RESTRequest(path: "/membership/\(userId)") do { let data = try await Amplify.API.get(request: request) let decoder = JSONDecoder() let fetchedMembershipInfo = try decoder.decode(MembershipInfo.self, from: data) DispatchQueue.main.async { self.membershipInfo = fetchedMembershipInfo self.currentSubscriptionTier = fetchedMembershipInfo.MembershipTypeName } } catch { print("Failed to fetch membership info for user ID \(userId): \(error)") } } // MARK: - StoreKit2 API func loadProducts() async { do { self.products = try await Product.products(for: productIDs) .sorted(by: { $0.price > $1.price }) } catch { print("Failed to fetch products: \(error)") } } func buyProduct(_ product: Product) async { do { print("Attempting to purchase product: \(product.id)") let result = try await product.purchase() await handlePurchaseResult(result) } catch { print("Failed to purchase product: \(product.id), Error: \(error)") } } private func handlePurchaseResult(_ result: Product.PurchaseResult) async { switch result { case .success(let verification): switch verification { case .verified(let transaction): print("Purchase verified for transaction: \(transaction)") await transaction.finish() case .unverified(_, let error): print("Unverified purchase: \(error)") @unknown default: print("Unknown verification result.") } case .userCancelled: print("User cancelled the purchase") case .pending: print("Purchase pending for product.") @unknown default: print("Unexpected purchase result") } } func changeMembershipTo(newMembershipTypeId: String, action: String) async { let userId = UserManager.shared.userSub // Log the beginning of the membership change process print("Attempting to change membership. UserID: \(userId), NewMembershipTypeID: \(newMembershipTypeId), Action: \(action)") let requestBody: [String: Any] = ["UserId": userId, "NewMembershipTypeId": newMembershipTypeId, "Action": action] do { // Attempt to serialize the request body to JSON and log potential serialization failure guard let requestBodyData = try? JSONSerialization.data(withJSONObject: requestBody) else { print("Failed to serialize request body to JSON for UserID: \(userId), NewMembershipTypeID: \(newMembershipTypeId)") return } let request = RESTRequest(path: "/membership/change", body: requestBodyData) // Log the API request details print("Sending API request to change membership. Path: \(request.path), Body: \(requestBody)") _ = try await Amplify.API.post(request: request) // Log successful membership change print("Successfully changed membership for UserID: \(userId) to MembershipTypeID: \(newMembershipTypeId)") await fetchLatestMembershipInfo() } catch { // Log any errors that occur during the API request print("Failed to change membership for user ID \(userId): \(error.localizedDescription). Request Body: \(requestBody)") } } func determinePlanChangeAction(from currentPlan: String, to newPlan: String) -> String { let tierHierarchy = ["Starter": 0, "Growth": 1, "Premium": 2] let currentPlanIndex = tierHierarchy[currentPlan] ?? -1 let newPlanIndex = tierHierarchy[newPlan] ?? -1 return newPlanIndex > currentPlanIndex ? "upgrade" : "downgrade" } func subscriptionName(for productId: String) -> String { return subscriptionNameToIDMapping.first(where: { $0.value == productId })?.key ?? "Starter" } } extension SubscriptionsManager: SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // Handle transaction updates if necessary } func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { return true } }
Editor is loading...
Leave a Comment