You don’t close a sale, you open a relationship if you want to build a long-term, successful enterprise.

—Patricia Fripp

Throughout this book so far, we have been working with both Game Center and Game Kit to add rich social networking into your apps. However, there is another important feature slowly becoming more popular in modern software: in-app purchases. Allowing your users to purchase upgrades or additional content for your app, from directly within your app, opens up a potentially significant new revenue stream. Over the past few years, a new business model has emerged called freemium. Freemium is a new type of game or product that is offered to your users for free, but is monetized through selling add-ons. The 100 Top Grossing apps on the App Store consist mostly of free apps; when this chapter was written, 81 of the 100 Top Grossing apps were all free to download, seen in Figure 11-1.

Figure 11-1.
figure 1figure 1

Top Grossing apps on the App Store, showing the dominance of the free-to-play model

Let’s consider the popular We Rule by ngmoco:) as an example of a highly successful free-to-play game. The game is free to download and play on both iPhones and iPads. Each user is in control of a virtual kingdom, in which they are responsible for constructing buildings and growing crops. The user generates “mojo” over time and can use that in-app currency to create new structures and farms. However, some users want to construct faster than is normally allowed because of the restrictive nature of mojo, which accumulates slowly.

These power users can visit the in-app store to purchase more mojo in bulk. Shown in Figure 11-2 is We Rule’s built-in store. It offers a number of purchases ranging from very affordable to shockingly expensive. It is important to cater to both types of users when working with sellable add-ons. Some of your users will be interested in spending one or two dollars occasionally, while others will be power users who want to spend one hundred dollars, or more, at a time.

Figure 11-2.
figure 2figure 2

The in-app purchase store, as seen in We Rule by ngmoco:)

The freemium model has become such a profitable approach that ngmoco:) has stopped working on games that do not fit into the model, going so far as to even cancel Rolando 3 in mid-development because it couldn’t be adapted to a freemium-type game. The model appears to be paying off well for ngmoco:). As shown in Figure 11-3, the current highest-selling item in the We Rule store costs $9.99. This one in-app purchase is retailing for more than most stand-alone iOS games. It is able to get that sale because the customer becomes committed to the game while it is free.

Not all games or apps supported by in-app purchase need to be free. In fact, until recently, you were not allowed to implement in-app purchase in free apps. You can easily add additional features or unlocks in a paid game, such as the Mighty Eagle in Angry Birds. In-app purchase is also not only just for games. Almost any software can benefit from it, whether you are unlocking pro-level features or charging users a subscription for push notification support. Throughout this chapter we will explore how to add a full-featured in-app store to your iOS software.

Figure 11-3.
figure 3figure 3

Current listing of the best-selling in-app purchases for We Rule by ngmoco:)

Setting Up Your App in iTunes Connect

