Update: MKStoreKit 4.0, an updated version of the one presented here is available. Please check it out and use 4.0 instead of this.
Update: A Chinese translation is available here.
Last week, Apple announced that in-app purchases will be available for free apps as well. This could probably free developers from creating “lite” and “pro” versions of the app and allow developers to “unlock” features inside the app and create business models that the AppStore haven’t seen. This model could be a great boon for developers like us to upsell our apps (provided they are of good quality) and to reduce piracy. In this article, we will look at how to incorporate StoreKit to do In-App purchases for your iPhone Application.

Update: Did it really free you from creating “lite” and “pro” versions? How did you manage to give away free copies of your in-app purchases for reviewers? Read my tutorial on how toenable in-app purchases for free

Apple allows three types of purchases within the app and Apple terms them as consumables, non-consumables and subscriptions.

Consumables are products that are “consumed” immediately. This means, if the purchase is made today, and the user wants to purchase it again tomorrow, he will be charged again when he attempts a purchase.

Non-Consumables are features that are purchased exactly once. Apple automatically takes care of “remembering” those purchases and allows the user to purchase them again for free, just like downloading apps you already purchased.

Subscriptions are the most complicated part, They behave like non-consumables during the subscribed period and like consumables after that. You as a developer have to ensure that anything that is subscribed by the user is available across all of his iTunes synced devices when they are purchased from one device. Hence, do not lock in-app purchases to UDIDs. This might even get your app rejected. The StoreKit, as on date, doesn’t have any built-in mechanism to do it automatically which is why subscriptions are a bit tougher to develop.

One important point to note is that, in-app purchases cannot be used to deliver product updates. Changes to the binary has to be separately submitted. However, if you are a game developer, game data, maps, levels and other “data files” are allowed for in-app purchase.

In this post, we will focus on how to prepare your app for enabling features for the “pro” version from the “lite”. Or technically, we will focus on how to bring in, consumables and non-consumables into your app. We will leave the subscriptions part to another blog post as it’s quite complicated and involves some server side programming as well.

Preparing your iTunes Connect

To start with, in-app purchases, you need to do some ground work on your iTunes Connect account. A three step process that Apple thinks every developer should know. (Unfortunately I couldn’t find any official documentation for this)

Step 1:

First is to create an App ID and enable in-app purchases for that. This App ID shouldn’t have any wild card characters or else, the in-app purchases option will be grayed. I would always recommend to use a different App ID for every application you create.

Step 2:

Create provisioning profiles (Development and Distribution) using this App ID. Again it’s a good practice to create different provisioning profiles for every app. There are many tutorials on how to create this here and Apple’s own documentation here (This link will prompt you to login).

Step 3:

You need to create product references in your iTunes account. Each individual in-app purchase should be uniquely identifiable. Apple recommends using the reverse DNS notation, something like, com.mycompany.myiproduct.ifeature Before creating product ids, you need to associate it with a existing application in AppStore. If your app is not yet live, you can create a dummy, placeholder application, fill in the metadata (which you can anyway change it later) and check “upload binary later”.

To create a new in-app purchase, open your itunes connect and choose “Manage In-App purchases”. Choose the app for which you want to setup in-app purchases and click next. You should see a screen like this.

iTunes Connect in-app Purchase

The reference name is the name that appears during the in-app purchase prompt. Any name like, Levels 10 – 50 will be fine. The Product ID should be unique. This is used for reporting as well as within your app for requesting a purchase (more on this later). You can select the type as consumable or non-consumable. When you say an in-app purchase is consumable, your users will be charged everytime they purchase it. This is perfect for a radio app that requires users to pay for listening to a song everytime. If it’s a product feature, set it as non-consumable. Non-consumable products are purchased exactly once. When the user attempts to purchase it again, it will be delivered to him for free.

Type in the other required detail in this page and click save.

Step 4:

The fourth and final step is to create test user accounts. After you program the app, you might want to test the app. You can use these accounts to login to the App Store. The purchases will be processed as if it were real but no financial transactions will take place.

This completes your iTunes connect configuration. Take a deep breath. We have just started. A lot more to go.

Writing the StoreKit Code

Now that you have setup the iTunes account, let’s start by writing the actual code to interact with the AppStore and allow users to make purchases. For coding help, nothing beats the Apple’s official StoreKit programming guide. However, the guide has one bug that crashes the program. We will see how to circumvent it properly here.

Step 1: Adding StoreKit.Framework

The first step here is to add the StoreKit.Framework to your project.

Step 2: Parental Controls

It’s important to check whether the iPhone/iPod doesn’t have parental control restrictions. When you try to initiate a purchase when parental controls are on, you might crash your app. The apple docs code seems to have an error here. The function, canMakePayments is a static method of class SKPaymentQueue and not a member function. The working code looks like this.

if ([SKPaymentQueue canMakePayments])
{
... // Display a store to the user.
}
else
{
... // Warn the user that purchases are disabled.
}

Step 3: Retrieving the product information and populating the UI

Now the you have designed a gorgeous looking view, you can show it to the user using, say presentmodalviewcontroller or any similar method. Do note that StoreKit doesn’t provide the UI to be displayed. It’s upto the developer to design the UI. The first step is to query your “In-App Purchases” and show the user, the available list of options. Retrieving the product information from AppStore is a couple of lines of code as illustrated here.

