Sunday, April 8, 2012

How To: Implement In App Purchase The Easy Way, Ever.

There are plenty of IAP (In App Purchase) tutorials in the internet. BUT, all of them are damn complicated. None of the tutorials (that I found anyway) that shows the easiest way to implement IAP. If that is how you feel, read on....

For example, over at Ray Wenderlich (this tutorial), you can try read it, unless you are very familiar with classes and what nots, you'd end up like me - giving up to make IAP in your app. Don't get me wrong, Ray Wenderlich provides one of the TOP NOTCH tuts, but sometimes they just speak a different language than us, noobies and amateur coders.

So I thought, since it is my first time to implement IAP in one of my apps, (I never had an IAP app before), might as well I try find my own way of putting an IAP which is EASY, STRAIGHTFORWARD, and FAST TO IMPLEMENT.



Lets get started.

Firstly, for this tutorial, I am going to assume a few things:

1. that you want to implement IAP by way of "feature" buttons.
2. that you do not have too many items to be purchased (if you do have many IAP items,
then it is probably best to use UITableView as in Ray Wenderlich's sample)
3. that all IAP items/features are built in into your app bundle (ie, you do not have a downloadable IAP items, etc)

If these assumptions are what you are doing, then read on! For this tutorial example project, I'll just put 2 buttons on a single view app. One button is an example of a readily available feature and another button is another feature that requires an IAP to use.

So, as normal, go to your XCode, create a project and goto the XIB file and put 2 buttons on it. For the 2nd button, you need to put an "indicator" that the feature is kinda locked. Here, I use a padlock icon as the background image to illustrate this. If you use button of the type Custom, then you could set the button's Image property as the padlock icon and the background property as your button appearance. When the app is ran, here's what it looks like:

I also added a UILabel at the bottom just to do something when you press the feature button. For this example, I am going to make the label say "Feature 1" when you press button 1 and say "Feature 2" when you press button 2.

But since Feature 2 is a IAP item, we need to check if user has purchased the feature before showing "Feature 2". Anyway, lets make the basic functions first.

Declare 2 IBActions for the 2 buttons. And also declare IBOutlets for the IAP Feature button and for the UILabel in .h. Also declare a UIAlertView so that we can check for the user response in this alert later. Remember to synthesize both objects in your .m file.

Your .h should look something like this:


#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>

@interface ViewController : UIViewController {
    IBOutlet UIButton *feature2Btn;
    IBOutlet UILabel *featureLabel;
    UIAlertView *askToPurchase;
}

@property (nonatomic, retain)  UIButton *feature2Btn;
@property (nonatomic, retain)  UILabel *featureLabel;

-(IBAction)doFeature1:(id)sender;
-(IBAction)doFeature2:(id)sender;


@end

 Now open XIB file and connect all the necessary links. Connect IBActions to Touch Up Inside of File Owner, and both IBOutlets. Note that we need IBOutlet of Feature 2 button because we are going to change the Lock icon condition later on. Having the button declared will make things easier later.

Next, open .m file and write the methods for the button taps.


 
-(IBAction)doFeature1:(id)sender {

    featureLabel.text = @"Feature 1";

}

-(IBAction)doFeature2:(id)sender {

    featureLabel.text = @"Feature 2";

}

Now run the app, both buttons should be working now when you tap it, the UILabel below will change according to which button you tap! Congratulations. No, you are not done. We want to lock Feature 2 now, so tapping button "2" should check for IAP first before allowing user to use it.

For this purpose, we will create another method to check for IAP item somewhere.... Earlier, when I was learning how to implement IAP, I thought of using NSUserDefaultsto store some values (like a BOOL) so that we can set that flag if user completed an IAP. However, there is a problem with this, while it is simple, if user deleted the app, and redownload it later, the IAP flag will be gone forever - NSUserDefaults is stored as .plist file in the APP BUNDLE folder.... means user who already bought the IAP, needs to purchase the IAP again = angry users. Grr..

A better way to handle this is to make use of the KEYCHAIN. Now, we can learn to implement Keychain, but why reinvent the wheel? Head over HERE to download the KeyChain wrapper. After you downloaded it copy and include both SFHFKeychainUtils.h and .m into your project. (I've already included the files in the Sample project of this tutorial, but do download the latest version for your app).

What is Keychain?Keychain is something like Registry in Windows. It is a more secure place to store sensitive information in the iDevice and it is not tied to the app bundle. 

In your .m file, remember to #import  "SFHFKeychainUtils.h". We also need to add the "Security.framework" to our project. After that, lets create the method to check the IAP item in the keychain.



-(BOOL)IAPItemPurchased {

      

    NSError *error = nil;

    NSString *password = [SFHFKeychainUtils getPasswordForUsername:@"IAPNoob01" andServiceName:kStoredData error:&error];

   

    if ([password isEqualToString:@"whatever"]) return YES; else return NO;

   

}
We are just simply using the username/password saving feature of KeyChain utility to save our IAP item data. Nothing fancy. Also notice we are declaring this function as a BOOL, so it returns a YES or NO immediately when we call for it. Note that all 3 data here: username, password and ServiceName are developer defined. You choose what you want for those keys (they are all NSStrings). It does not matter what they are, as long as we can check for the "password".

Now, lets implement the lock feature of Feature 2. Modify the doFeature2 method as below:

-(IBAction)doFeature2:(id)sender {

   

    if ([self IAPItemPurchased]) {

   

        featureLabel.text = @"Feature 2";

    } else {

        // not purchased so show a view to prompt for purchase

        askToPurchase = [[UIAlertView alloc]

                            initWithTitle:@"Feature 2 Locked"

                            message:@"Purchase Feature 2?"

                            delegate:self

                            cancelButtonTitle:nil

                            otherButtonTitles:@"Yes", @"No", nil];

        askToPurchase.delegate = self;

        [askToPurchase show];

        [askToPurchase release];

    }

}

What we do here is check if the IAP item flag is available in the keychain, if it exist then user has purchased Feature 2 before, so we proceed with it's function. If not, prompt user to buy Feature 2. In this example, I just use a simple AlertView. You can code a pretty UIView with colorful images and so on - entirely up to you.

Now lets handle the alertview feedbacks. If user chose to purchase, then we start the IAP request.
Before we go on to that, lets create an IAP item in iTunesConnect first.

Creating/Registering IAP Items in iTunesConnect.
 Logon to your iTC account and click Manage Applications. Then click your app icon, and at the top right hand corner, click "Manage In-App Purchases". On the top left side, there is a Create New button. Click it. Click on Non Consumable. (Non consumable is a purchase type where user only needs to pay for a feature just one time.) Key In Ref Names and other details as shown in the pic below:



As for the screenshot, just upload a Dummy image of size 960x640 and you'll be able to save the IAP item. But you have to remember to update this screenshot prior to submission. The key info in this form is the Product ID. In this case com.emirbytes.IAPNoob.01 , this is what we will be calling from our app later. Once we are done we can then create Test Accounts to test the IAP later. In the main page of iTunesConnect, go to Manage Users, and click on Test User and add a test user. Once you are done, you can logoff and return to coding.

Implementing StoreKit
The API that will help us implement the IAP is called StoreKit. So obviously we need to add the StoreKit.framework to our project. Next we need to import StoreKit.h and implement the related delegates, so modify the .h file as below:



#import <UIKit/UIKit.h>

#import <StoreKit/StoreKit.h>

@interface ViewController : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver> {

    IBOutlet UIButton *feature2Btn;

    IBOutlet UILabel *featureLabel;

    UIAlertView *askToPurchase;
}

@property (nonatomic, retain)  UIButton *feature2Btn;

@property (nonatomic, retain)  UILabel *featureLabel;

-(IBAction)doFeature1:(id)sender;

-(IBAction)doFeature2:(id)sender;

@end

Since we use AlertView to prompt the user, we need to input the AlertView delegate as well, so add the UIAlertViewDelegate in .h file and implement the delegate function as below:



-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {



    if (alertView==askToPurchase) {

        if (buttonIndex==0) {

            // user tapped YES, but we need to check if IAP is enabled or not.

            if ([SKPaymentQueue canMakePayments]) {

               

                SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:@"com.emirbytes.IAPNoob.01"]]; 

               

                request.delegate = self; 

                [request start]; 

               

               

            } else {

                UIAlertView *tmp = [[UIAlertView alloc]

                                    initWithTitle:@"Prohibited"

                                    message:@"Parental Control is enabled, cannot make a purchase!"

                                    delegate:self

                                    cancelButtonTitle:nil

                                    otherButtonTitles:@"Ok", nil];

                [tmp show];

                [tmp release];

            }

        }

    }

   

}

 First we check if the alertview is the IAP prompt alertview, if it is then we check if Yes button was pressed, and if it is, then we check "canMakePayments". This function will return YES or NO depending on whether the In-App Purchases Settings in the device is set to On or Off (Settings App -> General -> Restrictions). This way, the payment is allowable only if this restriction is not set (to prevent childrens from simply buying your IAPs unintentionally). And, if we can make IAP request, initiate the request with the Product ID that we specified earlier in iTC.