As with Game Center, the first step is a visit to iTunes Connect to configure your app for purchases.

  1. 1.

    Log in to iTunes Connect ( http://itunesconnect.apple.com ), as discussed in Chapter 2. You will need an existing project to work on. If you don’t have a project created in iTunes Connect yet, go ahead and create one.

  2. 2.

    Select the project to which you want to add in-app purchase support. Then, click the button called Manage In-App Purchases, as shown in Figure 11-4.

Important

You have 90 days from creating an app to upload a binary for review. Make sure to save the in-app purchase configuration until you are within 90 days of finishing your project.

Figure 11-4.
figure 4figure 4

App listing in iTunes Connect showing Manage In-App Purchases button

  1. 3.

    Selecting the Manage In-App Purchases button will bring you to a screen for setting up a new product, as shown in Figure 11-5. Once there, click the Create New button in the upper-left corner of the window.

Figure 11-5.
figure 5figure 5

Setting up your first in-app purchase in iTunes Connect

There are several types of in-app purchase products that you can configure:

  • Consumable: A consumable in-app purchase must be purchased every time the user downloads it. These include in-game currency, as we saw in the We Rule example in the previous section. Figure 11-6 shows the consumable purchase setup screen.

Figure 11-6.
figure 6figure 6

Setup screen for both consumable and non-consumable purchases in iTunes Connect

  • Non-Consumables: A non-consumable purchase needs to be purchased only once by each user, and is often used for unlockable features. Examples of non-consumable purchases include additional levels, reusable power-ups, or additional content. The setup screen is the same as for consumables.

  • Auto-Renewable Subscriptions: An auto-renewable subscription allows the user to purchase in-app content for a set duration of time. At the end of that time, the subscription will automatically renew and charge the customer unless they opt out. Magazines and newspapers follow this model, delivering a new issue every week or month until the user opts out. Figure 11-7 shows the auto-renewable purchase setup screen.

Figure 11-7.
figure 7figure 7

Setup screen for auto-renewable purchases in iTunes Connect

  • Non-Renewing Subscriptions: For the most part, renewable subscriptions have eliminated the need for this model. A non-renewing subscription functions the same as an auto-renewable subscription, except that a user is required to renew it every time it is set to expire.

Note

Auto-renewable subscriptions will be sent to all devices associated with the user’s Apple ID.

We will begin by adding a non-consumable purchase item to our sample UFO game.

The first item we want to add is a paid upgrade to the user’s current ship; name the item com.dragonforged.ufo.newShip1. In this example the same title is used for both the product ID and the reference name. The reference name is used when searching in iTunes Connect, whereas the product ID is what will be used in your code base to identify this item.

After you have created a new item, you need to add at least one localized description and title, as shown in Figure 11-8. The last thing that you need to do is select a pricing tier for this item. You might have also noticed that there is a section for uploading a screenshot; this will be discussed in the later section “Submitting a Purchase GUI Screenshot.”

Figure 11-8.
figure 8figure 8

Adding a localized description to a product in iTunes Connect

Adding a consumable product follows the same procedure as adding a non-consumable product. If you want to add a subscription-based product, there are a few new fields that you need to be aware of, as shown in Figure 11-9. When configuring a subscription, you need to define a duration. iTunes Connect allows any of the following: one week, one month, two months, three months, six months, or one year. You also have the option to offer a free subscription if the user agrees to a marketing campaign, such as providing you with their e-mail address.

Figure 11-9.
figure 9figure 9

Configuring subscription duration in iTunes Connect

You should now have at least one product configured for in-app purchase. Your screen in iTunes Connect should look similar to Figure 11-10. This completes the initial configuration that is needed in iTunes Connect to get in-app purchases working. In the next section, we begin to work with the code required to complete a purchase on a device.

Note

Don’t worry about the “Waiting for Screenshot” error yet; this will be handled later in the process. You will still be able to test your purchases while waiting to upload a screenshot.

Figure 11-10.
figure 10figure 10

Products set up and ready for use in our app

Adding Products to Your App

Apple does not offer a predesigned GUI for in-app purchases. You, as the developer, are required to design a storefront for your user. In this section, you will learn how to get products that you have added in iTunes Connect to show up for sale in your app.

Note

It can take several hours for new purchases and changes to be reflected. If you double-check everything and are still not seeing products, wait a few hours and try again.

App IDs and In-App Purchase

When working with in-app purchases, Apple requires that your App ID does not include a wildcard, such as 76P4G6KX56.*. You are required to have a unique App ID, such as 76P4G6KX56.com.dragonforged.ufo. If you do not have a unique App ID, you need to create one. Use the following steps to create a new unique App ID.

  1. 1.

    Navigate to http://developer.apple.com/iPhone in your web browser and select the iPhone Developer Program Portal from the list on the right.

  2. 2.

    Select App ID from the column on the left, and select the New App ID button in the upper right.

  3. 3.

    Fill in the required information about your app.

  4. 4.

    Click Submit.

  5. 5.

    Click the Configure button next to the listing, and make sure In-App Purchase is turned on (it should be on by default).

Setting Up

We will begin by requesting a list of products from our app. First, add the StoreKit framework to your project. We will be modifying our existing UFO project from the previous chapter; you can follow along in your own project if that is more convenient.

Important

In-App Purchase does not work on the simulator; all testing needs to be done on a device.

Create a new class called UFOStoreViewController. We will use this class to display a store to the user. Set up the header to match the following:

#import <UIKit/UIKit.h>

#import <StoreKit/StoreKit.h>

@interface UFOStoreViewController : UIViewController <SKProductsRequestDelegate>

{

    SKProductsRequest *productsRequest

}

@end

As you can see, we imported the StoreKit headers. Set up the SKProductsRequestDelegate, and create a new object to hold the product request. We need to create a way for the user to access the store, so go ahead and add a button to the main screen and relevant code to present the new view controller (UFOStoreViewController).

Retrieving the Product List

Modify the viewDidLoad and viewDidUnload methods of our new store view controller to begin a new store request using the product identifiers that were set up in iTunes Connect. You may need to modify your product identifiers to match the ones that you set up in the previous section.

- (void)viewDidLoad

{

    [super viewDidLoad];

    NSSet *productIdentifiers = [NSSet setWithObjects:É

@"com.dragonforged.ufo.newShip1", @"com.dragonforged.ufo.subscription", nil];

    productsRequest = [[SKProductsRequest alloc]É

 initWithProductIdentifiers:productIdentifiers];

    productsRequest.delegate = self;

    [productsRequest start];

}

- (void)viewDidUnload

{

    productsRequest.delegate = nil;

}

The product request is released in the delegate callback, shown next. Right now, this method just prints your product information to the console and logs any invalid products.

- (void)productsRequest:(SKProductsRequest *)requestÉ

 didReceiveResponse:(SKProductsResponse *)response

{

    NSArray *productArray= [response products];

    for (SKProduct *product in productArray) {

    NSLog(@"Product title: %@" , product.localizedTitle);

    NSLog(@"Product description: %@" , product.localizedDescription);

    NSLog(@"Product price: %@" , product.price);

    NSLog(@"Product id: %@\n\n" , product.productIdentifier);

    }

    for (NSString *invalidProduct in response.invalidProductIdentifiers)

    {

    NSLog(@"Invalid: %@" , invalidProduct);

    }

    [request release];

}

Note

Although you can retrieve a list of invalid product identifiers using the code in this section, there are no associated errors to determine why a product is being flagged as invalid. Most often, the product ID is mistyped or not enough time has passed for the product to be distributed to the servers.

If you were to run the game now and navigate to the store, you should get output similar to the following:

2011–08-19 17:31:23.970 UFOs[3580:707] Product title: Ship+

2011–08-19 17:31:23.973 UFOs[3580:707] Product description: Paint your ship and show off to your friends

2011–08-19 17:31:23.975 UFOs[3580:707] Product price: 8.99

2011–08-19 17:31:23.977 UFOs[3580:707] Product id: com.dragonforged.ufo.newShip1

2011–08-19 17:31:23.979 UFOs[3580:707] Product title: Subscription

2011–08-19 17:31:23.981 UFOs[3580:707] Product description: A subscription service

2011–08-19 17:31:23.983 UFOs[3580:707] Product price: 1.99

2011–08-19 17:31:23.987 UFOs[3580:707] Product id: com.dragonforged.ufo.subscription

Note

It can take several seconds to get a response from the product request. Best practices dictate that you should present your user with some sort of loading indicator during this loading time.

Those are all the steps required to retrieve your products from Apple’s servers. In the next section we present this data to the user with a standard table view.

Presenting Your Products to the User

Begin by adding a table view to the store view controller. Don’t forget to hook up the data source and delegates, as required. We also add a new property to our class to hold the products. Create a new class array named productArray and set the product results to it in the productsRequest method.

Add the two required table view delegate and data source methods to your class, as shown in the following code snippets:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

    return [productArray count];

}