- (void) requestProductData
{
SKProductsRequest *request= [[SKProductsRequest alloc]
initWithProductIdentifiers: [NSSet setWithObject: kMyFeatureIdentifier]];
request.delegate = self;
[request start];
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:
        (SKProductsResponse *)response
{
NSArray *myProduct = response.products;
// populate UI
[request autorelease];
}

The kMyFeatureIdentifier you pass to initWithProductIdentifiers is the uniquely identifiable product id you created in your AppStore account. Remember the reverse DNS product id? You CANNOT specify a wild card character here. So, if you have multiple products, you have to initialize the request object with a list of product using

SKProductsRequest *request= [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObjects: myGreatFeature1, myGreatFeature2, nil]];

You can pass as many features as possible to setWithObjects and be sure to end the last object with nil.
Setting the delegate to self and calling the start method will invoke the “didReceiveResponse” delegate. The delegate will give you an array of products and the request itself. Use the array to populate your store UI and release your request here. (This is the same request you created initially).

Step 4: Adding a Transaction Observer

This is a very important step. You can do this as soon as your app is open or when you start a “In-App” purchase session. Do note that, in-app purchase requests are continued even if the user quits the app in between. So imagine a case, when user buys an item, but before the transaction is processed, he gets a phone call that interrupts everything. Though the actual transaction is not interfered (as it happens on Apple servers), your application will never know what happened. To ensure that you get all transaction notifications, (completed/ pending/restored), you have to register a class that receives the callbacks from AppStore. This class should implement and it’s delegate methods.
This is the code for registering the your store observer.

MKStoreObserver *observer = [[MKStoreObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];

(If you can’t follow anything here, just head below for the real source code, but it’s advised that you read through this, understand and then use the code in your app).

Step 4: Implementing the callback

Within your MKStoreObserver class, you have to implement the callback function paymentQueue:updatedTransactions:

This function will receive updates on the transactions as and when it’s made. Because your transactions take place even when the app is closed, you should be ready to receive these notifications as soon as you open the app. So the best place is to initialize it in applicationDidFinishLaunching or equivalent method.

Now in the updatedTransactions functions, handle the three types of transactions, purchased, failed and restored.

- (void)paymentQueue:updatedTransactions(SKPaymentQueue *)queue
updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
// take action to purchase the feature
[self provideContent: transaction.payment.productIdentifier];
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code != SKErrorPaymentCancelled)
{
// Optionally, display an error here.
}
// take action to display some error message
break;
case SKPaymentTransactionStateRestored:
// take action to restore the app as if it was purchased
[self provideContent: transaction.originalTransaction.payment.productIdentifier];
default:
break;
}
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
}

Purchase and failed are seemingly straightforward. You will receive a restored transaction message when your app was quit before the transaction was completed. You should always do the “same” thing when your purchase is new or restored. If you want to charge your users for every download, then probably, set the in-app purchase to be a “consumable” item. But be sure to use that only for purchases that are really “consumable”. Like a live radio show or a podcast and not for unlocking additional levels. Users expect that a level they have unlocked will stay forever.

Three things to note here
1) You should remove the transaction from the payment queue after the transaction is complete. Otherwise, the transaction will be re-attempted, which is not what the users expect (and your app will most likely get rejected).

2) You should provide the content (or unlock the feature) before completing the transaction. When you receive the message SKPaymentTransactionStatePurchased, it means that the users’ credit card has been charged. It’s high time that you provide the feature.

3) You should not display an error when the transaction fails because the user rejected it. Display any error message only when,

if (transaction.error.code != SKErrorPaymentCancelled)

Apple recommends you not to display an error, because that is not an error. The user has purposely cancelled the transaction (probably because the price was too high?). You can audit it and go on. But for heaven sake, don’t display an error like, “Unable to process transaction because user cancelled operation!”. It will be soooo Windozy…

Step 5: The actual purchase

Now that your architecture is ready, you can go ahead and initiate the purchase by calling the function below when the user clicks your “Buy” button on the UI.

SKPayment *payment = [SKPayment paymentWithProductIdentifier:myGreatFeature1];
[[SKPaymentQueue defaultQueue] addPayment:payment];

Optionally, you can add something like payment quantity for “consumable” items.

payment.quantity = n; // number of "items" that user wishes to purchase.

Finally, after providing the feature, you should “remember” that the user has purchased the app. Apple’s recommended way is to use NSUserDefaults, the same way you store your settings.

Testing your app

Alright. Now let’s test the app. Note that, the app cannot be testing from iPhone simulator. The StoreKit communicates with AppStore.app to complete transactions which is not present in the Simulator. So connect your iPhone and run the app.
Remember step 3 you did in iTunes connect? You created some test accounts? Well, they are used for testing your app. Before using that, sign out of AppStore by opening Settings.app -> Store -> Sign Out.
Start the app. (You should NOT sign into the AppStore with the test user account. It will anyway ask you to provide your credit card which we are not interested in)

Open the Store UI and initiate the purchase. You will get a prompt like, “Do you want to buy 1 “ABC feature” for 2.99$? Tap Buy. You will be prompted to login. Provide your test account login. The AppStore provides a secure connection to the iTunes account and notifies you whether the purchase was successful through the callback paymentQueue:updatedTransactions. When you use the test account, the scenarios will be exactly same expect that no one will be charged. These test accounts run inside a sandbox.