Since IAP requires internet connection and takes some time (a few seconds) to be processed, at this point it is a good idea to check for internet connection availability and to display a Wait View (I do not include it for the sake of simplicity). For easier demo, I put another UILabel to show the status of purchase.

Once we request this IAP to the IAP server, the app will then wait for a reply from the IAP server and fires off the StoreKit delegates:


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

-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response  

-(void)requestDidFinish:(SKRequest *)request

-(void)request:(SKRequest *)request didFailWithError:(NSError *)error  

The important delegates are the first two that are in bold.  Right after we request a product via SKProductRequest in alertView delegate above, the first delegate that is going to respond is the didReceiveResponse delegate. Here, it will check whether the Product ID we requested is available or not, and if available, do the payment (Ka Ching!)...


 -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response 

{ 

   

    // remove wait view here

   

    SKProduct *validProduct = nil;

    int count = [response.products count];

   

    if (count>0) {

        validProduct = [response.products objectAtIndex:0];

       

        SKPayment *payment = [SKPayment paymentWithProductIdentifier:@"com.emirbytes.IAPNoob.01"];

        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

       
[[SKPaymentQueue defaultQueue] addPayment:payment]; // <-- KA CHING!

       

       

    } else {

        UIAlertView *tmp = [[UIAlertView alloc]

                            initWithTitle:@"Not Available"

                            message:@"No products to purchase"

                            delegate:self

                            cancelButtonTitle:nil

                            otherButtonTitles:@"Ok", nil];

        [tmp show];

        [tmp release];

    }

   

   

}  