- (UITableViewCell *)tableView:(UITableView *)tableViewÉ

 cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableViewÉ

 dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil)

{

        cell = [[[UITableViewCell alloc] initWithStyle:É

UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];

        cell.selectionStyle = UITableViewCellSelectionStyleNone;

    }

    SKProduct *product = [self.productArray objectAtIndex: [indexPath row]];

    cell.textLabel.text = [NSString stringWithFormat:@"%@ - $%@",É

 product.localizedTitle, product.price];

    cell.detailTextLabel.text = product.localizedDescription;

    return cell;

}

The first method simply returns the number of products that we retrieved from Apple’s servers for the number of rows in the table. When we display them as a cell, we use the built-in UITableViewCellStyleSubtitle. We set the main label to the product title and price, and use the detail label to display the description. All that remains is to add a reload table method to the end of the productsRequest method. Upon running the game again, your output should look similar to that shown in Figure 11-11.

Note

Although the API returns a localized title and description, it doesn’t localize the price. You need to take this extra step yourself in international apps.

Figure 11-11.
figure 11figure 11

Displaying a list of products in a standard table view

Purchasing a Product

In the previous section, you learned how to add products to your app. Without the ability to purchase these products, our implementation is only partially complete. In this section, we look at how to handle purchasing products directly through your app.

