IAP 计费被拒绝解决方案

来源:互联网 发布:拼接屏调试软件 编辑:程序博客网 时间:2024/04/30 22:35
In-App purchasing in iOS 6 2013年5月之后,由于苹果需求一次性道具要求添加按钮问题解决方案:


Reject message:

We found that while your app offers In-App Purchase(s) that can be restored, it does not include the required "Restore" feature to allow users to restore the previously purchased In-App Purchase(s), as specified in Restoring Transactions section of the In-App Purchase Programming Guide:

"...if your application supports product types that must be restorable, you must include an interface that allows users to restore these purchases. This interface allows a user to add the product to other devices or, if the original device was wiped, to restore the transaction on the original device."

To restore previously purchased In-App Purchase products, it would be appropriate to provide a "Restore" button and initiate the restore process when the "Restore" button is tapped by the user.

For more information about restoring transactions and verifying store receipt, please refer to the In-App Purchase Programming Guide.


解决方案:

In-app purchases are a must in today’s app economy.  This provides a great opportunity for you to get your app into people’s hands (iPhones, iPods or iPads) for free, and then try to capture some revenue from their enjoyment of playing or using your app.  This is known as the Freemium model.  Some ways to use in-app purchasing for value add revenue streams include:

  1. Ad removal
  2. Tokens (e.g. coins, badges, donuts, dollars, pesos, you name it)
  3. Virtual goods (e.g. weaponry, power-ups, health, etc.)
  4. Featured or additional content
  5. Extended functionality
  6. Extended game play or time
  7. Removal of restriction (e.g. number of connections or CCUs)
  8. Support

Let’s dive into the detail of creating an in-app purchase (IAP) for removing ads, as I think this is probably one of the most common uses for an IAP.

Creating IAP product(s)

Manage In-App PurchasesThe first step is to create your products in the iTunes Connect web site and:

  1. Click “Manager Your Applications”
  2. Click on the application you wish to add the ad removal IAP product to
  3. Click on “Manage In-App Purchases” (as shown the screen shot)
  4. Click “Create New”
  5. Choose Select under a “Non-Consumable” product.  Since the ad removal will be paid for just once, and can be applied to the app running on any of the user’s devices, it is considered a non-consumable product.  A virtual good or token would be an example of a consumable product.
  6. Provide all of the details for the product:
    1. Reference name: “remove_ads
    2. Product ID: com.yourdomain.yourapp.remove_ads
    3. Pricing: you choose
    4. Add the languages.  I added one for English with the display name “Ad-Free Upgrade“, and the description: “Tired of the ads?  You can remove all ads in the app for only $0.99!
    5. Provide any notes for the Apple reviewer
    6. Upload a screen shot.  I just took a screen shot of the “Remove Ads” button in the right of the top navigation bar of my app.

Add StoreKit framework

The first step is to add the StoreKit framework to your project.

  1. Open up Xcode and select the project at the top of your file view
  2. Then choose your target
  3. Choose the Build Phases tab, and then open the “Link Binary With Libraries” settings area
  4. Click on the plus ( + ) sign to add a new library
  5. Add the StoreKit.framework library as shown below in the screen shot.

Add StoreKit framwork

Create InAppPurchaseHelper

We are going to create a static InAppPurchaseHelper singleton class that is going to be used to manage our in-app purchasing products that are available for purchasing, to initiate the product purchase, to receive the callback when the purchase is completed, and to restore an products that were previously purchased by the user (we need to do this to be in accordance with Apple’s rules).

Please note that I based my implementation of my InAppPurchaseHelper off of a wonderful tutorial by Ray Wenderlich.

Setting It All Up

The first thing we need to do is request the product list from the App Store in order to be able to purchase or restore the product purchase.  Before we can do that, however, we need to get everything set up and ready to make our request.  To get started, we are going to use the product identifier that we created above to ask the App Store for our product information.  I store an NSSet of product identifiers for my app in my AppDelegate. I also store each product identifier string in individual methods. There is probably a better approach to this (maybe a plist or xml file?), and please feel free to comment if you have a suggestion. In this case, I have a removeAdsProductIndentifier: method that returns the NSString pointer.

