Search code examples
iosswiftin-app-purchase

In-app purchases fail only in first time app launch


I have in-app purchase in my app. My purchase is activated after button click. But if I open app first time and click on button I have this result: No products found with identifier

from this function:

func purchaseProduct(identifier:String) {
    guard let product = self.product(identifier: identifier) else {
        print("No products found with identifier: \(identifier)")
            
        // fire purchase status: failed notification
        delegate?.purchaseStatusDidUpdate(PurchaseStatus.init(state: .failed, error: PurchaseError.productNotFound, transaction: nil, message:"An error occured"))
        return
    }
    //.etc
}

But if I press the button again, everything works fine. Also if I close app, open again and press the button mu purchase works fine. Why this happens only in first time app launch? And how to fix it?

I use this code for in-app purchases https://stackoverflow.com/a/51688015

Update

There is the following class and I think the point is in it:

class MyAppProductStore: Store {
    static let shared = MyAppProductStore()
    
    // purchase processor
    var itunes: iTunesStore = iTunesStore()
    
    init() {
        // register for purchase status update callbacks
        itunes.delegate = self
        
        validateProducts(MyProductIds.allIdentifiers())
    }
    
    /// This function is called during the purchase/restore purchase process with each status change in the flow. If the status is complete then access to the product should be granted at this point.
    ///
    /// - Parameter status: The current status of the transaction.
    internal func processPurchaseStatus(_ status: PurchaseStatus) {
        switch status.state {
        case .initiated:
            // TODO: show alert that purchase is in progress...
            break
        case .complete:
            if let productID = status.transaction?.payment.productIdentifier {
                // Store product id in UserDefaults or some other method of tracking purchases
                UserDefaults.standard.set(true , forKey: productID)
                UserDefaults.standard.synchronize()
            }
        case .cancelled:
            break
        case .failed:
            // TODO: notify user with alert...
            break
        }
    }
}

extension MyAppProductStore: iTunesPurchaseStatusReceiver, iTunesProductStatusReceiver {
    func purchaseStatusDidUpdate(_ status: PurchaseStatus) {
        // process based on received status
        processPurchaseStatus(status)
    }
    
    func restoreStatusDidUpdate(_ status: PurchaseStatus) {
        // pass this into the same flow as purchasing for unlocking products
        processPurchaseStatus(status)
    }
    
    func didValidateProducts(_ products: [SKProduct]) {
        print("Product identifier validation complete with products: \(products)")
        // TODO: if you have a local representation of your products you could
        // sync them up with the itc version here
    }
    
    func didReceiveInvalidProductIdentifiers(_ identifiers: [String]) {
        // TODO: filter out invalid products? maybe... by default isActive is false
    }
}

if I click on button after calling it, everything will work fine.

To call it I use the following line var store = MyAppProductStore() in AppDelegate it is look like this:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var store = MyAppProductStore()
.etc

Is this the fastest way to call the class?


Solution

  • Looking at the code you linked to, you have to call fetchStoreProducts first, then make sure the products have loaded before you call "self.product". The original code should probably be altered to include some kind of callback that is activate when the product load request is complete.