Purchasing Code

The first thing that we need to do is make the UFOStoreViewController class conform to the SKPaymentTransactionObserver protocol. After that is done, we modify our existing viewDidLoad method. Add self as a new transaction observer. Additionally, we perform a test to make sure that we can make payments on this device, and if not, display a UIAlert to inform the user.

- (void)viewDidLoad

{

    [super viewDidLoad];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    if ([SKPaymentQueue canMakePayments])

{

        NSSet *productIdentifiers = [NSSet setWithObjects:É

@"com.dragonforged.ufo.newShip1", @"com.dragonforged.ufo.subscription", nil];

        productsRequest = [[SKProductsRequest alloc]É

 initWithProductIdentifiers:productIdentifiers];

        productsRequest.delegate = self;

        [productsRequest start];

    }

    else

 {

        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:É

@"Unable to make purchases with this device." delegate:nil cancelButtonTitle:@"Dimiss"É

 otherButtonTitles: nil];

        [alert show];

        [alert release];

    }

}

Next, we need to add a didSelectRowAtIndexPath method to register selection events in the table view.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:É

(NSIndexPath *)indexPath

{

    SKProduct *product = [self.productArray objectAtIndex: [indexPath row]];

    SKPayment *payment = [SKPayment paymentWithProductIdentifier:É

 product.productIdentifier];

    [[SKPaymentQueue defaultQueue] addPayment:payment];

}

If you were to run the app now and select a table row, you would get a confirmation alert, as shown in Figure 11-12. However, we have not yet written any code to process this transaction, nor have you set up a test user, so this is as far as you can currently go. We’ll move on to those stages after a quick look at the code for multiple purchases of an item.

Figure 11-12.
figure 12figure 12

Confirming a purchase in the Sandbox

Purchasing Multiple Items

Apple has made it easy to allow your users to purchase multiple items at a time. The following code snippet can be used to bulk-purchase multiple quantities of an item at one time, such as a user purchasing five packs of 100 gold:

SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:É

 com.dragonforged.rpg.100gold];

payment.quantity = 5;

[[SKPaymentQueue defaultQueue] addPayment: payment];

Processing a Transaction

After the user has requested a purchase, there are several steps needed in order to ensure that the purchase is completed successfully. First, we implement the required method from the SKPaymentTransactionObserver. As you can see in the following code example, we test the current transaction state and then call some new methods, depending on whether the transaction succeeded, failed, or restored:

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

{

    for (SKPaymentTransaction *transaction in transactions)

   {

        if ([transaction transactionState] == SKPaymentTransactionStatePurchased)

        {

        [self transactionDidComplete:transaction];

        }

        else if ([transaction transactionState] == SKPaymentTransactionStateFailed)

        {

            [self transactionDidFail:transaction];

        }

        else if ([transaction transactionState] == SKPaymentTransactionStateRestored)

        {

            [self transactionDidRestore:transaction];

        }

        else

        {

            NSLog(@"Unhandled case: %@", transaction);

        }

    }

}

We need to implement some convenience methods to help streamline the process. If a transaction completed successfully or is restored, we need to record the transaction event, unlock the content that the user purchased, and perform some cleanup. If the transaction failed or was cancelled, we just need to perform the cleanup and probably notify the user that something went wrong. Here’s the code:

- (void)transactionDidComplete:(SKPaymentTransaction *)transaction

{

    [self recordTransactionData:transaction];

    [self unlockContent:[[transaction payment] productIdentifier]];

    [self finishTransaction:transaction withSuccess:YES];

}

- (void)transactionDidRestore:(SKPaymentTransaction *)transaction

{

    [self recordTransactionData:transaction.originalTransaction];

    [self unlockContent:[[[transaction originalTransaction] payment]É

 productIdentifier]];

    [self finishTransaction:transaction withSuccess:YES];

}

- (void)transactionDidFail:(SKPaymentTransaction *)transaction

{

    if ([[transaction error] code] != SKErrorPaymentCancelled)

   {

        [self finishTransaction:transaction withSuccess:NO];

    }

    //SKErrorPaymentCancelled

    else

    {

        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    }

}