1
2
3
4
5
6
@interfaceAppDelegate : UIResponder
 
@property(strong,nonatomic)NSSet*inAppProductIdentifiers;
-(NSString*)removeAdsProductIdentifier;
 
@end

I then override the accessor method for the property to return the set with my product identifiers:

1
2
3
4
5
6
7
- (NSSet*)inAppProductIdentifiers {
    return[NSSetsetWithObjects:[selfremoveAdsProductIdentifier],nil];
}
 
- (NSString*)removeAdsProductIdentifier {
    return@"com.acuity_apps.word_learner_owl.remove_ads";
}

I can then use this set of product identifiers to initialize my shared instance using a class method in the InAppPurchaseHelper.m file. Let’s first look at my InAppPurchaseHelper.h header file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
 
UIKIT_EXTERNNSString*constInAppPurchaseHelperProductPurchasedNotification;
typedefvoid(^RequestProductsOnCompleteBlock)(BOOLsuccess,NSArray*products);
 
@interfaceInAppPurchaseHelper : NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>
 
@property(strong,nonatomic)NSString*removeAdsProductIdentifier;
 
+ (InAppPurchaseHelper *)sharedInstance;
- (id)initWithProductIdentifiers:(NSSet*)productIdentifiers;
- (void)requestProductsWithOnCompleteBock:(RequestProductsOnCompleteBlock)onCompleteBlock;
 
@end

First, I am importing the StoreKit framework on the second line. I am then declaring a constant NSStringnamed InAppPurchaseHelperProductPurchaseNotification to contain the notification message on line 4.

On the next line (line 5) I am declaring a new block type RequestProductsOnCompleteBlock. We will create a block that meets this new block type specification as our callback that is executed after the Store responds with the product information from the App Store. You may want to read up on how to use blocks in Objective-C if you are unfamiliar with them. Blocks provide the functionality of closures, or anonymous or inline function, that you might have used or learned about in other languages, such as JavaScript and Actionscript.

Next, I declare that the InAppPurchaseHelper interface will implement the SKProductsRequestDelegateprotocol as well as the SKPaymentTransactionObserver protocol. These are necessary for using StoreKit’s API.  We will look at these in more detail later.

Lastly, I am going to declare the public methods that our class will implement.  I am not showing them all here yet, just the first couple of methods that we will be creating.  The first is the sharedInstance: class method (as denoted by the plus sign).  This will be used to get the single (or shared) instance of our class.  This ensures that our class is used as a singleton object.  Let’s look at the details of thesharedInstance: class method now.

1
2
3
4
5
6
7
8
9
+ (InAppPurchaseHelper *)sharedInstance {
    staticInAppPurchaseHelper *sharedInstance;
    staticdispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
        sharedInstance = [[selfalloc] initWithProductIdentifiers:delegate.inAppProductIdentifiers];
    });
    returnsharedInstance;
}

