Puchases / subscriptions issues

Hi, I've some issues on my application about purchases.

My app is available for iOS and tvOS. Some part of my code is the same on both target (my purchase code is one of them).

The issue is:

  • Some users can't do a purchase on Apple TV (but not all and I can't reproduce)
  • Some users purchased on a device and are not considered as pro on the other device (logged with same apple account). Again it's not all.

import Foundation
import StoreKit

typealias Transaction = StoreKit.Transaction
typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo
typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState

public enum StoreError: Error {
    case failedVerification
}

public enum ServiceEntitlement: Int, Comparable {
    case notEntitled = 0
    
    case pro1Month = 1
    case pro6Month = 2
    case pro1Year = 3
    case pro1MonthFamily = 4
    case pro6MonthFamily = 5
    case pro1YearFamily = 6
    case pro3Month = 7
    case proLifeTime = 8
    
    init?(for product: Product) {
        // The product must be a subscription to have service entitlements.
        guard let subscription = product.subscription else {
            return nil
        }
        if #available(iOS 16.4, *) {
            self.init(rawValue: subscription.groupLevel)
        } else {
            switch product.id {
            // Plans actifs
            case "monthlyPlan":
                self = .pro1Month
            case "semesterlyPlan":
                self = .pro6Month
            case "yearlyPlan":
                self = .pro1Year
            case "monthlyPlanFamily":
                self = .pro1MonthFamily
            case "semesterlyPlanFamily":
                self = .pro6MonthFamily
            case "yearlyPlanFamily":
                self = .pro1YearFamily
            // Anciens plans qui doivent être reconnus
            case "lifetimeProPlan":
                self = .proLifeTime
            case "quarterlyPlan":
                self = .pro3Month
            default:
                self = .notEntitled
            }
        }
    }

    public static func < (lhs: Self, rhs: Self) -> Bool {
        // Subscription-group levels are in descending order.
        return lhs.rawValue > rhs.rawValue
    }
}

class Store: ObservableObject {
        
    let productIDProPlan = "lifetimeProPlan"
    let productIDSNotAvailable = ["quarterlyPlan"]
    let productIDSSingle = ["monthlyPlan", "semesterlyPlan", "yearlyPlan"]
    let productIDSFamily = ["monthlyPlanFamily", "semesterlyPlanFamily", "yearlyPlanFamily"]
    
    @Published private(set) var singleSubscriptions: [Product] = []
    @Published private(set) var familySubscriptions: [Product] = []
    @Published private(set) var notAvailableSubscriptions: [Product] = []
    @Published private(set) var nonRenewablesSubscriptions: [Product] = []
    
    @Published private(set) var purchasedSubscriptions: [Product] = []
    @Published private(set) var purchasedNonRenewableSubscriptions: [Product] = []
    @Published private(set) var subscriptionGroupStatus: Product.SubscriptionInfo.Status?
    
    var hasProAccount: Bool {
        return !purchasedSubscriptions.isEmpty || !purchasedNonRenewableSubscriptions.isEmpty
    }
    
    var updateListenerTask: Task? = nil
    
    init() {
        // Start a transaction listener as close to app launch as possible so you don't miss any transactions.
        updateListenerTask = listenForTransactions()

        Task {
            // During store initialization, request products from the App Store.
            await requestProducts()

            // Deliver products that the customer purchases.
            await updateCustomerProductStatus()
        }
    }
    
    deinit {
        updateListenerTask?.cancel()
    }
    
    @MainActor
    func requestProducts() async {
        do {
            // Request single products
            let storeSingleProducts = try await Product.products(for: productIDSSingle)
            let storeFamilyProducts = try await Product.products(for: productIDSFamily)
            let storeNotAvailableProducts = try await Product.products(for: productIDSNotAvailable)
            let storeNonRenewablesProducts = try await Product.products(for: [productIDProPlan])

            // Filter and sort single products
            singleSubscriptions = sortByPrice(storeSingleProducts)
            
            // Filter and sort family products
            familySubscriptions = sortByPrice(storeFamilyProducts)
            
            notAvailableSubscriptions = sortByPrice(storeNotAvailableProducts)
            
            nonRenewablesSubscriptions = sortByPrice(storeNonRenewablesProducts)
            
        } catch {
            print("Failed product request from the App Store server. \(error)")
        }
    }
    
