search by tags

for the user

adventures into the land of the command line

In App Purchase - The Code

Here’s my sample IAP Class which contains all the things you need. (That I know of so far…)

It has a function to check if the user’s iTunes account can make payments. Meaning they have added a payment method to their itunes account:

requestProductInfo() -> SKPaymentQueue.canMakePayments()

If yes, it requests from apple, the IAP products you created in your iTunes Connect Account when submitting the app:

requestProductInfo() -> SKPaymentQueue.canMakePayments() -> productRequest.start() -> productsRequest(...)

It has a function to initiate a new purchase:

beginTransaction(...) -> .addPayment -> paymentQueue(...) -> case .Purchased -> .finishTransaction

And another to initiate the restoring of already purchased items (in the case where a user gets a new phone):

restorePurchase() -> .restoreCompletedTransactions -> paymentQueue(...) -> case .Restored -> .finishTransaction -> paymentQueueRestoreCompletedTransactionsFinished(...)

These two functions add transactions to a payment queue which completes them asynchronously and on completion you decide what to unlock in you app. It also has some functions for failed purchases or restorations and space to do something in those scenarios.

Here’s the complete swift file, but note at the top, productIDs. These array elements have to be named exactly what you named them in iTunes Connect when you created them. If you make a typo, productsRequest(…) will not find it.

--- IAPHelpers.swift ---

import StoreKit

public var productIDs = NSSet(array: ["my.product.id1", "my.product.id2", "my.product.id3"])
public var productRequest = SKProductsRequest()
public var productArray: Array = []
public var currentProductID: String! = ""
public var product: SKProduct?
public var transactionInProgress = false
public var canMakePayments = false

class IAPHelpers: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {

    func requestProductInfo() -> Bool {
        let result: Bool
        if SKPaymentQueue.canMakePayments() {
            productRequest = SKProductsRequest(productIdentifiers: productIDs as! Set)
            productRequest.delegate = self
            productRequest.start()
            print("Can perform In App Purchases.")
            result = true
        }
        else {
            print("Cannot perform In App Purchases.")
            result = false
        }
        return result
    }

    func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
        if response.products.count != 0 {
            print("Here come the products.")
            if response.invalidProductIdentifiers.count != 0 {
                print(response.invalidProductIdentifiers.description)
            }
            else {
                for product in response.products {
                    productArray.append(product)
                    print(productArray)
                }
            }
        }
        else {
            print("Waiting for the products.")
        }
    }

    func beginTransaction(payment: SKPayment) {
        if transactionInProgress == true {
            return
        }
        print("Beginning transaction...")
        SKPaymentQueue.defaultQueue().addPayment(payment)
        print("adding payment...")
        transactionInProgress = true
    }

    func restorePurchase() {
        if transactionInProgress == true {
            return
        }
        SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
        transactionInProgress = true
    }

    func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction: SKPaymentTransaction in transactions {
            switch transaction.transactionState {
            case .Purchased:
                if ( currentProductID == "my.product.id1" ) {
                    print("unlock something")
                }
                if ( currentProductID == "my.product.id2" ) {
                    print("unlock something")
                }
                if ( currentProductID == "my.product.id3" ) {
                    print("unlock something")
                }
                SKPaymentQueue.defaultQueue().finishTransaction(transaction)
                transactionInProgress = false
                print("Transaction completed successfully.")
                break
            case .Failed:
                SKPaymentQueue.defaultQueue().finishTransaction(transaction)
                print("Transaction Failed")
                transactionInProgress = false
                break
            case .Restored:
                SKPaymentQueue.defaultQueue().finishTransaction(transaction)
                print("Transaction Restored")
                transactionInProgress = false
                break
            default:
                break
            }
        }
    }

    func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {

    }

    func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
        for transaction:SKPaymentTransaction in queue.transactions {
            if ( transaction.payment.productIdentifier == "my.product.id1" ) {
                print("unlock something")
            }
            if ( transaction.payment.productIdentifier == "my.product.id2" ) {
                print("unlock something")
            }
            if ( transaction.payment.productIdentifier == "my.product.id3" ) {
                print("unlock something")
            }
        }
        print("Purchased Transactions Restored")
    }

    func paymentQueue(queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {

    }

    func paymentQueue(queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) {

    }

}

Including the file above won’t actually do anything on it’s own. You need to use it in other parts of your app. How can we do so? This code sample is from the first ViewController “Index” that gets loaded when you enter the app. We have a button which takes us to a page with a list of things to buy. The button is disabled at first, but when the view loads, it check if the user’s iTunes account can make payments and if yes, enables the button.

--- Index.swift ---

class Index: UIViewController {

    @IBOutlet weak var toPurchasesViewButton: UIButton!

    let paymentTransactionObserver = IAPHelpers()

    .
    .
    .

    override func viewWillAppear(animated: Bool) {
        if canMakePayments == true {
            self.toPurchasesViewButton.enabled = true
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        .
        .
        .

        // In App Purchase checks
        if canMakePayments == false {
            self.toPurchasesViewButton.enabled = false
            canMakePayments = paymentTransactionObserver.requestProductInfo()
        }

    .
    .
    .

    }

}

Clicking the button has been set up to segue us to this other View Controller which has one button to buy something and one button to restore purchases.

--- Purchase.swift ---

import StoreKit

class PurchaseViewController: UIViewController {

    let paymentTransactionObserver = IAPHelpers()

    @IBOutlet weak var purchaseButton: UIButton!
    @IBOutlet weak var restoreButton: UIButton!

    @IBAction func purchaseSomething(sender: UIButton) {
        for product in productArray {
            if product.productIdentifier == "my.product.id1" {
                currentProductID = product.productIdentifier
                purchaseButton.enabled = false
                paymentTransactionObserver.beginTransaction(SKPayment(product: product))
            }
        }
    }

    @IBAction func restoreSomething(sender: UIButton) {
        paymentTransactionObserver.restorePurchase()
        restoreButton.enabled = false
    }

}

Lastly, I forgot why we have to include these here in the AppDelegate.swift file, but I just know when we don’t, it doesn’t work.

--- AppDelegate.swift ---

import StoreKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let paymentTransactionObserver = IAPHelpers()

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        SKPaymentQueue.defaultQueue().addTransactionObserver(paymentTransactionObserver)
        return true
    }

    .
    .
    .

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        SKPaymentQueue.defaultQueue().removeTransactionObserver(paymentTransactionObserver)
    }
}

And that’s all I know about Apple’s In-App Purchase for now :?