In case your app got quit by a phone call, the transactions continue and you will get a restoreTransactions the next time your app is opened. This is why you should start listening to transactions as soon as you open the app (not just when the user opens the store UI)

That’s it. I wouldn’t say its easy. But for developers who have some intermediate knowledge in iPhone development and Objective C, it shouldn’t be a big deal. Remember that when your app is free, you get 10 times more downloads (as reported by admob statistics). When people actually use your app, there is a high chance that they buy your in-app features. That’s upselling becomes easy. Given that this is a very strong business model for the already saturated AppStore, you should start incorporating this model into your code soon.

Source code

The source code, MKStoreKit, contains four files. MKStoreManager.h/m and MKStoreObserver.h/m. The StoreManager is a singleton class that takes care of *everything* Include StoreKit framework into your product and drag these four files into the project. You then have to initialize it by calling [MKStoreManager sharedStorageManager] in your applicationDidFinishLaunching. From then on, it does the magic. The MKStoreKit automatically activates/deactivates features based on your userDefaults. When a feature is purchased, it automatically records it into NSUserDefaults. For checking whether the user has purchased the feature, you can call a function like,

if([MKStoreManager featureAPurchased])
{
//unlock it
}

To purchase a feature, just call

[[MKStoreManager sharedManager] buyFeatureA];

It’s that simple with my storekit. The source code will be uploaded when this post is read by at least 1000 people. Please spread the word.

As always, all my source code can be used without royalty into your app. Just make sure that you don’t remove the copyright notice from the source code if you make your app open source. You don’t have to attribute me in your app, although I would be glad if you do so :)

Downloads:
Update: MKStoreKit 4.0, an updated version of the one presented here is available. Please check it out and use 4.0 instead of this.

Version 2:MKStoreKit V2.0
Version 1:MKStoreKit.zip

Troubleshooting

Despite all this, In-App Purchases remain a biggest and the most PITA situation for any iPhone developer. If you can’t get it work, check whether your “didReceiveResponse” delegate returns the product id you passed as invalid. You can confirm this by adding a NSLog statement inside the delegate. Double check if your invalid product id returned here matches the product id you created in iTunes connect. If they are same, check if your product id on iTunes connect says “Cleared for Sale”. For this, you have to provide a screenshot and “Developer Approve” it.

Another case is, if this is your first app, then chances are that, your “Paid Applications Contract” isn’t yet in effect. If this is the case, you have to wait till Apple approves your bank details.

If these two doesn’t work, you might have to wait for 12-24 hrs, till Apple propagates your iTunes connect information to all it’s servers. See the last line in this documentation for more details.

If you have issues adding this to your application, you can hire me to do it for you. I charge a nominal fee of $350 – $700(depending on the complexity) for the whole integration and it should take approximately half a day to one day. You can also approach me for a customized code that suits your business model as well.

Update 1: (22-Oct-2009): Code changes to fix a crash that occured in most cases.
Update 2: (7-Dec-2009): Added link to MKStoreKit v2.0.
Update 3: (4th Dec 2010) MKStoreKit 3.0, an updated version of the one presented here is available. Please check it out and use 3.0 instead of this.
Update 4: (8th July 2011) MKStoreKit 4.0, an updated version of the one presented here is available. Please check it out and use 4.0 instead of this.

Support me

Hourly rates of a iPhone developer is skyrocket high. I believe this code would have saved your coding hours by at least a day. You can consider supporting further development by funding me through PayPal.  My PayPal email is mugunth.kumar@gmail.com

Mugunth