    func purchase(_ product: Product) async throws -> Result {
            // Begin purchasing the Product the user selected.
        do {
            #if os(macOS)
            let result = try await product.purchase()
            #else
            let result: Product.PurchaseResult
            if #available(iOS 18.2, tvOS 18.2, *),
               let currentViewController = await UIApplication.shared.currentViewController {
                       result = try await product.purchase(confirmIn: currentViewController)
            } else {
                result = try await product.purchase()
            }
            #endif
            switch result {
            case .success(let verification):
                // Check whether the transaction is verified.
                let transaction = try checkVerified(verification)

                // The transaction is verified. Deliver content to the user.
                await updateCustomerProductStatus()

                // Always finish a transaction.
                await transaction.finish()

                return .success(transaction)
            case .userCancelled:
                return .failure(.userCancelled)
            case .pending:
                return .failure(.pending)
            default:
                return .failure(.purchaseUnknowError)
            }
        } catch {
            print(error)
            return .failure(.purchaseUnknowError)
        }
    }
    
    /*
     // OLD
    func purchase(_ product: Product) async throws -> Result {
        // Begin purchasing the `Product` the user selected.
        let result = try await product.purchase()
        

        switch result {
        case .success(let verification):
            // Check whether the transaction is verified.
            let transaction = try checkVerified(verification)

            // The transaction is verified. Deliver content to the user.
            await updateCustomerProductStatus()

            // Always finish a transaction.
            await transaction.finish()

            return .success(transaction)
        case .userCancelled:
            return .failure(.userCancelled)
        case .pending:
            return .failure(.pending)
        default:
            return .failure(.purchaseUnknowError)
        }
    }
     */
    
    func sortByPrice(_ products: [Product]) -> [Product] {
        products.sorted(by: { return $0.price < $1.price })
    }
    
    func listenForTransactions() -> Task {
        return Task.detached {
            // Iterate through any transactions that don't come from a direct call to `purchase()`.
            for await result in Transaction.updates {
                do {
                    let transaction = try self.checkVerified(result)

                    // Deliver products to the user.
                    await self.updateCustomerProductStatus()

                    // Always finish a transaction.
                    await transaction.finish()
                } catch {
                    // StoreKit has a transaction that fails verification. Don't deliver content to the user.
                    print("Transaction failed verification.")
                }
            }
        }
    }
    
    func checkVerified(_ result: VerificationResult) throws -> T {
        // Check whether the JWS passes StoreKit verification.
        switch result {
        case .unverified:
            // StoreKit parses the JWS, but it fails verification.
            throw StoreError.failedVerification
        case .verified(let safe):
            // The result is verified. Return the unwrapped value.
            return safe
        }
    }
    
    @MainActor
    func updateCustomerProductStatus() async {
        var purchasedSubscriptions: [Product] = []
        var purchasedNonRenewableSubscriptions: [Product] = []

        // Iterate through all of the user's purchased products.
        for await result in Transaction.currentEntitlements {
            do {
                // Check whether the transaction is verified. If it isn't, catch `failedVerification` error.
                let transaction = try checkVerified(result)

                // Check the `productType` of the transaction and get the corresponding product from the store.
                switch transaction.productType {
                case .nonConsumable:
                    if let nonRenewable = nonRenewablesSubscriptions.first(where: { $0.id == transaction.productID }),
                           transaction.productID == productIDProPlan {
                            purchasedNonRenewableSubscriptions.append(nonRenewable)
                        }
                case .autoRenewable:
                    if let subscription = singleSubscriptions.first(where: { $0.id == transaction.productID }) {
                        purchasedSubscriptions.append(subscription)
                    } else if let subscription = familySubscriptions.first(where: { $0.id == transaction.productID }) {
                        purchasedSubscriptions.append(subscription)
                    } else if let subscription = notAvailableSubscriptions.first(where: { $0.id == transaction.productID }) {
                        purchasedSubscriptions.append(subscription)
                    }
                default:
                    break
                }
            } catch {
                print("Transaction failed verification")
            }
        }

        // Update the store information with auto-renewable subscription products.
        self.purchasedSubscriptions = purchasedSubscriptions
        self.purchasedNonRenewableSubscriptions = purchasedNonRenewableSubscriptions

        // Vérifier le statut des abonnements
        let allSubscriptions = singleSubscriptions + familySubscriptions + nonRenewablesSubscriptions + notAvailableSubscriptions
        subscriptionGroupStatus = try? await allSubscriptions.first?.subscription?.status.max { lhs, rhs in
            // There may be multiple statuses for different family members, because this app supports Family Sharing.
            // The subscriber is entitled to service for the status with the highest level of service.
            let lhsEntitlement = entitlement(for: lhs)
            let rhsEntitlement = entitlement(for: rhs)
            return lhsEntitlement < rhsEntitlement
        }
    }
    