Now let’s take a more detailed look at each of the methods that we are calling, starting with the recordTransactionData method. The main purpose of this method is to keep a virtual paper trail for our purchases. We are using NSUserDefaults to hold on to an array of all completed transactions, which allows us to check transaction data at any point in the future:

- (void)recordTransactionData:(SKPaymentTransaction *)transaction

{

    NSArray *transactions = [[NSUserDefaults standardUserDefaults]É

 objectForKey:@"transactions"];

    NSMutableArray *transactionArray = [transactions mutableCopy];

    [transactionArray addObject:[transaction transactionReceipt]];

    [[NSUserDefaults standardUserDefaults] setObject:transactionArrayÉ

 forKey:@"transactions"];

    [transactionArray release];

}

Next, take a look at the unlockContent method. This is where your app might differ. In this example, we set a flag in the NSUserDefaults that we can check to see whether the user has purchased a feature. Depending on how your app is structured, you might want to take a different approach to unlocking content, but no matter what approach you take, remember that you need to preserve the unlocked content through app restarts. See the section “Tying Everything Together in UFOs” for a sample of how to implement this approach.

- (void)unlockContent:(NSString *)productId

{

    if ([productId isEqualToString:@"com.dragonforged.ufo.newShip1"])

   {

        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:É

@"shipPlusAvailable" ];

    }

    if ([productId isEqualToString:@"com.dragonforged.ufo.subscription"])

   {

        [[NSUserDefaults standardUserDefaults] setBool:YESÉ

 forKey:@"subscriptionAvailable" ];

    }

}

The last step that is taken for both successful and unsuccessful purchases is to perform a bit of cleanup on the transaction process. The most important step in the following method is to call the finishTransaction method. We also log the results of the transaction for debugging purposes. Until you have called finishTransaction, the transaction remains open and in the system.

- (void)finishTransaction:(SKPaymentTransaction *)transaction withSuccess:(BOOL)success

{

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    NSDictionary *transactionDictionary = [NSDictionaryÉ

 dictionaryWithObjectsAndKeys:transaction, @"transaction" , nil];

    if (success)

    {

        NSLog(@"Transaction was successful: %@", transactionDictionary);

    }

    else

   {

        NSLog(@"Transaction was unsuccessful: %@", transactionDictionary);

    }

}

Restoring Previously Completed Transactions

Often, your users will need to restore purchases that they have previously made. This could happen if they have reinstalled your app or have begun using it on a different device. It is important to always add a path for your user to download all of their content again. Luckily, Apple has planned ahead for this scenario and has provided a simple method for restoring the user’s purchases.

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

This will repurchase all of your content as if the user had selected it from your store. You will receive appropriate callbacks to the paymentQueue:updatedTransactions method and can use your existing code to unlockcontent.

Test Accounts and Testing Purchases