Follow me on Twitter

  • Faisal Rahman

    Hi kumar,

    I am in a strange problem!

    In my application I used your MKStore class.

    And it successfully worked.

    but today i change the price of my product in storekit. From then, My application shows “SKPaymentTransactionStateFailed:”

    why this happening, I don not know. But I did not change any line of code.

    please help……

    • mugunthkumar

      Wait for about 2-5 hrs after changing price. this happens to Apps as well. When you change the price of the app, your users will not be able to download it for another 2-5 hrs :)

  • Hi, purchaseableObjects = NULL when I log it. I have an in-app purchase set up and the correct reverse DNS id. What could the problem be?

  • Alex

    I have been attempting an in app purchase for hours now.

    Every time, I don't get a log in prompt and the error being passed to the observer is coming in as null! I have no information to debug by.

    Here's what I did:

    * Set up a new App ID with purchases enabled
    * Created the application in iTunes and added an in app purchase
    * Logged out of my iTunes account on the device

    The strange thing is that my request for the product works great – I get a response back as expected. However, when I add a Payment to the queue, the callback always receives a failed transaction. More frustrating is that the error provided is null!

    Here's my observer code:

    – (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    for(SKPaymentTransaction *transaction in transactions) {
    switch(transaction.transactionState) {
    case SKPaymentTransactionStatePurchased:
    [SpeakHereAppDelegate setIsUpgraded:YES];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    break;
    case SKPaymentTransactionStateFailed:
    NSLog(@"%@", [transaction.error localizedFailureReason]);
    if(transaction.error.code != SKErrorPaymentCancelled) {
    [self showAlertMessage:@"Your purchase could not be completed. Please try again later." withCaption:@"Oops!"];
    }
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    break;
    case SKPaymentTransactionStateRestored:
    [SpeakHereAppDelegate setIsUpgraded:YES];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    default:
    break;
    }
    }

    Again, the transaction state is always a failure. Anybody have any ideas? This is incredibly frustrating….

    • mugunthkumar

      Did you try with my source code attached here?Sent from my iPhone

      • Alex

        I have tried your source code (which is great, by the way) and I get the same result – a null error returned and a transaction failure.

        The only reason I can think of that it wouldn't be working for is this:

        * My bundle identifier is com.mydomain.myapp
        * I am testing the app with a provisioning profile specifically for that app id with in-app purchases enabled
        * However, the uploaded binary is "v1" of my app and was built with a wildcard provisioning profile – the bundle id of the uploaded app is still com.mydomain.myapp though

        Would the fact that the uploaded binary uses a wildcard provisioning profile even have an effect on the ability to test this out? Again, the bundle IDs are a perfect match.

        Thanks!

      • Alex

        I tried uploading a new application built with a provisioning profile with in-app purchases enabled and still have the same issue.

        Not sure where to go from here…

  • Bruce

    Hi

    Thanks for the great code.

    I have a question which seems weird. When logging the [[NSUserDefaults standardUserDefaults] boolForKey:featureAId] directly, I get the correct item BOOL, but when following your suggestion of [MKStoreManager featureAPurchased], I dont.

    Any idea why or what I am doing wrong??

  • mugunthkumar

    If you have refactored my code to change variables, check them thoroughly again. The only possibility could be that some other variable is getting set when the feature is purchased.Sent from my iPhone

  • marshn

    Hi, great example and thanks for the great code! I've been able to implement and use the class successfully. However, I'm very confused about how to manage the display of a successful view page. Specifically, the provideContent function simply updates the NSUserDefaults and does not send any other type of notification, nor does it offer a delegate method. My question is 2 fold:
    1) how does the view controller know when the payment is complete so it can push a new payment complete view.
    2) because the class is instantiated in applicationDidFinishLaunching it can pick up on an interrupted purchase process, but then what view controller gets called upon to push the completed payment view, and how would you go about doing that?

    • mugunthkumar

      There is a delegate which you can set to receive product purchased notifications.

      • marshn

        hmm… do you mean SKPaymentTransactionObserver protocol which is used in the MKStoreObserver which has the method:
        – (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;

        If not, I have no idea what you mean by your response. And if you do mean that function, are you suggesting using it elsewhere… other than in the MKStoreObserver… like in a view controller _as well_?

        Maybe my question was confusing. I have a view controller, lets call it checkoutView, with a button that says checkout now, which calls [[MKStoreManager sharedManager] buyFeatureA]. Because the singleton was instantiated in applicationDidFinishLaunching, when the callback from apple is received by your class, and the provideContent function is called, there is no direct way for the singleton class to notify checkoutView so the app can update its display. The same goes for when the app starts up, and the MKStoreManager discovers there is a pending transaction, at that point how are you passing that message to a specific viewController?

        I believe there are a few ways to go about this, I'm looking for suggestions, improvements, standard way to communicate with view controllers from a singleton class instantiated in the appDelegate… What I've done for now is use the NSNotificationCenter to blast a notification to the application, and then in the checkoutView and my very first view loaded, I listen for the notification and handle the view logic accordingly.

        I added something like this to MKStoreManagers function 'provideContent'(note i'm also passing 'state' into that function now from MKStoreObserver):

        NSLog(@"sending notification of purchase");
        NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
        [dict setObject:state forKey:@"state"];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Purchased" object:self userInfo:dict];
        [dict release];

        And then in the view controllers that I want to handle the notification:

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(PurchasedNotification:) name:@"Purchased" object:nil];
        – (void) PurchasedNotification:(NSNotification *)notify {
        id ndict = [notify userInfo];
        id val = [ndict objectForKey:@"state"];
        NSLog(@"recieved notification: %@",val);
        NSString* errorMessage = @"Your purchase went through but was interrupted, click ok to continue with your purchase";
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Interupted Purchase" message:errorMessage delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alert show];
        [alert release];
        }

        Does this method follow best practices? Is there a better way? How would you, or anyone else reading this, do it?

        • mugunthkumar

          Are you using MKStoreKit version 2.0? It has a callback delegate.So in your view, prior to calling buyProductA, set the delegate to selfMKStoreManager.delegate = self;Implement theMKStoreKitDelegate (see MKStoreManager.h for its definition) in your checkoutview.This delegate was added in V2 along with this article.http://blog.mugunthkumar.com/coding/iphone-tutori

          • marshn

            AH! I was not using V2. And using a protocol/delegate is exactly what I was after. My first attempt to accomplish this was to create a protocol and delegate methods, but I failed in trying to create and implement the protocol within a singleton, so I went with the NSNotification center. But I think delegate methods are much cleaner. Thanks for everything, V2 works great!

          • marshn

            maybe update this article with a link to the version2 code. I got here through google search, and didn't notice there was a version 2 or separate blog post. Thanks so much!

  • UncleArby

    Hi Mugunth Kumar,
    You have done a great job with this code. Way better than what Apple provides. Thanks for taking the time to do this service for all of us. You can be sure that it is appreciated.

    I have it working fine in my application on a test basis. I have not approved the in-app for review yet because when I run the Apple Analyzer on the code, it flags a Potential memory leak.

    Here is where the error is flagged:

    + (MKStoreManager*)sharedManager
    {
    @synchronized(self) {
    if (_sharedStoreManager == nil) {
    [[self alloc] init]; //THIS ALLOC CAUSES THE POTENTIAL LEAK OF OBJECT MESSAGE IN THE ANALYZER
    [MKStoreManager loadPurchases];
    _sharedStoreManager.storeObserver = [[MKStoreObserver alloc] init];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:_sharedStoreManager.storeObserver];
    }
    }
    return _sharedStoreManager;
    }

    I do not get a leak there running instruments. Is the analyzer just not understanding how the code works at that point?

    Also, do you know if having that analyzer message will affect acceptance by Apple. This will be my first submission so I would like to have everything as ready as possible. As I mentioned, except for that message the code compiles and runs fine.

    • mugunthkumar

      This shouldn't be a problem.The static analyzer cannot understand where this is deallocated as this is a singleton class and lives throughout the lifecycle of the app.You can ignore this error safely.

  • wei

    Hi ~
    I have the same problem…
    but I try your method "turning off wifi just in case. 3G connected to AT&T should have worked"
    it doesn't work :(

    my NSLog:
    2009-12-21 17:22:21.836 test01[2161:207] transaction.transactionState = 2282224
    2009-12-21 17:22:21.840 test01[2161:207] err = (null)
    2009-12-21 17:22:24.437 test01[2161:207] response.products.count=(null)
    2009-12-21 17:22:24.440 test01[2161:207] response.invalidProductIdentifiers = (
    "com.mygame.test01.item01",
    "com.mygame.test01.item02"
    )
    2009-12-21 17:22:25.504 test01[2161:207] transaction.transactionState = 2287488
    2009-12-21 17:22:25.507 test01[2161:207] err = Error Domain=SKErrorDomain Code=0 UserInfo=0x22eb70 "無法連接 iTunes Store"
    2009-12-21 17:22:25.509 test01[2161:207] failedTransaction- Transaction Failed (null)

    P.S. "無法連接 iTunes Store" means "Cannot connect to iTunes Store"

    I am stuck from 2days …..
    if have solution please tell me
    I am very appreciate you :)

    • mugunthkumar

      U haven't created product ids in iTunes connect. Or your product ids are incorrect. Remember that product ids are case sensitive.Sent from my iPhone

      • wei

        Ummm…. :(
        I have purchased my product before
        but I didn't change anything…(I swear…)
        my transaction.state become null…
        so…I don't know what happened..

        but thank you for reply me so fast 😀

        • mugunthkumar

          In that case u have to write it to apple. There are cases when they take sandbox servers for non us stores out for maintenance for really long periods of timeSent from my iPhone

          • wei

            Hi ~
            I am thank you somuch :)
            I will write it to apple or redo everything @@
            thanks a lot :)

          • wei

            HI~ I find my sulotion
            I think it doesn't worked because my appliction had been reject
            so my response.products.count become null ,transaction.state null too.(it can works before)
            I redo everything , it can works again :)
            some people have the similar problem
            http://stackoverflow.com/questions/1633072/iphone

            I create a new App ID
            new app
            upload new app
            create new products….
            after redo everything

            everything is OK

            the code is no problem
            if it doesn't work
            maybe some problem in iTunes connect

            thanks mugunthkumar again :)
            Best Regards

  • I got SKPaymentQueue was not declared in this scope… Have I to import a file?

  • Hardik

    Hello Sir
    Instead of using the name of App id can we use the produt Id.for example I created an application and I got the app id that is 34757415.So can i use product id.

  • Joao

    Hi,

    There's this delegate method to know when a transaction has finished with a purchase

    – (void)productPurchased:(NSString *)productId;

    How can i implement to know when the transaction has failed or user has cancelled it?

  • idev

    Hi. I've worked with in-app purchases before without any issues. I am using the same method again for another project. But for some reason the products are returned as invalid. I tried using the fully qualified id as well as just the id (without the com.company part) but get the same response every time.

    Thought I could get some responses here. Would appreciate some help. Thanks.

  • mugunthkumar

    Check your iTunes connect configuration.Sent from my iPhone

  • idev

    Appreciate the response. I did check the configuration twice.
    The App ID for the application is the same that I am using in my info.plist and mobileprovision file while testing.
    The app id does not have a wildcard character.
    The in-app purchase products are given numeric IDs and I tried using just the numeric id as well as the fully qualified name to get the purchase info. But in both cases I get invalid products.

    I'm at a loss on what else could be going wrong.

  • mugunthkumar

    Are you specifying the “fully qualified product name in iTunes connect”? Just numeric ids in iTunes connect will give this trouble…

  • idev

    I tried both, a fully qualified name and just numeric ids. They both give out errors.

  • Michael

    Hi

    What state does your app have to be in. My app was rejected so l had to add in app purchase. Do l have to resubmit the app?

  • Andy

    Hi,

    Thank you so much for posting this tutorial. Great stuff!

    I am developing an app for a charity, and want users to enter the amount they want to donate through in-app donate button. So there is no product id, is there a way to give the transaction a unique id and then assign to it the amount the user enters? Or any better suggestions to get this done? P.S. I assume every donation would be a consumable purchase.

    • mugunthkumar

      u still need a product id. And yeah, as you assumed, every donation would be a consumable purchase. You however cannot allow the user to enter a amount he is willing to donate. The workaround is to create multiple product ids, like
      Donate $1
      Donate $2
      Donate $5 etc., to whatever you feel fit…

  • Gert-Jan

    Hi there,

    Thanks for sharing knowledge :)

    Hope someone is able to help me out on the following scenario;

    In the app we're developing there is a possibility (let's say 1% chance) we cannot deliver a certain (paid) service. The problem is that we cannot check (at least not for free) upfront if the service will be available.

    A solution would be to:
    1) 'charge' the creditcard, assuming the service will be available
    2a) if delivery cannot be made (unavailable service); rollback the 'charge' of the creditcard (undo it) and inform user the charge was cancelled and apologize
    2b) else deliver service, and 'commit' the charge

    Is there a way to do a 'rollback' of the transaction after the charge is made? (or are there any workaround you can think of?).

  • BOTH readme.txt files fail to mention that besides the listed calls:

    [MKStoreManager sharedManager];

    [[MKStoreManager sharedManager] buyFeatureA];

    if ([MKStoreManager featureAPurchased] { …}

    one also has to include the code from step "Step 4: Adding a Transaction Observer":

    MKStoreObserver *observer = [[MKStoreObserver alloc] init];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];

    • Never mind. I did find the initialization of the observer in sharedManager.

  • I think that if your delegate responds to the selector "productPurchased", you'll get called as:

    [_delegate producPurchased:productIIdentifier];

    and can then the notified as a purchase "event", not by polling featureAPurchased.

  • Hi! Thanks for the tutorial!

    I have implemented it in my new game and it's running ok with iphone's but with ipod's don't work.
    In the ipod i can't read the price of my levels. Any idea?

  • MK, there is a problem with the code. You have featureAPurchased as BOTH the name of a method and the name of a BOOL variable. That seems to screw up the generated code. I changed the name of the BOOL variable to featureAPurchasedFlag (and did the same for featureBPurchasedFlag) and now the code works.

  • MK, there is a problem with the code. You have featureAPurchased as BOTH the name of a method and the name of a BOOL variable. That seems to screw up the generated code. I changed the name of the BOOL variable to featureAPurchasedFlag (and did the same for featureBPurchasedFlag) and now the code works.

    • Rajivratna

      Its not a problem. Objective C supports method name and instance variable name to be same :-)

  • Wayne Lo

    Hi Mugunth,

    Great thanks for the tutorial and example code. Apple should have hire you to help them clean up this part. I have a question regarding releasing objects in MKStoreManager. Since you do not release the purchasableObjects in either relase or autorelease function, how do you release this object?

    Thanks,

    Wayne

  • Another suggestion: flush the new UserDefaultSettings:
    ….
    [userDefaults setBool:upgradeAPurchasedFlag forKey:upgradeAId];
    // Writes the modifications to the persistent domains to disk
    [[NSUserDefaults standardUserDefaults] synchronize];

  • Amit

    Hi Mugunth,

    Great thanks for the tutorial and example code. First time I am working on this stuff,I have read your documentation but I am having one question that is it necessary we have to register each product or element ,If I am having same kind of product like music and having the same value(Cost) for all and I have to change these files dynamically..Is it necessary that I have to register each music file for In App Purchase. Is there any way to handle this dynamically.

    Please send me the process how can I handle this…

    Thanks in Advance…. please reply ASAP…

    Amit

  • John Kern

    Generic question: How secure is to store user purchases in the 'NSUserDefaults'? I guess that anyone with access to the iPhone directory can modify this settings and can get a new feature unlocked. Should we use special encrypted files to store purchases?

    • Gaurav Agarwal

      Yes as per my view. I am encrypting the purchase information using AES and storing in Document directory. When I require to read/modify it, I decrypt that in memory and read/modify again, again encrypt and save onto disk.

  • Ashish

    HI,
    Can u please tell me the meaning of line in step 2 i.e "If your app is not yet live, you can create a dummy, placeholder application, fill in the metadata (which you can anyway change it later) and check “upload binary later” " as i am unable to understand how to create a placeholder application.

    Thanks
    Ashish

  • abc

    how to create product references ?
    i mean i have a product "A", still not uploadded in app store,
    i have 3 features in it, say x.y.z
    now the prize for each one is different how inividually buy this features..?

    • Gaurav Agarwal

      Lets say your identifier for "A" is "com.yourcompanyname.A"

      So for x, y and z you need to create three more products in itunes account for in app purchase with following identifiers:
      com.yourcompanyname.A.x, com.yourcompanyname.A.y and com.yourcompanyname.A.z. And set the pricing for each sub product what you want.

      • abc

        do i need to add 3 more builds in to app store for it or one will work (it shud work with one only lo)

  • abc

    Ya finally got working..
    but what are the possible messages that can come from appstore
    can i have list of such messages with proper documentation from apple ?
    Nice work..!!!

  • Pingback: In-App Purchasing at Under The Bridge()

  • JohnCST

    Thanks for the very helpful tutorial. The code is easy to implement and use. I'm having an issue I haven't seen mentioned. When I try to buy a product in the sandbox, I get the normal buy verification alert with the correct product ID and price. When I press BUY, I get another alert saying that "Your account info has changed. Tap Continue to sign in, then review and confirm the changes. [Environment: Sandbox]" If you press Cancel, control is returned to my code and I can press another selection or exit. If I press Continue, my code exits (return code 0, it is not a "crash"), but the store loops asking for my password, then presenting the "account info has changed" alert. Any ideas?

    • mugunthkumar

      you should create a test user account, signout of the appstore on your test device, use the test user account to test the in-app purchases. This is explained in Step 4 of “Preparing the iTunes Connect” section.Thanks,MK

      • JohnCST

        Thanks for your reply. I did read your explanation, and I did create an in-app purchase test user. I insured I was not logged into any Apple Web site. On the device, in Setup I configured the Store info to use the test user. I entered a valid credit card, address, etc., and was able to log into the iTunes store. Should I not enter credit card info and leave Apple's address? Thanks in advance for your suggestions.

        • SpinalJack

          If you log in with your test account anywhere except when prompted in your app it'll invalidate your test account.

  • Just wonder if any developer is at the similar stage. I'm planning to make a transition an existing paid app to free version with "In App Purchase". For example, the original one is version 1.0 and it's a paid app. After updating to version 2.0, it will be free + "In App Purchase". What I concerned is that will the original version 1.0 buyers be charged again because this kind of transition?

    • mugunthkumar

      It's not easy to do this. But you can use the work around i explained in this blog post.

      http://blog.mugunthkumar.com/coding/iphone-tutori

      Release a minor update to your "paid" app and collect all Device IDs in your server. Use MKStoreKit v2.0 and setup your server with the sql scripts given. Set the ownServer property to your server location. MKStorekit will automatically ping your server to check your device id tables to check if the user has already purchased the paid app and enables the in-app for free.

      Or come back to my blog by this weekend. I'm actually writing a tutorial on this right now :)

      • Wow!! Thanks mugunthkumar!! I can't wait and look forward it!!

  • Terry G.

    Hey MK,

    THANK YOU for the code and your help! I am experiencing an odd behavior though.
    I have 3 possible purchases. I tested all three, and the sandbox reported the transactions went through perfectly.
    I then wanted to test to make sure the various alternate code paths and errors would be handled. I started to purchase item #3, and then canceled it.
    Now, I can still purchase 1 and 2, but if I try to purchase 3 I get SKErrorPaymentInvalid

    Any suggestions about how to debug this? I'm thinking of adding some NSLOG code at app launch to see if there are incomplete transactions in the queue.

    Again, thanks for your tutorial and code!

    Terry

    • Terry

      Strangely enough I haven't been able to duplicate this behavior for either items #1 or #2. This leads me to suspect that it isn't something with your code, but I'm still stumped.

      The only difference in calls between purchases for items 1,2, and 3 is the purchase app id. And I checked that the IDs are ok. And that all three have the same status in iTunes connect!?

      I cleared out the store credentials on my test device, and if I try to purchase 1 or 2, I get the "Sign In" screen. But not for #3, it immediately kicks back with the error, it doesn't ask for a login.

      Argh!

      • Terry

        I think I may have solved it. Item #3 is a LARGE purchase (tier 84 — $899.99) which I don't really expect to be purchased (for real) and I think there may be a daily limit on the amount that can be bought, even in the sandbox!

        I will go make a new test account (thanks for the suggestion), which may let me verify this. On the other hand, it may be per-device and not per-account.

        • mugunthkumar

          Check whether the in-app purchase is printed in the nslog statements in didReceiveResponse of MKStoreManager.Un-comment the line (or add lines) to print invalidproductidentifiers returned in the same delegate. If your 3rd purchase is being returned as a invalid product, check your itunes connect settings.

  • mugunthkumar

    try with a different user account or sign off and sign in again…

  • sanket

    hi,i am using inAppPurchase functionality in my Project.
    after completing the transactions i want to navigate to another class(xib)
    but on completetransaction UI replated code is not working.

    have any one faced same problem?

    • mugunthkumar

      Did u set the delegate properly?Sent from my iPhone

      • sanket

        yes, i have et the delegate properly.

  • sanket

    My requirement are as follows

    After CompleteTransaction method i want to navigate to another class(xib).
    or atleast i want to change title of navigation bar.
    but after completeTransaction method UI replated code is not working.

    kindly help me or give some pointers.

  • Excellent post. If you wish to listen to BBC Radio on the iPhone – I strongly recommend BBC Radio iPhone Streams – it is easily the best way to listen to BBC Radio on the iPhone, iPad or iPod Touch and it’s free at BBCStreams.com.

  • Great post. If you are looking to listen to BBC Radio on the iPhone – I strongly recommend BBC Streams – it is easily the quickest and most reliable way to listen to BBC Radio on the iPhone, iPad or iPod Touch and it’s free at BBCStreams.com.

  • Anish

    Hi,
    Great Code.. thanks.. but
    I am trying to implementing in app purchase in my app. i followed iPhone dev doc to create product id. I have created a test account for sandbox testing of in-app purchases. I am following these steps to test
    Logged out of any existing accounts
    Open app and start getting list of products available
    Start process to download a product
    static NSString *featureAId = @"com.oaas.awed.product";

    [MKStoreManager sharedManager] ; //called in view did load
    [[MKStoreManager sharedManager] buyFeatureA];

    NSLog:Payment invalid (3), product: com.oaas.awed.product

    Now ideally I should be asked for the user name/pwd at this point. But I am not prompted for anything and got the error SKErrorPaymentInvalid from the observer.

    Thanks
    Anish

  • rajivratnasingh

    Hi,

    I successfully received response form App Store. But it returns me no products. Hence i cannot proceed.
    Can anyone please help me in getting the possible reasons?

    Thanks,

    • mugunthkumar

      you have to check your configurations on iTunes connect.Check if your in-app purchases are marked “Cleared for sale”.In 99% of the case, problem will be in your iTunes connect configuration

      • Rajiv

        ok Thanks a lot, I'll check it and update this thread as soon as possible.

        • Rajiv

          Hello Mr. Mugunth,

          I have successfully used your StoreKit in my application. Thanks a lot for your effort.

          However, I am facing a minor problem. Sometimes, after i get the response from the App Store, i also enter the can make payment block… but the transaction fails due to “OTHER ERROR”. I would like to know what this other errors are and when they arise.

          Also, what difference would that make if we invoke buyFeature before getting product response from the App Store?

          PS: Your code is copyright in my application 😀

          • Invoking buy before the requestproductdata responds doesn't have any issues if your products are valid. Except that it takes a loooong time to show the in app purchase dialog. If u request a product you don't sell yet, it behaves as a no-op. I'm not sure about the "OTHER ERROR" till I debug your app.

          • mugunthkumar

            Invoking buy before the requestproductdata responds doesn't have any issues if your products are valid. Except that it takes a loooong time to show the in app purchase dialog. If u request a product you don't sell yet, it behaves as a no-op. I'm not sure about the "OTHER ERROR" till I debug your app.

    • mugunthkumar

      you have to check your configurations on iTunes connect.Check if your in-app purchases are marked “Cleared for sale”.In 99% of the case, problem will be in your iTunes connect configuration

  • Walsh

    Thanks a lot for this mate. Really appreciate it. Have been contracted to make an app and this functionality is essential.

  • Rajiv

    Hello Mr. Mugunth,

    I have successfully used your StoreKit in my application. Thanks a lot for your effort.

    However, I am facing a minor problem. Sometimes, after i get the response from the App Store, i also enter the can make payment block… but the transaction fails due to "OTHER ERROR". I would like to know what this other errors are and when they arise.

    Also, what difference would that make if we invoke buyFeature before getting product response from the App Store?

    PS: Your code is copyright in my application 😀

  • Matthijs

    Just wanted to say thanks for a great tutorial!

    • Ajay Singh Azad

      Is ios paid applications contract compulsory to sign for testing in app?

      • MugunthKumar

        Yes.

  • Justin GOren

    if a user tries to buy the in app purchase from a different device using the same account info, will it let them, and where in the code does this. I need in app purchase up quick so please answer ASAP

    btw, im talking about the source code you gave MKStoreKit

  • Justin Goren

    I've got a problem with it. Ive linked a buton up to [[MKStoreManager sharedManager] buyFeatureA]; but it fails. Ive used various nslogs and heres what it does:

    1. goes to the buyfeatureA method
    2. goes to buyFeature
    3. checks the if statements
    – it goes to the canCurrentDeviceUseFeature, but instantly returns No because ownServer = nil because it was set to equal nil at the top of the page so it doesnt go to the rest of the code ( i think the problem might have to do with something here

    – at the if statement of ([SKPaymentQueue canMakePayments]) , it can and adds the payment then goes over to the MKStoreObserver i think. It ends up that i dont get an alert or anything, but when i put an NSLog in the failedTransaction thing, it gets called. Please Help :(

    • mugunthkumar

      Ur in app purchases aren't configured properly.Sent from my iPad

  • David

    HI there,

    Ive got some of this to work, but im having a problem.

    when the updatedTransactions gets called after a little wait from after ive purchased a product.

    it then loops through the transactions. which i have NSLog’ed. but this causes it to crash at this point in my code. im not sure why as im sure every transaction would have a valid state??.

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

    {

    for (SKPaymentTransaction *transaction in transactions)

    {

    NSLog(@”StoreManager Transation State %@”,transaction.transactionState); // crashes right here…??????

    switch (transaction.transactionState)

    {

    case SKPaymentTransactionStatePurchased:

    [self completeTransaction:transaction];

    break;

    case SKPaymentTransactionStateFailed:

    [self failedTransaction:transaction];

    break;

    case SKPaymentTransactionStateRestored:

    [self restoreTransaction:transaction];

    default:

    break;

    }

    }

    }

  • I am having problems getting this to work…. I keep getting "Transaction Failed" message. Any suggestions?

  • Can you post a working "Nothing but a button" Xcode project with this code working in it? Wow… I REALLY AM ASKING AND BEGGING! I've been unsuccessfully trying to get IAP working for one of my apps for over 2 weeks now!. Please anyone?