In the code above we are allocating a new instance of this class (InAppPurchaseHelper) and calling our custom initWithProductIdentifiers: method using the product identifiers NSSet that we are storing in our AppDelegate. Let’s look at the initWithProductIdentifiers: method next.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (id)initWithProductIdentifiers:(NSSet*)productIdentifiers {
    self= [superinit];
    if(self!=nil){
        //save the product identifiers
        _productIdentifiers = productIdentifiers;
 
        //check for previously purchased products
        _purchasedProductIdentifiers = [NSMutableSetset];
        for(NSString*productIdentifier in _productIdentifiers){
            BOOLpurchased = [[NSUserDefaultsstandardUserDefaults] boolForKey:productIdentifier];
            if(purchased){
                [_purchasedProductIdentifiers addObject:productIdentifier];
            }
        }
 
        //set this object as the transaction observer
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    returnself;
}

In the initWithProductIdentifiers: method we are accepting the NSSet of product identifiers that we are using for this application.  These product identifiers are now stored within the InAppPurchaseHelpersingleton class so we can use them as necessary within the app.  We store these into an instance variable we declared previously in our class implementation declaration:

1
2
3
4
@implementationInAppPurchaseHelper {
    NSSet*_productIdentifiers;
    NSMutableSet*_purchasedProductIdentifiers;
}

We also declared a mutable set for the purchased product identifiers. This will be based off of what we know in the application’s NSUserDefaults, which are stored locally in the app. I have defaulted the value for each product identifier in the NSUserDefaults to be false, or NO, when I initialize my application (in my AppDelegate):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(void)setDefaultUserPreferences {
    NSUserDefaults*defaults = [NSUserDefaultsstandardUserDefaults];
 
    //create diction to hold defaults
    NSMutableDictionary*appDefaults = [NSMutableDictionarydictionary];
 
    //add in-app product identifier defaults with a default of NO
    for(NSString*productIdentifier in _inAppProductIdentifiers){
        [appDefaults setObject:@NOforKey:productIdentifier];
    }
 
    //register the app defaults
    [defaults registerDefaults:appDefaults];
}

The last thing we did in the initWithProductIdentifiers: method is declare the helper class as the transaction observer, so that is will receive notifications from the App Store when a transaction has been processed or updated.

Request Products from App Store

Now that we have everything set up, we can start talking to the App Store.  To do so, we are going to implement the requestProductsWithOnCompleteBlock: method in our InAppPurchaseHelper.m file.  Note, that we declared the block of type RequestProductsOnCompleteBlock in our header file.  Let’s look at this is more detail.

1
typedefvoid(^RequestProductsOnCompleteBlock)(BOOLsuccess,NSArray*products);

When we declare this new type, we are saying that the type is called “RequestProductsOnCompleteBlock”, and that it does not return anything (void).  The return type is declared first, then within parenthesis and following the carrot symbol, we declare the new type.  Within the second set of parenthesis we are declaring the signature of the block.  The block will receive two arguments, first a success BOOL, and second, a NSArray of products (which are objects of typeSKProduct).

Now that we have our block type declared, we can implement the requestProductsWithOnCompleteBlock: method in our InAppPurchaseHelper.m file:

1
2
3
4
5
6
7
8
9
- (void)requestProductsWithOnCompleteBock:(RequestProductsOnCompleteBlock)onCompleteBlock {
    //keep a copy of the onComplete block to call later
    _onCompleteBlock = [onCompleteBlock copy];
 
    //request information about in-app purchases using product identifiers
    _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
    _productsRequest.delegate = self;
    [_productsRequest start];
}

This method will be called when we are ready to retrieve the products from the App Store. I chose to not do this during the application initialization. Rather, I am going to “lazy load” this after the first game list screen is shown to my users. This is to minimize any network bandwidth, necessary CPU cycles, and potential delay in initializing my app and showing the first screen to the user.

In the requestProductsWithOnCompleteBock: method shown above I am accepting the callback block that will be executed when the store returns with the information. This is done in this fashion so that the request is performed asynchronously, and no application locking occurs. After storing reference to a copy of the block in the _onCompleteBlock instance variable, I am going to request the in-app purchase product information from the App Store.

Phew — congratulations if you made it this far in this tutorial! We perform the request by using the StoreKit API’s initWithProductIdentifiers: method, providing it with our NSSet of product identifiers. We then tell StoreKit that this class is the delegate method for the request. This is why we implement theSKProductsRequestDelegate protocol. Lastly, we fire off the request to the App Store.

Before we move on, let’s also look at what the calling code will look like for retrieving the product information from the App Store.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad
{
    [superviewDidLoad];
 
    //Add the "Remove Ads" button if necessary
    InAppPurchaseHelper *inAppPurchaseHelper = [InAppPurchaseHelper sharedInstance];
    AppDelegate *delegate = [UIApplication sharedApplication].delegate;
    if(![inAppPurchaseHelper productPurchasedWithProductIdentifier:[delegate removeAdsProductIdentifier]]){
        [selfaddRemoveAdsButtonToNavigationBar];
    }
 
    //obtain the list of in-app products
    [inAppPurchaseHelper requestProductsWithOnCompleteBock:^(BOOLsuccess,NSArray*products) {
        if(success){
            _products = products;
        }
    }];
}

This is the viewDidLoad: method in my ViewController that is first shown the user. This is my home page view controller, if you will. In my case, it is my GameChooserViewController, but this is certainly going to be different for you.

First, I am going to check my local NSUserDefaults to determine if I have already purchased the removal of ads. If not, I ama going to add a button to the right-side of my navigation bar. The code for this is quite simple:

1
2
3
4
- (void)addRemoveAdsButtonToNavigationBar {
    _removeAdsButton = [[UIBarButtonItem alloc] initWithTitle:@"Remove Ads" style:UIBarButtonItemStyleBordered target:selfaction:@selector(removeAds:)];
    self.navigationItem.rightBarButtonItem = _removeAdsButton;
}

Back to our viewDidLoad:, after adding the button to the navigation bar if necessary, we are going to obtain the list of products from the App Store, and store the products in the _products NSArray instance variable. I will use this later to initiate the purchasing of the ad removal IAP product when the user chooses to remove the ads.

Purchasing IAP Products

This is what we’re all about. After all, we have to pay the bills.  To handle product purchasing, we are going to create a new method in our InAppPurchaseHelper that is going to handle the details for us.  We’ll call this new method buyProduct:

1
2
3
4
5
6
7
- (void)buyProduct:(SKProduct *)product {
    NSLog(@"Performing in-app purchase: %@", product.productIdentifier);
 
    //add to payment queue
    SKPayment *payment = [SKPayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

The buyProduct: method is very straight-forward. We just add the product to the payment queue, and wait for the user to do the rest. This will cause a prompt on the device asking the user if they wish to “Buy” or “Cancel” the in-app purchase request. Of course, we reallllly hope they click “Buy”. If not, the prompt disappears.

Remember that we declared previously that our InAppPurchaseHelper would be the transaction observer for the SKPaymentQueue? If not, that’s OK, just refer back to the code for our initWithProductIdentifiers: method.  We set the InAppPurchaseHelper (self) as a transaction observer for the default payment queue.  Here is the single line of code that I am referring to:

1
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

This means that we are going to receive the event notifications when a transaction in the queue is processed.  The transaction queue handling is performed by the OS and App Store.  Understanding the inner workings of this are not necessary for us.  All we need to know is that after the transaction is completed, all of the transaction observers are notified.  In our case, we use a single transaction observer — our helper class.  In order for you to add an object as a transaction observer, that object must implement the SKPaymentTransactionObserver protocol.  There are several optional methods that you can implement for this protocol if you wish.  We are going to implement the paymentQueue:updatedTransactions: method that is required (it is the only method that is required for this protocol).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# pragma mark - Implement SKPaymentTransactionObserver protocol
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions {
    for(SKPaymentTransaction *transaction in transactions){
        switch(transaction.transactionState) {
            caseSKPaymentTransactionStatePurchased:
                [selfcompleteTransaction:transaction];
                break;
            caseSKPaymentTransactionStateFailed:
                [selffailedTransaction:transaction];
                break;
            caseSKPaymentTransactionStateRestored:
                [selfrestoreTransaction:transaction];
                break;
            default:
                break;
        }
    }
}

This method is our callback from the App Store transaction queue. When this method is executed we are provided with an array of transactions that have been completed. We loop over all of the transactions, and based on the state of the transaction, we take the necessary steps. The transaction states are declared as an enum in the SKPaymentTransaction class.

1
2
3
4
5
6
7
enum{
SKPaymentTransactionStatePurchasing,
SKPaymentTransactionStatePurchased,
SKPaymentTransactionStateFailed,
SKPaymentTransactionStateRestored
};
typedefNSIntegerSKPaymentTransactionState;

As you can see, the transaction can be in a state of:

  • Purchasing
  • Purchased
  • Failed
  • Restored

I don’t care about the “Purchasing” state, so I just ignore it.  We do care about the other three transaction states.  As such, I have created three new private methods that will handle these states:

  • completeTransaction:
  • failedTransaction:
  • restoreTransaction:

Here is the code for each of these methods in my InAppPurchaseHelper class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"Completing transaction.");
    [selfprovideContentForProductIdentifier:transaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
 
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"Failed transaction.");
    if(transaction.error.code != SKErrorPaymentCancelled){
        NSLog(@"Transaction error: %@", transaction.error.localizedDescription);
    }
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
 
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"Restoring transaction.");
    [selfprovideContentForProductIdentifier:transaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

In the code above, you can see that I simply doing some logging in each method.  For completed (successful) and restored transactions, I am going to provide the user with the content that they purchased.  In this case, that is the removal of showing ads in the app.  If the transaction failed, I don’t do provide the content, but just log out that the transaction failed.

An important thing to note is that after being notified that the transaction was completed (or failed, or restored), we need to let the App Store know that we have received the notification by calling thefinishTransaction: method. Otherwise, we will keep getting notifications about the transaction completion. In the event that the transaction is completed, we want to perform the necessary business logic to give the user what they paid for. I do this in the provideContentForProductIdentifier: method.

1
2
3
4
5
6
7
8
9
10
11
- (void)provideContentForProductIdentifier:(NSString*)productIdentifier {
    //add the product identifier to the set of purchased identifiers
    [_purchasedProductIdentifiers addObject:productIdentifier];
 
    //set the NSUserDefault value to YES
    [[NSUserDefaultsstandardUserDefaults] setBool:YESforKey:productIdentifier];
    [[NSUserDefaultsstandardUserDefaults] synchronize];
 
    //send out notification that the purchase went through for the in-app product
    [[NSNotificationCenterdefaultCenter] postNotificationName:InAppPurchaseHelperProductPurchasedNotification object:productIdentifier];
}

First, I am adding the product identifiers to my local NSSet of product identifiers that have been purchased by the user, so that if the application checks if a product is purchased, I can quickly inspect this set.

Second, I am storing this in my NSUserDefaults cache so that if the application is restarted, it will still know that the product was purchased. If they uninstall and re-install the app, the user can click “Remove Ads” in the application, and their product purchase will be restored (and this method called again).  Note, that in this case, they are not charged again for the non-consumable IAP product.  Apple knows that they already purchased the product.

Third, I am sending out an event notification that I can listen for in my application code. In my GameChooserViewController I am listening for this notification.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void) viewWillAppear:(BOOL)animated {
    [superviewWillAppear:animated];
 
    //register in-app purchase notifications
    [[NSNotificationCenterdefaultCenter] addObserver:selfselector:@selector(productPurchased:) name:InAppPurchaseHelperProductPurchasedNotification object:nil];
}
 
- (void)productPurchased:(NSNotification*)notification {
    NSString*productIdentifier = notification.object;
    AppDelegate *delegate = [UIApplication sharedApplication].delegate;
    if([productIdentifier isEqualToString:[delegate removeAdsProductIdentifier]]){
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success"message:@"Thank you for purchasing Word Learner!" delegate:selfcancelButtonTitle:@"OK"otherButtonTitles:nil];
        [alert show];
        self.navigationItem.rightBarButtonItem = nil;
    }
}

When the remove ads IAP product is purchased successfully, we alert the user thanking them for their purchase along with removing the “Remove Ads” button in the navigation bar.

Woo hoo! We have now successfully completed a transaction for an in-app purchase. Our app is now ready to start raking in the dough. :)