    // Get a subscription's level of service using the product ID.
    func entitlement(for status: Product.SubscriptionInfo.Status) -> ServiceEntitlement {
        // If the status is expired, then the customer is not entitled.
        if status.state == .expired || status.state == .revoked {
            return .notEntitled
        }
        // Get the product associated with the subscription status.
        let productID = status.transaction.unsafePayloadValue.productID
        
        // Check in both single and family subscriptions
        if let product = singleSubscriptions.first(where: { $0.id == productID }) {
            return ServiceEntitlement(for: product) ?? .notEntitled
        } else if let product = familySubscriptions.first(where: { $0.id == productID }) {
            return ServiceEntitlement(for: product) ?? .notEntitled
        }
        
        return .notEntitled
    }
    

    func isPurchased(_ product: Product) -> Bool {
        // Determine whether the user purchases a given product.
        switch product.type {
        case .nonConsumable:
            return purchasedNonRenewableSubscriptions.contains(product)
        case .autoRenewable:
            return purchasedSubscriptions.contains(product)
        default:
            return false
        }
    }
}

extension Store {
    func restorePurchases() async throws {
        // Synchronise avec l'App Store pour restaurer les achats
        try await AppStore.sync()
        
        // Met à jour le statut des produits après la restauration
        await updateCustomerProductStatus()
    }
    
    // Cette fonction n'est plus nécessaire car nous utilisons directement AppStore.presentCodeRedemptionSheet()
    // dans ProAccountController, mais je la laisse ici comme référence
    #if os(iOS)
    func redeemPromoCode() async throws {
        guard let scene = await UIApplication.shared.connectedScenes.first as? UIWindowScene else {
            throw PurchaseError.sceneNotFound
        }
        
        try await AppStore.presentOfferCodeRedeemSheet(in: scene)
    }
    #endif
    
    // Fonction utilitaire pour obtenir les informations de prix avec gestion des offres promotionnelles
    func getPriceInformation(for product: Product) -> (displayPrice: String, originalPrice: String?) {
        let displayPrice = product.displayPrice
        
        // Vérifie s'il y a une offre promotionnelle
        if let intro = product.subscription?.introductoryOffer {
            return (displayPrice: intro.price.formatted(), originalPrice: displayPrice)
        }
        
        return (displayPrice: displayPrice, originalPrice: nil)
    }
}

enum PurchaseError: Error {
    case productNotFound
    case invalidPromoCode
    case sceneNotFound
    case userCancelled
    case pending
    case purchaseUnknowError
}

extension Store {
    
    enum productIdentifiers:String, CaseIterable {
        case pro1Month = "monthlyPlan"
        case pro6Month = "semesterlyPlan"
        case pro1Year = "yearlyPlan"
        case pro1MonthFamily = "monthlyPlanFamily"
        case pro6MonthFamily = "semesterlyPlanFamily"
        case pro1YearFamily = "yearlyPlanFamily"
        case pro3Month = "quarterlyPlan"
        case proLifeTime = "lifetimeProPlan"
        
       
    }
    
    func checkPurchase(for productIdentifier: String) async -> Bool {
        guard let verificationResult = await Transaction.latest(for: productIdentifier) else {
            return false
        }

        switch verificationResult {
        case .verified(let transaction):
            // Vérifions si l'abonnement est toujours actif
            if transaction.productType == .autoRenewable {
                let currentDate = Date()
                return transaction.expirationDate?.compare(currentDate) == .orderedDescending
            }
            // Pour les achats non renouvelables (comme votre lifetimeProPlan)
            return true
        case .unverified:
            return false
        }
    }
    
