IOS app in app purchase shows up on on some devices but not others

Viewed 43

I am developing an IOS app with in app purchases. While debugging via USB, the in app purchase shows up on all devices and I am able to purchase it. When deployed to TestFlight and installed, the in app purchase shows up on iPhones, but not on iPads. The UI element just doesn't load in that specific scenario. The in app purchase is set up within App Store Connect, and as I mentioned is loading properly when viewed on an iPhone.

Code for the view displaying the IAP:

List(storeManager.myProducts, id: \.self) { product in
            HStack {
                VStack(alignment: .leading) {
                    Text(product.localizedTitle)
                        .font(.headline)
                    Text(product.localizedDescription)
                        .font(.caption2)
                }
                Spacer()
                if UserDefaults.standard.bool(forKey: product.productIdentifier) {
                    Text("Purchased")
                        .foregroundColor(.green)
                } else {
                    Button(action: {
                        storeManager.purchaseProduct(product: product)
                    }) {
                        Text("Buy for \(product.price) $")
                    }
                        .foregroundColor(.blue)
                }
            }
        }
            .navigationTitle("")
            .toolbar(content: {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: {
                        storeManager.restoreProducts()
                    }) {
                        Text("Restore Purchases ")
                    }
                }
            })
            .onAppear(perform: {
                storeManager.myProducts = []
                storeManager.getProducts(productIDs: productIDs)
            })

Code for the StoreManager:

class StoreManager: NSObject, ObservableObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {

//FETCH PRODUCTS
var request: SKProductsRequest!

@Published var myProducts = [SKProduct]()

func getProducts(productIDs: [String]) {
    print("Start requesting products ...")
    let request = SKProductsRequest(productIdentifiers: Set(productIDs))
    request.delegate = self
    request.start()
}

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    print("Did receive response")
    
    if !response.products.isEmpty {
        for fetchedProduct in response.products {
            DispatchQueue.main.async {
                self.myProducts.append(fetchedProduct)
            }
        }
    }
    
    for invalidIdentifier in response.invalidProductIdentifiers {
        print("Invalid identifiers found: \(invalidIdentifier)")
    }
}

func request(_ request: SKRequest, didFailWithError error: Error) {
    print("Request did fail: \(error)")
}

//HANDLE TRANSACTIONS
@Published var transactionState: SKPaymentTransactionState?

func purchaseProduct(product: SKProduct) {
    if SKPaymentQueue.canMakePayments() {
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    } else {
        print("User can't make payment.")
    }
}

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transaction in transactions {
        switch transaction.transactionState {
        case .purchasing:
            transactionState = .purchasing
        case .purchased:
            UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
            queue.finishTransaction(transaction)
            transactionState = .purchased
        case .restored:
            UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
            queue.finishTransaction(transaction)
            transactionState = .restored
        case .failed, .deferred:
            print("Payment Queue Error: \(String(describing: transaction.error))")
            queue.finishTransaction(transaction)
            transactionState = .failed
        default:
            queue.finishTransaction(transaction)
        }
    }
}

func restoreProducts() {
    print("Restoring products ...")
    SKPaymentQueue.default().restoreCompletedTransactions()
}
}

Since everything works properly on all devices via USB debugging, I'm at a loss for how to move forward. Has anybody ever encountered this issue?

1 Answers

It seems like threading issue where the request delegate is operating on another thread. According to documentation of SKProductsRequestDelegate :

Responses received by the SKProductsRequestDelegate may not be returned on a specific thread. If you make assumptions about which queue will handle delegate responses, you may encounter unintended performance and compatibility issues in the future.

Therefore if you do not get the products, your List is not updated. Try checking your DispatchQueue or Task and make sure that you make no assumtions about on which thread the delegate returns. Also if you use the Task and its async/await API, try using the DisptchQueue and vice versa.

Related