After we request a payment, the next delegate that is going to respond is the updatedTransactions delegate, where we check for transactions type: there are 4 types that must be handled properly.


SKPaymentTransactionStatePurchasing - indicates still processing the purchasing - display a wait view.

SKPaymentTransactionStatePurchased - indicates purchase completed - unlock features by code

SKPaymentTransactionStateRestored - purchase restored from an interrupt (phone call etc)

SKPaymentTransactionStateFailed - purchase failed - show alert

I will explain the code in StatePurchased condition:

 
case SKPaymentTransactionStatePurchased:

           

                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

                // remove wait view and unlock feature 2

               

                UIAlertView *tmp = [[UIAlertView alloc]

                                    initWithTitle:@"Complete"

                                    message:@"You have unlocked Feature 2!"

                                    delegate:self

                                    cancelButtonTitle:nil

                                    otherButtonTitles:@"Ok", nil];

                [tmp show];

                [tmp release];

               

               

                NSError *error = nil;

                [SFHFKeychainUtils storeUsername:@"IAPNoob01" andPassword:@"whatever" forServiceName:kStoredData updateExisting:YES error:&error];

               

                // apply purchase action  - hide lock overlay and

                [feature2Btn setBackgroundImage:nil forState:UIControlStateNormal];

                // do other thing to enable the features

               

                break;


First, in above case, we tell the server to finish the transaction, then we show to user that the Feature has been unlocked. Then, we register a flag in Keychain with the specific username, password and servicename (remember they are case sensitive). Finally we remove any Padlock icon and other visual settings.

IMPORTANT: YOU CAN ONLY TEST IN-APP PURCHASE IN YOUR DEVICE, AND NOT IN SIMULATOR. USE THE DEVELOPMENT PROVISION TO TEST IT. AND MAKE SURE YOU LOG OFF YOUR OWN APPLE ID and USE THE TEST USER ID TO DO TEST THE IAP IN SANDBOX ENVIRONMENT! see additional notes.
We are almost done. Finally we need to re-check the purchase in the Keychain everytime the user activate the app. In this case I put it in viewDidLoad - here we call the checking function we created earlier - see how it is useful to have?:


 
 if ([self IAPItemPurchased]) {

        [feature2Btn setBackgroundImage:nil forState:UIControlStateNormal];

    } else {

        [feature2Btn setBackgroundImage:[UIImage imageNamed:@"Locked.png"] forState:UIControlStateNormal];

    }

Here are some screenshots sequence of the IAP in action:







____________________________________

Oops! I forgot one more thing!

Since you will be testing this in your device, you might want to test the IAP a few times. So we need to add another button (which will be hidden or just deleted when you submit the app) to reset the Keychain value.

Just add a button... and implement IBAction for it as below:



-(IBAction)deleteKeyChain:(id)sender {

    NSError *error = nil;

    [SFHFKeychainUtils deleteItemForUsername:@"IAPNoob01" andServiceName:kStoredData error:&error];

}


To test the IAP process again, just tap the button and delete the app in your device, and then rerun it. The app's Feature 2 should be Locked again.

Ok, now we're Done! Yay!


ADDITIONAL NOTES:

Some readers have given me some feedback about how they get a "No Products" alert when running it on the device. Please check that you have assigned Code Signing to the certificates correctly (both development and Release provision profiles). Things you can try:

Reset your app - delete the app in sim, do Clean All in XCode, double check your provisioning profiles (reinstall them if necessary). Remember that bundle ID are case sensitive. Ensure to logout your iTunes Store account in Sim/Device and make sure you login with a test account to test the IAP. Check everything in detail.

Also, I have verified that you can test the IAP in Simulator as well (I tested it using XCode 4.3.1 and Simulator 5.1). Thanks all for your feedbacks!