    func hasPurchases() async -> Bool {
        return true
        var hasPurchases = false
        for productId in productIdentifiers.allCases {
            let isPurchased = await checkPurchase(for: productId.rawValue)
            if isPurchased {
                hasPurchases = true
            }
        }
        return hasPurchases
    }
}

import Foundation
import StoreKit

typealias Transaction = StoreKit.Transaction
typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo
typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState

public enum StoreError: Error {
    case failedVerification
}

public enum ServiceEntitlement: Int, Comparable {
    case notEntitled = 0
    
    case pro1Month = 1
    case pro6Month = 2
    case pro1Year = 3
    case pro1MonthFamily = 4
    case pro6MonthFamily = 5
    case pro1YearFamily = 6
    case pro3Month = 7
    case proLifeTime = 8
    
    init?(for product: Product) {
        // The product must be a subscription to have service entitlements.
        guard let subscription = product.subscription else {
            return nil
        }
        if #available(iOS 16.4, *) {
            self.init(rawValue: subscription.groupLevel)
        } else {
            switch product.id {
            // Plans actifs
            case "monthlyPlan":
                self = .pro1Month
            case "semesterlyPlan":
                self = .pro6Month
            case "yearlyPlan":
                self = .pro1Year
            case "monthlyPlanFamily":
                self = .pro1MonthFamily
            case "semesterlyPlanFamily":
                self = .pro6MonthFamily
            case "yearlyPlanFamily":
                self = .pro1YearFamily
            // Anciens plans qui doivent être reconnus
            case "lifetimeProPlan":
                self = .proLifeTime
            case "quarterlyPlan":
                self = .pro3Month
            default:
                self = .notEntitled
            }
        }
    }

    public static func < (lhs: Self, rhs: Self) -> Bool {
        // Subscription-group levels are in descending order.
        return lhs.rawValue > rhs.rawValue
    }
}

class Store: ObservableObject {
        
    let productIDProPlan = "lifetimeProPlan"
    let productIDSNotAvailable = ["quarterlyPlan"]
    let productIDSSingle = ["monthlyPlan", "semesterlyPlan", "yearlyPlan"]
    let productIDSFamily = ["monthlyPlanFamily", "semesterlyPlanFamily", "yearlyPlanFamily"]
    
    @Published private(set) var singleSubscriptions: [Product] = []
    @Published private(set) var familySubscriptions: [Product] = []
    @Published private(set) var notAvailableSubscriptions: [Product] = []
    @Published private(set) var nonRenewablesSubscriptions: [Product] = []
    
    @Published private(set) var purchasedSubscriptions: [Product] = []
    @Published private(set) var purchasedNonRenewableSubscriptions: [Product] = []
    @Published private(set) var subscriptionGroupStatus: Product.SubscriptionInfo.Status?
    
    var hasProAccount: Bool {
        return !purchasedSubscriptions.isEmpty || !purchasedNonRenewableSubscriptions.isEmpty
    }
    
    var updateListenerTask: Task? = nil
    
    init() {
        // Start a transaction listener as close to app launch as possible so you don't miss any transactions.
        updateListenerTask = listenForTransactions()

        Task {
            // During store initialization, request products from the App Store.
            await requestProducts()

            // Deliver products that the customer purchases.
            await updateCustomerProductStatus()
        }
    }
    
    deinit {
        updateListenerTask?.cancel()
    }
    
    @MainActor
    func requestProducts() async {
        do {
            // Request single products
            let storeSingleProducts = try await Product.products(for: productIDSSingle)
            let storeFamilyProducts = try await Product.products(for: productIDSFamily)
            let storeNotAvailableProducts = try await Product.products(for: productIDSNotAvailable)
            let storeNonRenewablesProducts = try await Product.products(for: [productIDProPlan])

            // Filter and sort single products
            singleSubscriptions = sortByPrice(storeSingleProducts)
            
            // Filter and sort family products
            familySubscriptions = sortByPrice(storeFamilyProducts)
            
            notAvailableSubscriptions = sortByPrice(storeNotAvailableProducts)
            
            nonRenewablesSubscriptions = sortByPrice(storeNonRenewablesProducts)
            
        } catch {
            print("Failed product request from the App Store server. \(error)")
        }
    }
    