If you were to try to purchase one of your items in the Sandbox now, you would receive an account error. You need to first create a new test account in order to be able to test purchases without being charged for them. To set up a new test user, you need to log in to iTunes Connect ( http://iTunesConnect.apple.com ). Select the Manage User section from the main screen of iTunes Connect; from here, select the option for a new Test User.

Test users do not need to use a real e-mail address, and you will want to select something quick to type and easy to remember, such as abc@def.com. Although you do need to enter a date of birth and other identifying information, there is no reason you cannot fabricate this data. Make sure to select the iTunes Store that is appropriate to test the app’s localization against. You can make a new account for each region that you will test with.

Signing In with a Test Account

You cannot simply sign in with your test account in the Settings App. If you did, you would be forced to agree to the standard user agreement and prompted to enter a credit card number. In order to resolve this issue, you need to use the Settings App to log out of your existing iTunes account. After you are logged out of an account, you will be prompted to log in or create a new account during a purchase attempt. This is where you will enter your test account credentials.

Note

If you are testing on your primary device, don’t forget to revisit the Settings App to log out of your test account before making real purchases or downloading updates.

Submitting a Purchase GUI Screenshot

We talked briefly about this step earlier in this chapter. Apple requires that you submit a screenshot of your in-app purchase before it will clear it for sale. There is some confusion about what Apple is specifically looking for in this screenshot.

Apple is looking, in the simplest terms, for a screen capture proving that your in-app purchase is working as intended. For unlockable content, this would be a screenshot of the item being used, such as the user playing a purchased level or using a purchased item. However, sometimes your product might not be visible while being used. In cases such as these, Apple has accepted a screenshot of the store showing that the item has been purchased, an example of which is shown in Figure 11-13.

Note

You will not be required to submit a screenshot until you have finished writing and debugging your app and are ready to submit it for review.

Figure 11-13.
figure 13figure 13

An example of an acceptable screenshot for in-app purchase when there isn’t an item to capture

Note

When Apple test In App Purchase during the review process they do so using a Sandbox account. This means that apps shipped to Apple need to support both Sandbox and Production enviroments.

Developer Approval

The last step that you need to take before your in-app purchase is ready to go is developer approval. Return to iTunes Connect in your web browser and navigate to the Manage In-App Purchase section of your app review page. There will be a new green button in the upper-right corner of the screen.

You will be prompted for how to submit your product. The following two options are available.

  • Submit with Binary: This option will turn on in-app purchase with your next binary upload.

  • Submit Now: This will allow you to submit a new product to an existing app.

Note

If you see the option Submit with a New Binary, it could be because the last version of your App was uploaded before in-app purchasing was available in iOS 3.0.

Receipts

When you successfully complete a transaction, you are provided with a receipt as part of the competed transaction object. The following is a sample receipt from one of the purchases in our UFOs demo project.

{

        "signature" = "AnQ+nzB60K5Lc6pI6zh8bptEO+GSGUJ+xT5DSef2p66H8gz/P/D13mBqf96ciJoLesI64fohhZTNb9NrCEkZMVyeqkJed2t38509XpckLWeLJCDYUJqUS1t+fsoy7fwSU0v8TUBzF3Eua1h83GszxlPyylo3mRDssrG+QcgrHwFOAAADVzCCA1MwggI7oAMCAQICCGUUkU3ZWAS1MA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MDYxNTIyMDU1NloXDTE0MDYxNDIyMDU1NlowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrRjF2ct4IrSdiTChaI0g8pwv/cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ==";

        "purchase-info" = "ewoJIml0ZW0taWQiID0gIjQ1ODk4NjQ4NCI7Cgkib3JpZ2luYWwtdHJhbnNhY3Rpb24taWQiID0gIjEwMDAwMDAwMDU4NDM1MzgiOwoJInB1cmNoYXNlLWRhdGUiID0gIjIwMTEtMDgtMjEgMjI6Mzk6NTIgRXRjL0dNVCI7CgkicHJvZHVjdC1pZCIgPSAiY29tLmRyYWdvbmZvcmdlZC51Zm8ubmV3U2hpcDIiOwoJInRyYW5zYWN0aW9uLWlkIiA9ICIxMDAwMDAwMDA1ODQzNTM4IjsKCSJxdWFudGl0eSIgPSAiMSI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZSIgPSAiMjAxMS0wOC0yMSAyMjozOTo1MiBFdGMvR01UIjsKCSJiaWQiID0gImNvbS5kcmFnb25mb3JnZWRzb2Z0d2FyZS5Ucml2aWFsU2NpIjsKCSJidnJzIiA9ICIxLjAiOwp9";

        "environment" = "Sandbox

        "pod" = "100";

        "signing-status" = "0";

}

Apple urges developers to check this receipt for validity. Although this step remains optional, checking adds an extra layer of security and prevents users from activating your in-app purchases without having to pay for the content. Apple provides two servers for you to validate receipts against: one for Sandbox environments, and one for shipping software, as described in Table 11-1.

Table 11-1. Server Addresses for Verifying Receipts with Apple

Joe D’Andrea has posted two methods to help you check for valid receipts on Stack Overflow ( http://stackoverflow.com/questions/1298998 ). His approach is streamlined and efficient, so I have included it here for your convenience.

-(BOOL)verifyReceipt:(SKPaymentTransaction *)transaction

{

    NSString *jsonObjectString = [self encode:(uint8_t *)É

transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];

    NSString *completeString = [NSString stringWithFormat:É

@" http://url-for-your-php?receipt =%@", jsonObjectString];

    NSURL *urlForValidation = [NSURL URLWithString:completeString];

    NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc]É

 initWithURL:urlForValidation];

    [validationRequest setHTTPMethod:@"GET"];

    NSData *responseData = [NSURLConnection sendSynchronousRequest:É

validationRequest returningResponse:nil error:nil];

    [validationRequest release];

    NSString *responseString = [[NSString alloc] initWithData:responseDataÉ

 encoding: NSUTF8StringEncoding];

    NSInteger response = [responseString integerValue];

    [responseString release];

    return (response == 0);

}

- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length

{

    static char table[] =É

 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];

    uint8_t *output = (uint8_t *)data.mutableBytes;

    for (NSInteger i = 0; i < length; i += 3) {

        NSInteger value = 0;

        for (NSInteger j = i; j < (i + 3); j++) {

            value <<= 8;

            if (j < length) {

                value |= (0xFF & input[j]);

            }

        }

        NSInteger index = (i / 3) * 4;

        output[index + 0] =                    table[(value >> 18) & 0x3F];

        output[index + 1] =                    table[(value >> 12) & 0x3F];

        output[index + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';

        output[index + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';

    }

    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]É

 autorelease];

}

After you have added these two methods to your project, you simply need to call verifyReceipt with the transaction that was returned to you, and then perform a Boolean test to see whether it is valid. This concludes all the steps that you need to take to check a receipt for authenticity.

This method of verifying receipts requires you to host an intermediate server; following is a very stripped-down PHP script to validate receipts:

$receipt = json_encode(array("receipt-data" => $_GET["receipt"]));

// NOTE: use "buy" vs "sandbox" in production.

$url = " https://sandbox.itunes.apple.com/verifyReceipt ";

$response_json = call-your-http-post-here($url, $receipt);

$response = json_decode($response_json);

// Save the data here!

print $response->{'status'};

iOS 7 Local Receipt Validation

iOS 7 added the ability to verify In App Purchase receipts on the device without needing to go through a third party server. Server receipt validation is still available and may even by the more appropriate depending on the needs of your app.

Apple recommends validating local receipts within the main function of your app before the NSApplicationMain function is called. In order to verify a receipt it must first be located. NSBundle contains a convenience helper, which will locate the reciept for you. In the event that no reciept is found, verification has failed.

[[NSBundle mainBundle] appStoreReceiptURL];

Once a reciept has been located it needs to be checked to make sure it has been properly been signed by Apple. Apple provides sample code for verifying the signature of receipts as part of the Receipt Validation Programming Guide. That sample code is reprinted here for ease of access. The code will verify the signature of the receipt using OpenSSL.

/* The PKCS #7 container (the receipt) and the output of the verification. */

BIO *b_p7;

PKCS7 *p7;

/* The Apple root certificate, as raw data and in its OpenSSL representation. */

BIO *b_x509;

X509 *Apple;

/* The root certificate for chain-of-trust verification. */

X509_STORE *store = X509_STORE_new();

/* ... Initialize both BIO variables using BIO_new_mem_buf() with a buffer and its size ... */

/* Initialize b_out as an output BIO to hold the receipt payload extracted during

 signature verification. */

BIO *b_out = BIO_new(BIO_s_mem());

/* Capture the content of the receipt file and populate the p7 variable with the

 PKCS #7 container. */

p7 = d2i_PKCS7_bio(b_p7, NULL);

/* ... Load the Apple root certificate into b_X509 ... */

/* Initialize b_x509 as an input BIO with a value of the Apple root certificate and load it into X509 data structure. Then add the Apple root certificate to the

 structure. */

Apple = d2i_X509_bio(b_x509, NULL);

X509_STORE_add_cert(store, Apple);

/* Verify the signature. If the verification is correct, b_out will contain the

 PKCS #7 payload and rc will be 1. */

int rc = PKCS7_verify(p7, NULL, store, NULL, b_out, 0);

/* For additional security, you may verify the fingerprint of the root certificate and verify the OIDs of the intermediate certificate and signing certificate. The OID in the certificate policies extension of the intermediate certificate is (1

 2 840 113635 100 5 6 1), and the marker OID of the signing certificate is (1 2 840 113635 100 6 11 1). */

The next code sample will parse the receipt payload using asn1c.

#include "Payload.h" /* This header file is generated by asn1c. */

/* The receipt payload and its size. */

void *pld = NULL;

size_t pld_sz;