When to Show Ads?

We expose another public method in our InAppPurchaseHelper class so that our application logic can check if a product has been purchased or not.  This way I can determine if we should show an ad to the user, or not (if they purchased our remove_ads product).  This method signature is pretty straight forward.

1
- (BOOL)productPurchasedWithProductIdentifier:(NSString*)productIdentifier;

This returns a BOOL value that we can then use in an if-statement or logical expression to determine whether or not to show the ads. Here is an example of using this method as such.

1
2
3
4
5
6
7
8
9
10
11
//show/hide banner ad and possibly load up the interstitial ad
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
if(![[InAppPurchaseHelper sharedInstance] productPurchasedWithProductIdentifier:[delegate removeAdsProductIdentifier]]){
    if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
        interstitial = [[ADInterstitialAd alloc] init];
        interstitial.delegate = self;
    }
}else{
    [_bannerView removeFromSuperview];
    _bannerView = nil;
}

The code above is in one of my view controllers, in the viewDidLoad: method. By default I have a banner view on my storyboard that bound to the ADBannerView IBOutlet, which is a property and is synthesized as _bannerView. If the user has purchased the removal of ads product, then I remove theADBannerView. If they have not purchased the removal of ads, I leave the ad banner and let it be shown to the user. Further, if the user using an iPad (and has not purchased the removal of ads), then I will start loading in the interstitial ad to be displayed after their game turn has ended.