73 comments:

  1. hi,
    i have an error.
    always count = 0:

    int count = [response.products count];

    if (count>0) {
    ......
    .......
    {
    else{
    .......
    ........
      }

    what xcode version are you used? and ios?
    thanks for the tutorial.

    regards.

    ReplyDelete
  2. Hello there.

    Have you added an IAP item in your iTunesConnect account?
    Also, double check your Product ID. They are case sensitive.

    ReplyDelete
  3. When i run the source code, with your app id it comes up with an UIalerview that tells me that there are no products. I have also tried to put in my own app id but it comes with the same error. Can you give me some help with this? :)

    ReplyDelete
  4. Hello SaveController,

    You need to put in your own app id. And, you need to have added the application in the itunesconnect. And, you need to have the corresponding IAP items with the correct Product ID in Manage In-App Purchase section.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hi Emir,

    I've done exactly what you did but with my Product ID and instead of unlocking feature 2, it removes ads by me.

    But I get the same alertview as SaveController says : No products to purchase.

    Any idea why?

    Thanks!
    BTW: great tutorial.

    ReplyDelete
  7. You are getting No products to purchase because iOS cannot find a product in your iTunesconnect that matched the ID in this code line:

    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:@"com.emirbytes.IAPNoob.01"]];

    Make sure the ID matches completely (case sensitive).

    Btw, I am using XCode 4.3.1. iOS 5.0.

    ReplyDelete
  8. Please note that if you just download my sample app and run it, it will NOT work, because those product IDs and Bundle IDs are fake ones. You need to change those to your own Bundle ID (com.yourcompany.yourapp) in plist settings and change the IAP product ID to your own (com.yourapp.item01).

    Follow the instructions in the tutorial carefully and you should be ok. :)

    ReplyDelete
  9. -(BOOL)IAPItemPurchased {



    NSError *error = nil;

    NSString *password = [SFHFKeychainUtils getPasswordForUsername:@"IAPNoob01" andServiceName:kStoredData error:&error];



    if ([password isEqualToString:@"whatever"]) return YES; else return NO;



    }

    I am not sure who's username/password must be entered here?

    ReplyDelete
  10. hello

    would still like an answer to the question posed earlier but I have this as well...

    1 - the purchase works fine on the simulator
    2 - i use the -(IBAction)deleteKeyChain:(id)sender function to back out the keychain on the simulator

    3 - I retry the purchase and keep getting alert that i already purchased would i like to download it for free...

    why would it still know that since i used the deletekeychain to remove all reference to the purchase? this again is being done on the simulator?

    please help...

    ReplyDelete
  11. Hello framic,

    For the first question:
    The username/password is not really a username/password of anybody. It is just a text that YOU choose to register your in app purchase into user's device.

    For the second questions:
    1. the purchase is not supposed to work in simulator because Apple prohibits StoreKit in simulator. If you can make it work, I guess Apple has allowed it recently? Not sure.

    2/3. Your getting alert like that (would like to download again for free?) after you delete the keychain IS OKAY! It means it works fine.

    ReplyDelete
  12. <>

    1. Just wondering where its being stored is it on apple servers or local to my computer? if so can I remove the keychain from my computer?

    2. Why isn't the (IBAction)deleteKeyChain function remove the keychain so that the retesting of the app provides me the same sequence of screens like before I executed the In App Purchase the first time.

    ReplyDelete
  13. The keychain is saved in user device 'registry'. Exactly i have no idea where.

    Your deletekeychain already works. The value was deleted, you are getting a different msg from iOS (ie redownload the purchase) is because that is how iTunes server works- it disallows multiple purchases of same item from same user.

    Hope this helps

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Does somebody use this in production?
    If " it disallows multiple purchases of same item from same user" why are we using storeUsername and deleteItemForUsername. If i use deleteItemForUsername i still get the message that the product i already purchased.

    Can someone explain?

    ReplyDelete
  16. Well, I have used this in 2 of my apps already.
    Purchases are coming in fine.

    That storeUsername is to provide a better data saving compared to, say, NSUserDefaults. It is only for your app's usage to securely register the "bought" flag that your user made into the device.

    For example, if a user purchased your IAP, and then deleted your app for some reason. If you use NSUSerDefaults to save the "bought" flag, then when the user reinstall your app, your app IAP will appear to be locked again. This is because NSUSerDefaults is saved with the app bundle. Deleting an app, will delete the NSUSerDefaults data as well. Feature 2 will not be able to be used.

    Of course, if after that the user attempt to purchase it again, iTunes will tell him he already purchased it last time and asks whether he'd like to redownload it. In terms of users' money, there is no problem. The problem is in the user's experience - he might get confused as why he need to tap the "purchase IAP" button again, while he has already bought it before he deleted the app.

    So by using storeUsername (by right this is called Keychain), even after the user deleted the app, the "bought" flag is still there. And that is the ONLY purpose of Keychain in this example.

    Hope u can understand my explanation.

    ReplyDelete
  17. Thank you for posting this and answering user's questions! It's helped alot!

    ReplyDelete
  18. I got this to work the second try, yay! Your notes really help me understand what is going on.

    The only thing I couldn't get working is the function to delete the keychain entry.
    I replaced the username with mine, and tried using single quotes (') or (\") around the attributes (you cant have embedded double quotes) but to no avail.

    I am able to get this to reset if I create a different test user.

    Thanks so much again for posting this, I owe you a couple beers

    ReplyDelete
  19. got the feedback from apple that you need a seperate restore purchase button. can't get it to work...

    ReplyDelete
  20. Dear emir,

    Firstly thank you for your efforts.
    I followed you tutorial and succeeded to purchase one feature on Simulator. But there were some question I need your help here:
    1: I got the message: " Do you want to buy one Unknow app for $0.99?". Not " Do you want to buy one Feature 2 for $0.99?". Does this is correct?
    2: When I try to buy the item again, I got the message" You already purhcase this...", this is expected behavior and what I want to know is where can I get is event message to unlock the item user bought?

    Thanks so much,

    ReplyDelete
  21. Hello,

    I found the solution for my second question and "ward" also:
    Add this code:
    - (IBAction) RestoreCompletedTransactions:(id)sender
    {
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    }

    and handle it at:

    - (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
    {
    NSLog(@"Restore completed transactions finished.");
    NSLog(@" Number of transactions in queue: %d", [[queue transactions] count]);
    for (SKPaymentTransaction *trans in [queue transactions])
    {
    NSLog(@" transaction id %@ for product %@.", [trans transactionIdentifier], [[trans payment] productIdentifier]);
    NSLog(@" original transaction id: %@ for product %@.", [[trans originalTransaction] transactionIdentifier],
    [[[trans originalTransaction] payment]productIdentifier]);
    }
    UIAlertView *tmp = [[UIAlertView alloc]
    initWithTitle:@"Purchases Restored"
    message:@"Your previously purchased products have been restored!"
    delegate:self
    cancelButtonTitle:nil
    otherButtonTitles:@"OK", nil];
    [tmp show];
    [tmp release];


    }

    waiting for my first question.

    Thanks

    ReplyDelete
  22. i always get message:@"Your previously purchased products have been restored!" but in the log in states that there are 0 transactions in queue...

    i first bought it with the same account

    ReplyDelete
  23. @ward i dont think the queue persists.. once the product is restored, why would there be an open transaction? My first time for IAP too..

    ReplyDelete
  24. the NSLog(@" Number of transactions in queue: %d", [[queue transactions] count]);
    is called before the transactions are restored. my restore function is still not working..

    ReplyDelete
  25. Hi Emir,

    I've done exactly what you did but with my Product ID and instead of unlocking feature 2, it removes ads by me.

    But I get the same alertview as SaveController says : No products to purchase.

    Any idea why?

    ReplyDelete
  26. Hi emir,
    for testing this code in device m trying to sign in with test account but the test account asking credit card details i enterd correct but its not taking can u tell me the steps to test InappPurchas e in device

    ReplyDelete
  27. Thank you so much for posting this - it really helped me out!

    ReplyDelete
  28. i get an error, i made a sreen picture of the error , and i cant fix the error plz help.

    http://www.mediafire.com/?mgih2jq8lb43vcd

    ReplyDelete
  29. You hardcoded password as NSString:
    [SFHFKeychainUtils storeUsername:@"IAPNoob01" andPassword:@"whatever" forServiceName:kStoredData updateExisting:YES error:&error];

    Isn't that unsafe..someone could find out the password value(http://stackoverflow.com/questions/11468465/get-nsstring-value-from-app-on-jailbroken-device)
    and use it somehow to avoid paying for inn-app-purchase.
    Correct me if i am wrong.Tnx.

    ReplyDelete
  30. Xcode says "paymentWithProductIdentifier is deprecated"
    Any idea ?

    ReplyDelete
  31. @Warner Lustgraaf
    This matter maybe come from when you adding a framework kit. You have to choice the 'Target' correctly where this framework will be applied when you add this framework.

    ReplyDelete
  32. @xuan -hy.....my paymentQueueRestoreCompletedTransactionsFinished is never called when i click restore button.? what u think would be an error.

    ReplyDelete
  33. I have a problem with the Reset InAppPurchase Code....

    It gives me three errors:

    1. Use of undeclared identifier'size'
    2. Use of undeclared identifier 'font'
    3.Receiver type 'char*' is not 'id' or interface pointer, consider casting it ti 'id'
    4.Instance method '-andServiceName:error:' not found (return type defaults to 'id')

    and i have one more question! is it correctly if i tap the cancel button when the ...buy alert appers it seems to be buyed?! Will it correctly run in the App Store? NEED YOUR HELP

    Thank you

    ReplyDelete
  34. Hey Subscriptions

    Great tutorial, I have one question.

    Can this work for in app renewal subscriptions.

    ReplyDelete
  35. paymentWithProductIdentifier is depreciated in xCode 4.4.1...any idea guys? thanks!!

    ReplyDelete
  36. Hey guys i got this error and warning when i copy pasting the Source Code to my new project.
    Error:
    - Use of undeclared identifier 'kStoredData'

    Warning:
    - 'paymentWithProductIdentifier:' is deprecated
    - on SFHFKeychainUtils.m there is method : + (NSString *) getPasswordForUsername: blablabla. i got this warning : Control may reach end of non-void function

    do u guys know how to solve it ???

    ReplyDelete
  37. okay, i'm sorry for my carelessness.
    i forget to put this code : #define kStoredData @"com.emirbytes.IAPNoobService" under the synthesize on my viewcontroller.m.
    the error is gone now but still have 2 warnings.

    do you guys know how to solve it ?

    ReplyDelete
  38. It looks like the author stopped answering in this area on May 29, 2012

    ReplyDelete
  39. I am never able to download the source. It keeps going round and round and tries to make you pay for the premium download. Can you send me the source to yooolie@yahoo.com? Thanks.

    ReplyDelete
  40. We recently shared an open source library for in-app purchase on iOS. You can find it at http://project.soom.la. The library simplifies the Storekit API and can be used as an SDK or as reference code. We also included a sample app.

    ReplyDelete
  41. This is the best tutorial for IAP thank you so much!,!

    ReplyDelete
  42. Hi, there. Thanx for the author for this valuable post!!!!

    The deprecated line can be replaced by

    SKPayment *payment = [SKPayment paymentWithProduct:validProduct];

    Best regards

    Ograo

    ReplyDelete
  43. First thanks for the code! I am trying to use for 2 different items in 2 different classes using unique store username/password and works great until, I make one purchase and receive UIAlertView *tmp for that item (all good), then make the second purchase when it finishes, I receive not only the UIAlertView *tmp for the second item but it re-triggers the UIAlertView *tmp for the first item again that was already purchased... any ideas how to fix... thanks, again

    ReplyDelete
  44. Hi, rossideas.

    You can avoid the general UIAlertView

    -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

    and put the code between the

    if ([SKPaymentQueue canMakePayments]) {
    ...
    ...
    ..
    [tmp show];

    directly on the procedure you've used to manage each of the itens.

    Best regards

    ReplyDelete
  45. Hey guys,

    Since this tutorial came out, Apple has altered their requirements for in-app purchases, now all developers are required to include a "restore" button, allowing users to access in-app purchases they had previously, well, purchased. As this isn't covered in the tutorial I figured I'd post the code that makes it happen just to save everyone some time and frustration.

    - (IBAction)restorePreviousTransaction:(id)sender {
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

    }

    - (void)restoreTransaction:(SKPaymentTransaction *)transaction {

    }
    -(void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {

    }


    - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
    {
    NSLog(@"%@",queue );
    NSLog(@"Restored Transactions are once again in Queue for purchasing %@",[queue transactions]);

    NSMutableArray *purchasedItemIDs = [[NSMutableArray alloc] init];
    NSLog(@"received restored transactions: %i", queue.transactions.count);

    for (SKPaymentTransaction *transaction in queue.transactions)
    {
    NSString *productID = transaction.payment.productIdentifier;
    [purchasedItemIDs addObject:productID];
    NSLog (@"product id is %@" , productID);


    if ([productID isEqualToString: @"com.sparktap.waltersbandadremoval"])

    {


    [feature2Btn setBackgroundImage:[UIImage imageNamed:@"SparkOfInsanity.png"] forState:UIControlStateNormal];
    //carry out whatever must change in you app after a purchase is made here, do this just to be safe, it shouldn't matter if you have proper if/else statements elsewhere in the app


    NSError *error = nil;
    [STKeychain storeUsername:@"IAPNoob01" andPassword:@"whatever" forServiceName:kStoredData updateExisting:YES error:&error];
    //The above couple lines are crucial to this working, do not leave them out.


    UIAlertView *tmp = [[UIAlertView alloc]
    initWithTitle:@"Purchases Restored"
    message:@"Your previously purchased products have been restored!"
    delegate:self
    cancelButtonTitle:nil
    otherButtonTitles:@"OK", nil];
    [tmp show];
    [tmp release];
    }


    else {


    UIAlertView *tmp = [[UIAlertView alloc]
    initWithTitle:@"Not Restored"
    message:@"Sorry"
    delegate:self
    cancelButtonTitle:nil
    otherButtonTitles:@"OK", nil];
    [tmp show];
    [tmp release];



    }
    }
    }

    Enjoy!

    ReplyDelete
  46. Hi,
    when i have test this app on simulator, it take username/password. So what details exactly entered here plz suggest...

    ReplyDelete
  47. when i have test this app on simulator, it take username/password. So what details exactly entered, i am entering id and password but it,s not unlock what should i do to make unlock.

    ReplyDelete
  48. @Brian T, thanks for your codes.
    THose are good.

    Just some more notes about IAP test accounts:
    The test accounts are extra sensitive - they can be deactivated
    by Apple if you accidentally try to buy apps/download apps in the
    actual store. So you need to create new test accounts.

    It is frustrating to make IAP work, especially with new iOS version,
    they keep on changing so many crap on it. If you are getting 0 products
    when trying to restore/purchase, DELETE THE APP in your device, and CLEAN ALL and rebuild and run again.

    Good luck all, and thanks for all the nice words. I just share what I know.
    :)

    ReplyDelete
  49. Hi emir, thanks for your post, it works perfectly fine, But I have 3 products to purchase, product1, product2 and product3, I have a confusion here, how to check whether these products are purchased.Whether I have to create different IAPItemPurchased functions and if YES then where I should store the passwords for each product, because delegate functions are written only once.

    ReplyDelete
  50. I think, in this function -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { and in case case SKPaymentTransactionStatePurchased:
    {
    }
    How I should check which item I am buying and accordingly show the message that you have bought this item and save it to key chain, I am not getting.

    ReplyDelete
  51. Hey Emir, KeyChain works for saving only one user/password , I have 4 products to buy, and user can buy each product individually, so I should store the data for each product separately in keyChain.

    ReplyDelete
  52. I solved my last query through this tutorial, can any one suggest auto-renewable In-app Purchase. !!!

    ReplyDelete
  53. I tried to download and execute the code in the simulator. I get the following message "you've already purchased this in app but it hasn't been downloaded"

    ReplyDelete
  54. Hello all,
    Sorry I can't answer all questions (busy making my own apps etc) however I appreciate all the interest on this blogpost.

    Anyway to answer the most recent posts:

    Ranjit: yes, if you have 4 products, then you will need 4 pair of user/pw data.

    Prasanna, if you are getting that message, means it works. If iOS detected that you have purchased the feature before, then that is the prompt you will get forever. (unless you change the IAP Ref ID).

    Cheers.

    ReplyDelete
  55. Hi,

    Great tutorial without any unnecessary fluff.

    Have found a couple of solutions:

    Get empty list back from productsRequest?

    Try to change the product identifier from "com.emirbytes.IAPNoob.01" to "01"
    Only the product id seems to be enough now.

    Getting deprecated error for paymentWithProductIdentifier?

    SKPayment *payment = [SKPayment paymentWithProductIdentifier:@"com.emirbytes.IAPNoob.01"];

    Change line to
    SKPayment *payment = [SKPayment paymentWithProduct:validProduct];

    validProduct is defined in the line above.

    Want to change to your test account on the device?
    Settings -> Itunes & App Stores -> Signout

    Then upload your app and start it. You will now be asked for login and pass. Do not enter these data in Settings -> Itunes & App Stores -> Signout because you will have to give CC details and your test login will probably not work as a test login anymore.

    Error connecting to process 0, want to change the login id or other IOS Simulator errors?
    IOS Simulator -> Reset Content and Settings

    Cheers

    ReplyDelete
  56. Hi Emir, thanks for this tutorial on implementing In-App purchases. I found it really helpful! I compiled a list of some top resources on this topic that I'd like to share with other developers. I included your post. Hope other developers find this useful also. Check it out, feel free to share: http://www.verious.com/board/Giancarlo-Leonio/implementing-your-ios-in-app-purchases/

    Thank you

    ReplyDelete
  57. If you get a warning on this line:
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    Error:
    "Sending 'InAppPurchaseManager *const __strong' to parameter of incompatible type 'id<SKPaymentTransactionObserver>'"

    Then make sure you add the following delegates to your .h file's @interface line: <SKProductsRequestDelegate,SKPaymentTransactionObserver>

    There is a post here that addresses the issue: http://stackoverflow.com/questions/8763985/error-objective-c-in-app-purchase-implementation

    ReplyDelete
  58. Just a quick note . i was alsp getting error
    But I get the same alertview as SaveController says : No products to purchase.

    Please make sure u not using jail broke device :)

    ReplyDelete
  59. In App Purchase - Help on Last stage - downloading content


    Following one of Ray's GREAT tutorials, i implemented IAP however i run into an issue towards the end which is downloading the content the user has just purchased. Ray left this part out in his tutorial so i decided to do some research and implement coding so that the user can download their purchased content and then with the same code place the content from the directory it was downloaded and back into the app for the user to access and use.

    [b]i used the following code however im having a few issues with it:
    [/b]
    [code]- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads;
    {
    for (SKDownload *download in downloads) {

    if (download.downloadState == SKDownloadStateFinished) {
    [self processDownload:download]; // not written yet
    // now we're done
    [queue finishTransaction:download.transaction];

    } else if (download.downloadState == SKDownloadStateActive) {

    NSString *productID = download.contentIdentifier; // in app purchase identifier
    NSTimeInterval remaining = download.timeRemaining; // secs
    float progress = download.progress; // 0.0 -> 1.0

    // NOT SHOWN: use the productID to notify your model of download progress...

    } else { // waiting, paused, failed, cancelled
    NSLog(@"Warn: not handled: %d", download.downloadState);
    }
    }
    }


    - (void) processDownload:(SKDownload*)download;
    {
    // convert url to string, suitable for NSFileManager
    NSString *path = [download.contentURL path];

    // files are in Contents directory
    path = [path stringByAppendingPathComponent:@"Contents"];
    NSString *zip = [path stringByAppendingPathComponent:@"FoundationSkills.zip"];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error = nil;
    NSArray *files = [fileManager contentsOfDirectoryAtPath:path error:&error];
    NSString *dir = [MyConfig downloadableContentPathForProductId:download.contentIdentifier]; // not written yet

    for (NSString *file in files) {
    NSString *fullPathSrc = [path stringByAppendingPathComponent:file];
    NSString *fullPathDst = [dir stringByAppendingPathComponent:file];

    // not allowed to overwrite files - remove destination file
    [fileManager removeItemAtPath:fullPathDst error:NULL];

    if ([fileManager moveItemAtPath:fullPathSrc toPath:fullPathDst error:&error] == NO) {
    NSLog(@"Error: unable to move item: %@", error);
    }
    }

    // NOT SHOWN: use download.contentIdentifier to tell your model that we've been downloaded
    }[/code]


    i have this persitent error that says "Use of undeclared identifier 'MyConfig'" is there anything else i can swap that with?

    i also have three warnings on the following lines:

    On the *NSString productID line i get a warning that says: Unused variable 'productID'

    On the NSTimeInterval remaining line i get a warning that says: Unused variable 'remaining'

    On the float progress line i get a warning that says: Unused variable 'progress'

    How do i fix those? or what should i add to the code..

    ReplyDelete
  60. OR is there a simpler code i can use to to this? which is...
    As a continuation to Ray's IAP IO^ tutorial, To automatically download the purchased content and transfer it back into the app or view controller and let the user access it - would also be nice to show a progress bar for the download. Please, all suggestions are welcome!!

    in the tutorial ray has this line of code which doesnt download anything as far as downloading the content back into the app and letting the user access it is concerned: [b]Is there something i can add to it or add to the code above to let me do what i need ?[/b]

    - (void)completeTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"completeTransaction...");

    [self provideContentForProductIdentifier:transaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    }

    (void)provideContentForProductIdentifier:(NSString *)productIdentifier {

    [_purchasedProductIdentifiers addObject:productIdentifier];
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
    [[NSUserDefaults standardUserDefaults] synchronize];
    [[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:productIdentifier userInfo:nil];

    }

    ReplyDelete
  61. Thank a lot, for your tutorial, it helps me a lot.
    Thanks again.....

    ReplyDelete
  62. Just wanted to thank you and other contributors for very informative, helpful tutorial and posts. I've used bits and pieces of it to build my own in-app engine for iOS. Keep doing great job and I hope you received my donation too.

    ReplyDelete
  63. simplest tutorial :)
    This will be my 1st in-app purchase app with auto-renewal process.

    ReplyDelete
  64. Great Tutorial. I'm finally able to start the app purchases with your tutorial, but not to fast. It work perfectly, but it opens all the products. I'll explain: The consumers will record how many objects they want to. But they need to pay to see advanced features for each one. Your code make the purchase, but open all the objects, did you know what I'm trying to do? What do I need to make each purchase open only one object. Those objects are on a table views and the purchase button are inside those details cells.

    Thank you very much

    ReplyDelete
  65. Wow, I've been having heck of a time figuring out how to have multiple iap items for sale. com.myapp.iap1 and com.myapp.iap2

    Could someone paste the code for this, including the deprecated fix.

    I will pay the first person to post a working code via paypal. $20 is waiting for ya!!!

    ReplyDelete
  66. Sorry, offer is expired. Found this. Way easier to modify.

    http://www.codetuition.com/ios-tutorials/integrating-ios-app-with-in-app-purchase/#comment-1966

    ReplyDelete
  67. this code is missing the restore previous purchase feature so edit this code when use..

    ReplyDelete
  68. Anyone else have this error:

    Failed to connect with error: Cannot connect to iTunes Store

    ReplyDelete
  69. I was able to implement this code in my app for one IAP, but have been unable to add a second button with unlockable content. Anyone have any success with adding a second IAP in their app with this method...and have any tips? Thanks.

    ReplyDelete
  70. How do you know if it is a free trial and if it is expired does it automatically start billing them or do they have to purchase again ?

    ReplyDelete