/* Variables used to parse the payload. Both data types are declared in Payload.h. */

Payload_t *payload = NULL;

asn_dec_rval_t rval;

/* ... Load the payload from the receipt file into pld and set pld_sz to the payload size ... */

/* Parse the buffer using the decoder function generated by asn1c.  The payload

 variable will contain the receipt attributes. */

rval = asn_DEF_Payload.ber_decoder(NULL, &asn_DEF_Payload, (void **)&payload, pld,

The following code sample extracts the receipt attributes

/* Variables used to store the receipt attributes. */

OCTET_STRING_t *bundle_id = NULL;

OCTET_STRING_t *bundle_version = NULL;

OCTET_STRING_t *opaque = NULL;

OCTET_STRING_t *hash = NULL;

/* Iterate over the receipt attributes, saving the values needed to compute the

 GUID hash. */

size_t i;

for (i = 0; i < payload->list.count; i++) {

    ReceiptAttribute_t *entry;

    entry = payload->list.array[i];

    switch (entry->type) {

        case 2:

            bundle_id = &entry->value;

            break;

        case 3:

            bundle_version = &entry->value;

            break;

        case 4:

            opaque = &entry->value;

            break;

        case 5:

            hash = &entry->value;

        break; }

}

Next the bundle identifier of the receipts needs to be compared to the CFBundleShortVersionStrong value found in the info.plist of the app. If these two identifiers do not match validation has failed.

Then verify that the version identifier string in the receipt also matches the hard coded value found in the CFBundleShortVersionString of the info.plist. If these values do not match verification has failed.

Finally, check the GUID of the receipt matches the GUID of the device. The GUID for the device can be obtained using the following code. Once again if these two values do not match then validation fails.

[[UIDevice currentDevice] identifierForVendor];

If the receipt is found and signed by Apple, the values for the bundle identifier, version, and GUID all match than the receipt and purchase is valid.

Tying Everything Together in UFOs

Depending on the complexity of your in-app purchase, it could be very easy or very difficult to integrate it into your code. In our UFOs app, we have a very simple product, in which paying a one-time fee unlocks a different colored ship. When the user purchases the product, we store a key in our user defaults to reflect that. To unlock this purchase in code, we simply check for that key, and then perform the required steps. To do this, we need to add some new art assets to the project. These have already been included in the Chapter 11 sample code (available at the Apress web site).

After this is done, we need to modify our viewDidLoad method to change the ship’s image. The following code snippet shows those changes:

-(void)viewDidLoad

{

    purchasedUpgrade = [[NSUserDefaults standardUserDefaults]É

 boolForKey:@"shipPlusAvailable"];

    CGRect playerFrame = CGRectMake(100, 70, 80, 34);

    myPlayerImageView = [[UIImageView alloc] initWithFrame: playerFrame];

    myPlayerImageView.animationDuration = 0.75;

    myPlayerImageView.animationRepeatCount = 99999;

    NSArray *imageArray;

    if (purchasedUpgrade)

   {

        imageArray = [NSArray arrayWithObjects: [UIImage imageNamed:É

 @"Ship1.png"], [UIImage imageNamed: @"Ship2.png"], nil];

    }

   else

   {

        imageArray = [NSArray arrayWithObjects: [UIImage imageNamed:É

 @"Saucer1.png"], [UIImage imageNamed: @"Saucer2.png"], nil];

    }

    myPlayerImageView.animationImages = imageArray;

    [myPlayerImageView startAnimating];

    [self.view addSubview: myPlayerImageView];

}

Summary

In this chapter, we covered StoreKit and in-app purchases. By leveraging StoreKit, you gain a number of new ways to monetize your app, from expandable content to special upgrades for your users. You should now feel confident adding a variety of products to your own in-app store. Although StoreKit isn’t directly a part of Game Center or Game Kit, you will undoubtedly find an in-app store an invaluable addition to your iOS software.

We spent some time talking about ngmoco:) and its experiments and successes with the freemium model. You should now feel confident with iTunes Connect and all the actions that are required to fully set up an in-app purchase product, as well as the required code to get that purchase to display.

We looked at how to handle failures with purchasing and also the path of a success. We also explored some of the advanced topics, such as validating receipts and multiple purchases at once. Lastly, we saw how we integrated the entire experience into our UFOs demo app.