Monday, April 8, 2013

iOS In-App Purchases

This is hopefully a concise getting started with in-app purchases article. Five Lakes Studio has had a fair amount of success with leveraging in-app purchases, and I wanted to share some of the lessons we have learned along the way and also share some code to help make basic in-app purchases a little easier.  This article focuses on non-consumable purchases with built-in product (app) delivery.

Setting Up Your Account


In order to use in-app purchases you have to complete several steps.  A good guide to how to do this is  TN2259 Adding In-App Purchase to your iOS and Mac Applications.   I would recommend taking some time and reading it thoroughly.

One of the key things you need to do is make sure your app has a specific AppId such com.appleseedinc.MyGreatApp.   If your current AppId is using wildcards (".*") you will need to replace it with a fixed AppId and regenerate and reapply the provisioning profiles.  This is because in-app purchases can't be shared across multiple apps.

Creating Test Accounts


You will also need a test account.  These can be created using your iTunes Connect account.




In order to test with the test account, you will need to logout of your current iTunes AppStore account.  You can do that in the "Settings" app under "iTunes & App Stores".


However, don't try to login with the test account through the Settings app.  The test account is only valid in the sandbox environment.  In order to use it, you enter it from within you app when you initiate the in-app purchase.  It will then ask you to login and then you can use the test account you just created.  Once you do this, you can logout of the test account through the Settings app.

Setting up In-App Purchases


You setup the items you want to be purchasable via iTunesConnect.  


This includes picking the type of in-app purchase, it's product id used to reference the purchase from within you app, pricing, and finally the actual text displayed to the user during the purchase process.

This article focuses on Non-consumable In-App Purchases.  These type of purchases only need to be purchased once by users. They do not expire or decrease with use and they can be restored.

Purchasable items need to be approved by apple, and they must include a screenshot.  When you define a new release of an app you can pick which new in-app purchases are available in the app.

It's App Time


Now it's time to start doing work in the app.  At this point, you should have your AppId provision profile set, a test account setup, and one or more non-consumable purchases configured in the App Store through iTunesConnect.  The StoreKit is used to communicate between your app and the AppStore.


The simplest type of in-app workflow to implement is for built in product delivery.  The workflow looks like:


The other workflow is a server based authentication workflow which is significantly more complicated and isn't a focus for this article.

I have made available a class called FLOStoreManager that helps manage the built in product deliver workflow and is part of the Five Lakes Studio Open Library project on GitHub.

FLSOpenLibrary


I'm happy to make FLOStoreManager available under the MIT license as part of the Five Lakes Studio Open Library on GitHub.  The FLOStoreManager class does most of the work to manage the in-app purchase process.   I suggest you download it and at least use it as an example.  

NOTE: The FLOStoreManager makes reference to featureId which is equivalent to to the App Store productIdentifier.

There are 3 main parts to the in product delivery process: Download, Present, and Purchase.

Downloading Purchasing Information


The in-app product definitions are stored on the App Store and must be download to the app before presenting or trying to purchase the item.  This is a download of the product information or meta-data such as product price.   Apple enforces this through the App review process so don't try to skip this step even if you think you can just embed all the meta-data in the app.  Apple still requires your to download and honor this information as it exists in iTunesConnect.

The product definitions contain information such as product description and price. In order to fetch the product definitions, the app needs to know the product ids in advance.  There is no method to query the product ids from the App Store.  This means the App needs to know the product ids by either hard coding them or by other means such as downloading them from a server.

The SKProductsRequest class is used to fetch the product definitions for a set of feature id's.  The FLOStoreManager wraps this in a handy method called startAsyncFetchOfProductForFeatureList.

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSSet *featureSet  = [[NSSet alloc] initWithArray:@[@"ProductId1", @"ProductId2"]];
    [[FLOStoreManager defaultManager] startAsyncFetchOfProductForFeatureList:featureSet];
}

This implementation also does some handy work for you automatically:
  • Auto retry on failed attempts
  • Sends an NSNotification (kFeatureListReady) when the feature list has been successfully retrieved.
  • If the feature list has already been retrieved and matches the given feature then it will only try and update the list once a day.   This is handy since it is called in applicationDidBecomeActive which may get called multiple times as users enter and leave the app.
  • Keeps track of the "registered" features and the product information associated with them
