Search code examples
iosin-app-purchaseapp-storeios11

How to handle shouldAddStorePayment for In-App Purchases in iOS 11?


I'm trying to implement that new paymentQueue(_:shouldAddStorePayment:for:) method so my app can handle IAPs directly from the App Store.

I'm using the itms-services:// url to test it, like it says here.

The thing is, my SKPaymentTransactionObserver is a specific view controller, and if it's not visible when I open the itms-services:// link the delegate method won't be called.

What can I do about that? I think I'd have to detect if the user is coming from the App Store to push the right view controller, but I don't know how. The only other option I can think of right now is to make the App Delegate an SKPaymentTransactionObserver, but that seems really cumbersome and I couldn't get it to work when I tried it. Is there any other way?


Solution

  • Here's a class I did that can help you achieve what you want, simply copy the code below and paste it inside a new file and then you can simply access the class StoreManager.shared to whatever method/variable you want to access.

    1- To init this class, just call from your didFinishLaunchingWithOptions StoreManager.shared.Begin() and then payment observer is added.

    import Foundation
    import StoreKit
    
    
    class StoreManager: NSObject{
    
        /**
         Initialize StoreManager and load subscriptions SKProducts from Store
         */
        static let shared = StoreManager()
    
        func Begin() {
            print("StoreManager initialized"))
        }
    
        override init() {
            super.init()
    
            // Add pyament observer to payment qu
            SKPaymentQueue.default().add(self)
        }
    
        func requestProductWithID(identifers:Set<String>){
    
            if SKPaymentQueue.canMakePayments() {
                let request = SKProductsRequest(productIdentifiers:
                    identifers)
                request.delegate = self
                request.start()
            } else {
                print("ERROR: Store Not Available")
            }
        }
    
        func buyProduct(product: SKProduct) {
    
            print("Buying \(product.productIdentifier)...")
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
        }
    
        func restorePurchases() {
    
            SKPaymentQueue.default().restoreCompletedTransactions()
        }
    }
    
    // MARK:
    // MARK: SKProductsRequestDelegate
    
    //The delegate receives the product information that the request was interested in.
    extension StoreManager:SKProductsRequestDelegate{
    
        func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    
            var products = response.products as [SKProduct]
    
            var buys = [SKProduct]()
    
            if (products.count > 0) {
                for i in 0 ..< products.count {
                    let product = products[i]
                    print("Product Found: ",product.localizedTitle)
                }
            } else {
                print("No products found")
            }
    
            let productsInvalidIds = response.invalidProductIdentifiers
    
            for product in productsInvalidIds {
                print("Product not found: \(product)")
            }
        }
    
        func request(_ request: SKRequest, didFailWithError error: Error) {
            print("Something went wrong: \(error.localizedDescription)")
        }
    }
    
    // MARK:
    // MARK: SKTransactions
    
    extension StoreManager: SKPaymentTransactionObserver {
    
        public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            for transaction in transactions {
                switch (transaction.transactionState) {
                case .purchased:
                    completeTransaction(transaction: transaction)
                    break
                case .failed:
                    failedTransaction(transaction: transaction)
                    break
                case .restored:
                    restoreTransaction(transaction: transaction)
                    break
                case .deferred:
                    // TODO show user that is waiting for approval
    
                    break
                case .purchasing:
                    break
                }
            }
        }
    
        private func completeTransaction(transaction: SKPaymentTransaction) {
    
            print("completeTransaction...")
    
            deliverPurchaseForIdentifier(identifier: transaction.payment.productIdentifier)
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    
        private func restoreTransaction(transaction: SKPaymentTransaction) {
    
    
            guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
    
            print("restoreTransaction... \(productIdentifier)")
    
    
            deliverPurchaseForIdentifier(identifier: productIdentifier)
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    
        private func failedTransaction(transaction: SKPaymentTransaction) {
    
            if let error = transaction.error as NSError? {
                if error.domain == SKErrorDomain {
                    // handle all possible errors
                    switch (error.code) {
                    case SKError.unknown.rawValue:
                        print("Unknown error")
    
                    case SKError.clientInvalid.rawValue:
                        print("client is not allowed to issue the request")
    
                    case SKError.paymentCancelled.rawValue:
                        print("user cancelled the request")
    
                    case SKError.paymentInvalid.rawValue:
                        print("purchase identifier was invalid")
    
                    case SKError.paymentNotAllowed.rawValue:
                        print("this device is not allowed to make the payment")
    
                    default:
                        break;
                    }
                }
    
            }
    
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    
        private func deliverPurchaseForIdentifier(identifier: String?) {
    
            guard let identifier = identifier else { return }
    
        }
    }
    
    //In-App Purchases App Store
    extension StoreManager{
    
        func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
            return true
    
            //To hold
            //return false
    
            //And then to continue
            //SKPaymentQueue.default().add(savedPayment)
        }
    }