    func purchase(_ product: Product) async throws -> Result {
            // Begin purchasing the Product the user selected.
        do {
            #if os(macOS)
            let result = try await product.purchase()
            #else
            let result: Product.PurchaseResult
            if #available(iOS 18.2, tvOS 18.2, *),
               let currentViewController = await UIApplication.shared.currentViewController {
                       result = try await product.purchase(confirmIn: currentViewController)
            } else {
                result = try await product.purchase()
            }
            #endif
            switch result {
            case .success(let verification):
                // Check whether the transaction is verified.
                let transaction = try checkVerified(verification)

                // The transaction is verified. Deliver content to the user.
                await updateCustomerProductStatus()

                // Always finish a transaction.
                await transaction.finish()

                return .success(transaction)
            case .userCancelled:
                return .failure(.userCancelled)
            case .pending:
                return .failure(.pending)
            default:
                return .failure(.purchaseUnknowError)
            }
        } catch {
            print(error)
            return .failure(.purchaseUnknowError)
        }
    }
    
    /*
     // OLD
    func purchase(_ product: Product) async throws -> Result {
        // Begin purchasing the `Product` the user selected.
        let result = try await product.purchase()
        

        switch result {
        case .success(let verification):
            // Check whether the transaction is verified.
            let transaction = try checkVerified(verification)

            // The transaction is verified. Deliver content to the user.
            await updateCustomerProductStatus()

            // Always finish a transaction.
            await transaction.finish()

            return .success(transaction)
        case .userCancelled:
            return .failure(.userCancelled)
        case .pending:
            return .failure(.pending)
        default:
            return .failure(.purchaseUnknowError)
        }
    }
     */
    
    func sortByPrice(_ products: [Product]) -> [Product] {
        products.sorted(by: { return $0.price < $1.price })
    }
    
    func listenForTransactions() -> Task {
        return Task.detached {
            // Iterate through any transactions that don't come from a direct call to `purchase()`.
            for await result in Transaction.updates {
                do {
                    let transaction = try self.checkVerified(result)

                    // Deliver products to the user.
                    await self.updateCustomerProductStatus()

                    // Always finish a transaction.
                    await transaction.finish()
                } catch {
                    // StoreKit has a transaction that fails verification. Don't deliver content to the user.
                    print("Transaction failed verification.")
                }
            }
        }
    }
    
    func checkVerified(_ result: VerificationResult) throws -> T {
        // Check whether the JWS passes StoreKit verification.
        switch result {
        case .unverified:
            // StoreKit parses the JWS, but it fails verification.
            throw StoreError.failedVerification
        case .verified(let safe):
            // The result is verified. Return the unwrapped value.
            return safe
        }
    }
    
    @MainActor
    func updateCustomerProductStatus() async {
        var purchasedSubscriptions: [Product] = []
        var purchasedNonRenewableSubscriptions: [Product] = []

        // Iterate through all of the user's purchased products.
        for await result in Transaction.currentEntitlements {
            do {
                // Check whether the transaction is verified. If it isn't, catch `failedVerification` error.
                let transaction = try checkVerified(result)

                // Check the `productType` of the transaction and get the corresponding product from the store.
                switch transaction.productType {
                case .nonConsumable:
                    if let nonRenewable = nonRenewablesSubscriptions.first(where: { $0.id == transaction.productID }),
                           transaction.productID == productIDProPlan {
                            purchasedNonRenewableSubscriptions.append(nonRenewable)
                        }
                case .autoRenewable:
                    if let subscription = singleSubscriptions.first(where: { $0.id == transaction.productID }) {
                        purchasedSubscriptions.append(subscription)
                    } else if let subscription = familySubscriptions.first(where: { $0.id == transaction.productID }) {
                        purchasedSubscriptions.append(subscription)
                    } else if let subscription = notAvailableSubscriptions.first(where: { $0.id == transaction.productID }) {
                        purchasedSubscriptions.append(subscription)
                    }
                default:
                    break
                }
            } catch {
                print("Transaction failed verification")
            }
        }

        // Update the store information with auto-renewable subscription products.
        self.purchasedSubscriptions = purchasedSubscriptions
        self.purchasedNonRenewableSubscriptions = purchasedNonRenewableSubscriptions

        // Vérifier le statut des abonnements
        let allSubscriptions = singleSubscriptions + familySubscriptions + nonRenewablesSubscriptions + notAvailableSubscriptions
        subscriptionGroupStatus = try? await allSubscriptions.first?.subscription?.status.max { lhs, rhs in
            // There may be multiple statuses for different family members, because this app supports Family Sharing.
            // The subscriber is entitled to service for the status with the highest level of service.
            let lhsEntitlement = entitlement(for: lhs)
            let rhsEntitlement = entitlement(for: rhs)
            return lhsEntitlement < rhsEntitlement
        }
    }
    