I usually request the product information during applicationDidBecomeActive so it will be downloaded and available as soon as is possible so the purchasable items can be presented to users.

    Present Purchasable Items


    It's up to you to decide how to present the in-app purchases to your users.  However, the App Store uses UIAlert messages which you can not control other then through some text customization.  These customizations are done through iTunesConnect when defining the in-app purchase.

    Picross HD - Daily Puzzle Pack In-App Purchase
    You also shouldn't present purchases for items whose purchasing information hasn't been downloaded.  Apple can reject your app if you try to do that.

    One of the things you are going to want to know from the server is the price of the item.  This can vary based on the country and also can be changed in iTunesConnect.  FLOStoreManager has a handy routine to allow you to retrieve the localized price of the product formatted in a nice string.

    NSString *price = [[FLOStoreManager defaultManager] formattedPriceForFeatureId:@"ProductId1"];
    

    You will also want to take into account some other factors in the UI such as:
    • The act of purchasing an item is done asynchronously so you will probably want something to indicate when a purchasing is in progress,
    • You will need to handle the notifications when the purchase either completed or failed. 

    Purchase


    The SKPaymentQueue is used to initiate a purchase.   It's easy to initiate a purchase, but it does require some tricky handling of the payment queue.  FLOStoreManager tries to make this really easy.  You just call startAsyncPurchaseOfFeature to begin the purchase:

    [[FLOStoreManager defaultManager] startAsyncPurchaseOfFeature:@"ProductId1"];
    

    Once the request is completed, one of two NSNotifications will be sent:
    • kFeaturePurchased is sent if the purchase is successful. The notification object with be the featureId NSString
    • kFeaturePurchasedFailed is sent if the purchase failed.  The notification object with be the featureId NSString
    FLOStoreManager also keeps track of which product ids (features) are in the process of being purchased.  This is handy when updating or displaying a view and you need to know if something is being purchased.


    if( [[FLOStoreManager defaultManager] isFeatureBeingPurchased:@"ProductId1"] )
    {
        // The feature is in the process of being purchased
    }
    
    if( [[FLOStoreManager defaultManager] isAnyFeatureBeingPurchased] )
    {
        // Some feature is being purchased, we don't care which one
    }
    

    One of the limitations with FLOStoreManager is that it doesn't try to do purchase receipt validation. There is a vulnerability in iOS 5.1 and earlier related to in-app receipt validation.   It is not a simple topic to validate a receipt.  Apple has a handy article that talks through the "In-App Purchase Receipt Validation on iOS".  If people are willing to go through the hassle to hack my app to save a buck or two then so be it, I don't think they would be willing to pay for it anyway.  Perhaps some day they will change there ways.

    You will also need to handle the condition of a purchase request that gets completed after the user has left your app.  In order to handle this case, one of the first things the app should do is setup the  SKPaymentQueue in application:didFinishLaunchingWithOptions by adding an observer.  This needs to be done so the app can receive payment notifications from the AppStore.  This is all taken care of by FLOStoreManager just by getting the defaultManager.

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        [FLOStoreManager defaultManager];  // Allows us to receive AppStore notification
    }
    

    Once the user starts purchasing items, how do you track what has been purchased?

    Purchase Tracking


    It is up to the app to figure out how to store and keep track of purchased items.  The StoreKit does provide a method called restoreCompletedTransactions which is available as part of SKPaymentQueue  to restore non-consumable purchases.  However, it requires that the user provide their authentication to the Apple Store.  So it's usually called in response to an explicit action by the user to restore purchases.

    FLOStoreManager exposes the ability to restore purchases through its own restoreCompletedTransactions method.  Using  FLOStoreManager also has the added benefit of sending kFeaturePurchased notifications for items that where purchased and needed to be restored.  It will also send a kPurchaseRestoredCompleted notification after all items have been restored.

    The other big advantage of FLOStoreManager is it has a built in mechanism to keep track of non-consumable purchases.  It uses the built-in keychain to keep track of purchases.  The nice thing about the keychain is that it persists across deletes of the app.  You can also instantly query FLOStoreManager to see what has been purchased.

    if( [[FLOStoreManager defaultManager] isPurchased:@"ProductId1"] )
    {
        // The feature has been purchased
    }
    

    Conclusion


    Wow you made it this far.  This turned out to be a wee bit longer then expected, but I hope you found it useful.

    Please feel free to contribute to the FLS Open Library.  I wasn't planning on making this open source so it wasn't designed to be general use, but I think it provides a good start and example.  It also contains a lot more functionality then discussed here including an extension to handle consumable purchases.

    Please feel free to follow me on twitter at @fivelakesstudio. I would love to hear about your in-app purchase experiences.

    Thanks for reading and be sure to visit us at Five Lakes Studio.

    References


    https://github.com/FiveLakesStudio/FLSOpenLibraryIOS.git
    TN2259 Adding In-App Purchase to your iOS and Mac Applications
    In-App Purchase Programming Guide
    In-App Purchase Receipt Validation on iOS
    iPhone Tutorial – In-App Purchases By Mugunth Kumar


    1 comment:

    1. This post is very encouraging and helpful for me. Thanks for sharing your ideas with us.

      online logo design services

      ReplyDelete