Restoring Purchases

2013-02-11_12-14-23This is not only a convenience to your users, but this is also required by the App Store requirements.  If you fail to implement the ability to restore an IAP, your app will be rejected by Apple.  They will send you something like:

We found that while your app offers In-App Purchase(s) that can be restored, it does not include the required “Restore” feature to allow users to restore the previously purchased In-App Purchase(s). ‘…if your application supports product types that must be restorable, you must include an interface that allows users to restore these purchases. This interface allows a user to add the product to other devices or, if the original device was wiped, to restore the transaction on the original device.

In my app, I am going to give the user the option to purchase the removal of ads for $0.99 when they click the “Remove Ads” button.  In this same fashion, I am also going to give them the option to restore their previous purchase.

I am using a UIActionSheet to allow the user to choose which action they want to take.  I implement the UIActionSheetDelegate protocol so that I can respond to the event of the user choose an option.  Here is my actionSheet:clickButtonAtIndex: implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma mark - Implement UIActionSheetDelegate protocol
 
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex    {
    AppDelegate *delegate = [UIApplication sharedApplication].delegate;
    NSString*removeAdsProductIdentifer = [delegate removeAdsProductIdentifier];
    if(buttonIndex == 0){
        NSLog(@"Performing in-app purchase to remove ads.");
        for(SKProduct *product in _products){
            if([product.productIdentifier isEqualToString:removeAdsProductIdentifer]){
                [[InAppPurchaseHelper sharedInstance] buyProduct:product];
                break;
            }
        }
    }elseif(buttonIndex == 1){
        [[InAppPurchaseHelper sharedInstance] restoreCompletedTransactions];
    }
}

Basically, I am either calling the buyProduct: method or the restoreCompletedTransaction: method in my InAppPurchaseHelper class. We have already looked at the buyProduct: implementation, so let’s look at the restoreCompletedTransaction: implementation now. Here is what the method signature looks like in the header file:

1
- (void)restoreCompletedTransactions;

When we are restoring a transaction, we leave all the heavy lifting up to the SKPaymentQueue and App Store. We just simply call the restoreCompletedTransactions: method, which will then process the request.

1
2
3
- (void)restoreCompletedTransactions {
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

If the IAP is restored successfully, then our paymentQueue:updatedTransactions: method is called with the transaction state SKPaymentTransactionStateRestored. We then call ourprovideContentForProductIdentifier: method, which we covered in detail previously. In the end, adding the ability to restore an IAP transaction is quite easy (since we did all of the work already).

Download the code

You want it?  You got it!  Download the complete InAppPurchaseHelper code below.  If you are using this code, I ask that you please link to this article indicating that it helped you.

  • InAppPurchaseHelper [ZIP]

Share the love!  Give me a link back, or go ahead and ask me a question or comment on how valuable this tutorial has been for you.