    // Get a subscription's level of service using the product ID.
    func entitlement(for status: Product.SubscriptionInfo.Status) -> ServiceEntitlement {
        // If the status is expired, then the customer is not entitled.
        if status.state == .expired || status.state == .revoked {
            return .notEntitled
        }
        // Get the product associated with the subscription status.
        let productID = status.transaction.unsafePayloadValue.productID
        
        // Check in both single and family subscriptions
        if let product = singleSubscriptions.first(where: { $0.id == productID }) {
            return ServiceEntitlement(for: product) ?? .notEntitled
        } else if let product = familySubscriptions.first(where: { $0.id == productID }) {
            return ServiceEntitlement(for: product) ?? .notEntitled
        }
        
        return .notEntitled
    }
    

    func isPurchased(_ product: Product) -> Bool {
        // Determine whether the user purchases a given product.
        switch product.type {
        case .nonConsumable:
            return purchasedNonRenewableSubscriptions.contains(product)
        case .autoRenewable:
            return purchasedSubscriptions.contains(product)
        default:
            return false
        }
    }
}

extension Store {
    func restorePurchases() async throws {
        // Synchronise avec l'App Store pour restaurer les achats
        try await AppStore.sync()
        
        // Met à jour le statut des produits après la restauration
        await updateCustomerProductStatus()
    }
    
    // Cette fonction n'est plus nécessaire car nous utilisons directement AppStore.presentCodeRedemptionSheet()
    // dans ProAccountController, mais je la laisse ici comme référence
    #if os(iOS)
    func redeemPromoCode() async throws {
        guard let scene = await UIApplication.shared.connectedScenes.first as? UIWindowScene else {
            throw PurchaseError.sceneNotFound
        }
        
        try await AppStore.presentOfferCodeRedeemSheet(in: scene)
    }
    #endif
    
    // Fonction utilitaire pour obtenir les informations de prix avec gestion des offres promotionnelles
    func getPriceInformation(for product: Product) -> (displayPrice: String, originalPrice: String?) {
        let displayPrice = product.displayPrice
        
        // Vérifie s'il y a une offre promotionnelle
        if let intro = product.subscription?.introductoryOffer {
            return (displayPrice: intro.price.formatted(), originalPrice: displayPrice)
        }
        
        return (displayPrice: displayPrice, originalPrice: nil)
    }
}

enum PurchaseError: Error {
    case productNotFound
    case invalidPromoCode
    case sceneNotFound
    case userCancelled
    case pending
    case purchaseUnknowError
}

extension Store {
    
    enum productIdentifiers:String, CaseIterable {
        case pro1Month = "monthlyPlan"
        case pro6Month = "semesterlyPlan"
        case pro1Year = "yearlyPlan"
        case pro1MonthFamily = "monthlyPlanFamily"
        case pro6MonthFamily = "semesterlyPlanFamily"
        case pro1YearFamily = "yearlyPlanFamily"
        case pro3Month = "quarterlyPlan"
        case proLifeTime = "lifetimeProPlan"
        
       
    }
    
    func checkPurchase(for productIdentifier: String) async -> Bool {
        guard let verificationResult = await Transaction.latest(for: productIdentifier) else {
            return false
        }

        switch verificationResult {
        case .verified(let transaction):
            // Vérifions si l'abonnement est toujours actif
            if transaction.productType == .autoRenewable {
                let currentDate = Date()
                return transaction.expirationDate?.compare(currentDate) == .orderedDescending
            }
            // Pour les achats non renouvelables (comme votre lifetimeProPlan)
            return true
        case .unverified:
            return false
        }
    }
    
    func hasPurchases() async -> Bool {
        return true
        var hasPurchases = false
        for productId in productIdentifiers.allCases {
            let isPurchased = await checkPurchase(for: productId.rawValue)
            if isPurchased {
                hasPurchases = true
            }
        }
        return hasPurchases
    }
}
Puchases / subscriptions issues
 
 
Q