O.Olivier
O.Olivier

Reputation: 240

Swift tvOS in-app purchase doesn't respond

I've made an Apple TV app but my in-app purchase doesn't respond. I have no idea what I'm doing wrong. I also don't get any build errors.

I added the in-app purchase to iTunes Connect, added the entitlement to my app ID and added the StoreKit framework. I tested my app on an Apple TV but when I press the button to buy my in-app purchase, nothing happens. When I press the button to restore the in-app purchases it does ask to log in with my apple ID so I guess that works.

This is the class I use for in-app purchases:


    import Foundation
    import StoreKit

    class InAppPurchase : NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {

        let kInAppProductPurchasedNotification = "InAppProductPurchasedNotification"
        let kInAppPurchaseFailedNotification   = "InAppPurchaseFailedNotification"
        let kInAppProductRestoredNotification  = "InAppProductRestoredNotification"
        let kInAppPurchasingErrorNotification  = "InAppPurchasingErrorNotification"

        class var sharedInstance : InAppPurchase {
            struct Static {
                static var onceToken: dispatch_once_t = 0
                static var instance: InAppPurchase? = nil
            }
            dispatch_once(&Static.onceToken) {
                Static.instance = InAppPurchase()
            }
            return Static.instance!
        }

        override init() {
            super.init()

            SKPaymentQueue.defaultQueue().addTransactionObserver(self)
        }

        func buyProduct(product: SKProduct) {
            print("Sending the Payment Request to Apple")
            let payment = SKPayment(product: product)
            SKPaymentQueue.defaultQueue().addPayment(payment)
        }

        func restoreTransactions() {
            SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
        }

        func request(request: SKRequest, didFailWithError error: NSError) {
            print("Error %@ \(error)")
            NSNotificationCenter.defaultCenter().postNotificationName(kInAppPurchasingErrorNotification, object: error.description)
        }

        func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
            print("Got the request from Apple")
            let count: Int = response.products.count
            if count > 0 {
                _ = response.products
                let validProduct: SKProduct = response.products[0] 
                print(validProduct.localizedTitle)
                print(validProduct.localizedDescription)
                print(validProduct.price)
                buyProduct(validProduct);
            }
            else {
                print("No products")
            }
        }

        func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            print("Received Payment Transaction Response from Apple");

            for transaction: AnyObject in transactions {
                if let trans: SKPaymentTransaction = transaction as? SKPaymentTransaction {
                    switch trans.transactionState {
                    case .Purchased:
                        print("Product Purchased")
                        savePurchasedProductIdentifier(trans.payment.productIdentifier)
                        SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
                        NSNotificationCenter.defaultCenter().postNotificationName(kInAppProductPurchasedNotification, object: nil)
                        break

                    case .Failed:
                        print("Purchased Failed")
                        SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
                        NSNotificationCenter.defaultCenter().postNotificationName(kInAppPurchaseFailedNotification, object: nil)
                        break

                    case .Restored:
                        print("Product Restored")
                        savePurchasedProductIdentifier(trans.payment.productIdentifier)
                        SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
                        NSNotificationCenter.defaultCenter().postNotificationName(kInAppProductRestoredNotification, object: nil)
                        break

                    default:
                        break
                    }
                }
            }
        }

        func savePurchasedProductIdentifier(productIdentifier: String!) {
            NSUserDefaults.standardUserDefaults().setObject(productIdentifier, forKey: productIdentifier)
            NSUserDefaults.standardUserDefaults().synchronize()
        }

        func unlockProduct(productIdentifier: String!) {
            if SKPaymentQueue.canMakePayments() {
                let productID: NSSet = NSSet(object: productIdentifier)
                let productsRequest: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set)
                productsRequest.delegate = self
                productsRequest.start()
                print("Fetching Products")
            }
            else {
                print("Сan't make purchases")
                NSNotificationCenter.defaultCenter().postNotificationName(kInAppPurchasingErrorNotification, object: NSLocalizedString("CANT_MAKE_PURCHASES", comment: "Can't make purchases"))
            }
        }

        func buyUnlockSeasonsPack() {
            unlockProduct("Company.SeasonsPack")        
        }
    }

And these are my IBAction functions for my buttons:

@IBAction func getSeasonsPack(sender: UIButton) {
    InAppPurchase.sharedInstance.buyUnlockSeasonsPack()
}

@IBAction func RestorePurchases(sender: UIButton) {
    InAppPurchase.sharedInstance.restoreTransactions()
}

Please help me.

Upvotes: 2

Views: 509

Answers (1)

Will
Will

Reputation: 686

You need retain your productsRequest because it got released automatically at the end of func unlockProduct() and the following callbacks won't be fired.

Simply declare it as a class level variable.

Upvotes: 2

Related Questions