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.

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

If you enjoyed this post, make sure you subscribe to my RSS feed!

Related posts:

  1. In App Purchases: Troubleshooting Code=0 “Cannot connect to iTunes Store” (SKErrorUnknown) When using MKStoreKit or any other equivalent framework or regular...

  • Pingback: uberVU - social comments

  • Steve

    Please enable the source. your tut is incomplete.

    • mugunthkumar

      The source is up!

      • Vijay shankar

        Hi Mugunth Kumar,

        Thanks for your wonderful tutorial for in app purchases.

        We created an app in the developer portal as ‘Main app’ with the product id as com.companyname.mainapp and also two inapp purchase apps as ‘Sub app1′, ‘Sub app2′ with the product identifiers as com.companyname.mainapp.subapp1 and com.companyname.mainapp.subapp2.
        We wrote the code to handle the payment and once the transaction state reaches SKPaymentTransactionStatePurchased.
        It is the responsibility of the apple to download and install the Sub app1 in the device? or do we need to write a code to install the sub app1 or sub app2 in the device.
        Note: We use the in app store inside the main app ie we have sub app1, sub app2 with the buy now button.
        Another question : After we purchase the Sub app1. Do we need to show as buy now inside the Main app for Sub app1 or show the status or button as Installed?

        Thanks,
        Vijay Shankar

      • Shaival Mehta

        How to handle Non Consumable In app Purchase?Like if its already purchased then how to avoid next ask for purchase?

        • Jack

          I handles this in an app I am developing for a client. If you try to repurchase from the app store apple will tell you that you already own the product and will download it again for you at no cost.  Unfortunately you still may have the problem of having a buy now button that will confuse the end user.  I solved this by creating a database where I stored each purchased product by product ID and user (currently UUID) but may need to change this due to apple’s plan to restrict this.  When an end user goes to a buy page I check to see if the person already purchased the product and if so I change the buy button to a reload button and skip the app store entirely.  This keeps the user from having to re-log into the app store.  I then handle the reload from my own back-end server.

  • Pingback: In App Purchase now available for free apps - The iPhone Blog Forums

  • nottoday

    Really thanks for that.
    Regards,
    nottoday

  • Sam

    Hi ,
    Thanks for wonderful explanation. As per Step mentioned above:

    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.

    As my app is still underdevelopment so how can i create the the product IDs for testing the code.

    Thanks
    Sam

    • mugunthkumar

      You can actually create a dummy “placeholder” app in iTunesConnect and don't upload binary. Then create product id for that app. Thanks for your question, I'll update the blog post accordingly.Sent from my iPhone

  • http://gamesbox.tv Nader Rahimizad

    In [MKStoreManager sharedStorageManager]; where is sharedStorageManager defined?

    • mugunthkumar

      I'm sorry, it should be [MKStoreManager sharedManager]; Thanks for pointing it out.

      • SpinalJack

        Think about changing the main post because I was stumped by this for a long time too

        • GenCode

          so was I but I really am happy u made this, when u have time can u change the main post

  • johnny_g

    Hi,
    Thanks for the post really coverts the basics to get started.

    I am just a little confused when in comes to testing this out. "…Open the Store UI and initiate the purchase…" do I do this on the device or on with iTunes? ( not sure how the test account that I created comes into effect) if you could maybe provide a little more screenshots that would he me out a bunch.

    • mugunthkumar

      You have to try this on the device. Store UI is the UI within your own App. Try an "in-app" purchase from your app and see the sequence of steps.

  • Iain

    Hi, Itried this code but just get this error and app crashes:
    'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'

    Any ideas?

    Regards iain.

  • Iain

    Sorry, it crashes when it hits this line:
    [[SKPaymentQueue defaultQueue] addPayment:payment];

    • mugunthkumar

      Are you running this from your device? It *will* crash on simulator.

    • faisal

      did you get all of your product id in 'response of product id request'

  • Iain

    No, I am running it from the device.

  • mugunthkumar

    Is your payment object nil?

    • http://www.highvoltagehotrods.com Andrew M

      I am getting the same error at the same point.
      NSLog(@"buyFeature-canMakePayments: %@ : %@", featureId, payment);
      [[SKPaymentQueue defaultQueue] addPayment:payment];
      NSLog outputs :
      com.rockvideomobile.mobiRockVideo : <SKPayment: 0x1a0d00>
      then
      *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'

    • http://www.paulstimesink.com Paul Westbrook

      The problem is that finishTransaction will assert when attempting to finish a transaction that is in the SKPaymentTransactionStatePurchasing state. I fixed my problem by not calling finish transaction in this case

  • Jason

    That's a great tutorial for In App Purchase. Just a little question regard [MKStoreManager sharedStorageManager].

    I read the readme file, and it guide me to put [MKStoreManager sharedStorageManager] at applicationDidFinishLaunching.

    My question is "If I'm using a TarBarApplication, and I have many ViewController. I want to call [[MKStoreManager sharedManager] buyFeatureA] from one of the ViewController (i.e. FoodViewController.m) not from AppDelegate. Shall I put [MKStoreManager sharedStorageManager] at applicationDidFinishLaunching or put it at viewDidLoad of the FoodViewController.m.

    Thanks

    • mugunthkumar

      [[MKStoreManager sharedManager] buyFeatureA] this can be called from any view controller. But when the app starts you should initialize the class by calling [MKStoreManager sharedManager]. (You can assign the shareManager to some variable or leave it like that) That function call will ensure that purchases restored from a interrupted sessions come to your app, when the user opens it again. Like, when the user attempts a purchase, and it's interrupted by a phone call, you will receive notifications only when shareManager is not nil. And only with those notifications can you update your internal variables that keep track of the purchase statuses. When you put it ONLY in FoodViewController.m, you will not be notified of restored purchases till the user actually opens FoodViewController.

      Hope that helps :)

  • Jason

    OK, thanks for your clear guide. In this same case, if I put [MKStoreManager sharedManager] at applicationDidFinishLaunching. And I want to call [[MKStoreManager sharedManager] buyFeatureA] from FoodViewController.m, shall I call [MKStoreManager sharedManager] again at FoodViewController.m?

    • mugunthkumar

      you don't have to.You have to call [MKStoreManager sharedManager] only once. Even if you call multiple times, it returns the same instance.Actually MKStoreManager is a singleton class.In your foodviewcontroller.m, you can just call [[MKStoreManager sharedManager] buyFeatureA] directly. Just change buyFeatureA to be something more meaningful and avoid calling buyFeature:(NSString*) productId directly.The reason why I say this is, if you call buyFeature directly, you will be littering your product id's through out the code. My idea is to collectively have all product ids into the singleton manager class MKStoreManager

      • Jason

        Clear!! Understand you design concept.
        Thanks for the great work.

  • http://www.highvoltagehotrods.com Andrew M

    Thanks for your post. I'm new to iPhone dev and the StoreKit portion of my app is that last part I am dealing with.
    I setup your code but am getting the following error when I do a build.

    Check dependencies
    [WARN]Warning: Multiple build commands for output file mobirock.app/Settings.bundle

    ! link
    "_OBJC_CLASS_$_SKPaymentQueue", referenced from:
    __objc_classrefs__DATA@0 in MKStoreManager.o
    __objc_classrefs__DATA@0 in MKStoreObserver.o
    "_OBJC_CLASS_$_SKProductsRequest", referenced from:
    __objc_classrefs__DATA@0 in subscribe.o
    __objc_classrefs__DATA@0 in MKStoreManager.o
    "_OBJC_CLASS_$_SKPayment", referenced from:
    __objc_classrefs__DATA@0 in MKStoreManager.o

    What am I missing?

    • mugunthkumar

      You have to link StoreKit.Framework in your app and then build it :) Sent from my iPhone

      • http://www.highvoltagehotrods.com Andrew M

        OK, sorry. Dumb newby question. Isn't that what #import <StoreKit/StoreKit.h> does? Do I need to "link" somewhere else in xCode?

        • mugunthkumar

          Yes,You have to add StoreKit.Framework to your project. The line #import will show an error if you haven't added the framework.

          • http://www.highvoltagehotrods.com Andrew M

            Sorry, another dumb question. Where do you add StoreKit.Framework? I have searched all over xcode. I found frameworks listed but thats not one of them. I have the latest xCode. Do I need to download it from somewhere?? Did a search in the iPhone Dev center and it revealed nothing. I am using the 3.1.2 iphone SDK.

            I am an experienced javaScript, Flash ActionScript and PHP developer and new to the xCode.

    • shishir.bobby

      u missing storekit framwork man,…..just add it and u r done

  • Faisal Rahman

    I am building an application, which shows a couple of picture from my server.

    user can purchase a ‘pixel area’ to upload there photo, replacing the existing picture in my application.

    i am stuck @ a point, “how this purchasing will be done”.

    plz tell me a stepwise direction.

  • http://www.highvoltagehotrods.com Andrew M

    Thanks. Opened up the list but Storekit isn't in there. Do I need to download it from somewhere? Doesn't it come with the SDK?

    • mugunthkumar

      It comes with the SDK. Are you on the right folder? Your folder path should show you which SDK you are browsing in. Even if you have the latest XCode, chances are that, you might be opening a 2.x SDK folder (which will not be removed by installing the latest XCode). Look into the correct folder. I'm not on my mac now. I can't tell exactly which folder it will be present :(

      • http://www.highvoltagehotrods.com Andrew M

        Thanks. Found it. Had to click other and then really search. Was really buried deep.

  • Jason

    I have done the preparation, such as define a special AppID, a specific Developer provision for this In App Purchase application, define my In App Purchase Item at iTunes Connect, include StoreKit to framework, import storeKit.

    While I test my In App Purchase, my program get SIGABRT, and after trace the program, the program crash at
    MKStoreManager.m line 139,
    [[SKPaymentQueue defaultQueue] addPayment:payment];

    The debugger console show: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'

    I only change MKStoreManager.m at line 18 to
    static NSString *featureAId = @"com.mycompany.FoodSearch.enableSendLocation";

    Looks there was a same post similar to my issue.

    • mugunthkumar

      the name com.mycompany.FoodSearch.enableSendLocation should be exactly same as your AppId.can you post your App ID?Is it com.mycompany or com.XXX (In my code, mycompany is only a placeholder)If this name is not EXACTLY same you will get a SIGABRT error.

      • Jason

        I have define a dummy application and set two In App Purchase feature rpoducts, the first feature ID is "enableSendLocation", the other one is "enableSaveLocation".

        These two ID are well defined except approved because the guide mention that "When you have tested your in app purchase and are ready to approve it and submit it for review, upload a screenshot below. This is for review purposes only and will not be displayed in the Store."

        Since my App ID is M93JNT2TV4.com.jasoncom.FoodSearch,
        So I define static NSString *featureAId = @"com.jasoncom.FoodSearch.enableSendLocation";

        • mugunthkumar

          I think your bundle id in your info.plist doesn't match “com.jasoncom.FoodSearch”. Though I'm not sure, do try this and let me know. If it works, I'll update my blog. (You can update bundle id by wading through some project properties window or by editing the plist)Thanks :)

        • mugunthkumar

          I think your bundle id in your info.plist doesn't match “com.jasoncom.FoodSearch”. Though I'm not sure, do try this and let me know. If it works, I'll update my blog. (You can update bundle id by wading through some project properties window or by editing the plist)Thanks :)

          • Jason

            My bundle id formally is "com.jasoncom.${PRODUCT_NAME:rfc1034identifier}".
            After change it to "com.jasoncom.FoodSearch", the result still same, SIGABRT.
            Ha Ha. My current environment/config work for my other applications, but just In App Purchase didn't work.
            Am I missing something at configuration?

          • Jason

            Shall I add any delegate to @interface {} ?

          • mugunthkumar

            one last question., can you place a breakpoint on the line that crashes and let me know what your SKPayment object is? Is it nil?

        • http://twitter.com/marcopapa @marcopapa

          When I was going through iTunes Connect setup, it specifically said that the "Product ID" needs to be "numeric", not "alphanumeric". Could that be your problem?

  • Jason

    Sure, The SKPayment *payment is "<Variable" , very strange.
    Below are console log.

    2009-10-21 16:36:40.227 FoodSearch[1643:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
    2009-10-21 16:36:40.236 FoodSearch[1643:207] Stack: (
    844776241,
    843056877,
    844331917,
    844331819,
    849152651,
    208313,
    849150721,
    844491911,
    849157277,
    849151455,
    849153177,
    206801,
    206937,
    191677,
    844537573,
    851058789,
    851058693,
    851058647,
    851057969,
    851060293,
    851056221,
    851054649,
    851040559,
    851039143,
    848378745,
    844528685,
    844526429,
    848374975,
    848375147,
    850798447,
    850793587,
    11663,
    11596
    )
    terminate called after throwing an instance of 'NSException'
    Program received signal: “SIGABRT”.
    kill
    quit

    • mugunthkumar

      run the app in debug mode. Otherwise every variable is shown like “<Variable”. Try NSLog([payment description]);

      • Jason

        It show <SKPayment: 0x14cd60>, is it what you need?

        • mugunthkumar

          Yes,which means SKPayment is not nil. what does NSLog([SKPayment description]) tell? (Email me, my email in CC)

          • Jason

            When I continue trace the program, the SIGABRT finally happend at
            line 34 of MKStoreObserver.m
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

            And the error message is Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'

            Any advise?

          • Mmqaz

            finishTransaction:
            Completes a pending transaction.

            - (void)finishTransaction:(SKPaymentTransaction *)transaction

            Discussion
            Your application should call this method from a transaction observer that received a notification from the payment queue. Calling finishTransaction: on a transaction removes it from the queue. Your application should call finishTransaction: only after it has successfully processed the transaction and unlocked the functionality purchased by the user.

            Calling finishTransaction: on a transaction that is in the SKPaymentTransactionStatePurchasing state throws an exception.

  • Obliviux

    My app to give me "SIGABRT"…i don't know what is wrong…

    ..if everyone can leave the source code of an Application that already use the In-App Purchase will be my hero!! :)

    • mugunthkumar

      this source code works in my app… :(

  • Ramakant Kulkarni

    After 15 long minutes of debugging, found the answer to the above errors that folks are getting.

    If you read the documentation of the SKPaymentQueue class, especially the finishTransaction method, it clearly says "Calling finishTransaction: on a transaction that is in the SKPaymentTransactionStatePurchasing state will throw an exception.". Hence, I made the below change to the switch statement in the MKStoreObserver class to move the finishTransaction method call only if the transaction is Purchased or StateRestored. I must say this is a well written API and masks a lot of complexity around In-app purchase. Thanks to the author.

    The switch statement (in file MKStoreObserver.m) now looks like:

    switch (transaction.transactionState)
    {
    case SKPaymentTransactionStatePurchased:
    [[MKStoreManager sharedManager] provideContent: transaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    break;
    case SKPaymentTransactionStateFailed:
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
    [[MKStoreManager sharedManager] failedTransaction: transaction];
    }
    break;
    case SKPaymentTransactionStateRestored:
    [[MKStoreManager sharedManager] provideContent: transaction.originalTransaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    default:
    break;
    }

    • http://www.highvoltagehotrods.com Andrew M

      I tried putting this code in but am still getting the same error.

      *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'

      • http://twitter.com/jaderfeijo Jader Feijo

        I am having the exact same problem, console outputs:
        CoreAnimation: ignoring exception: Cannot finish a purchasing transaction

        Wondering if you ever found a solution to this…

    • mugunthkumar

      Thanks for the pointers… Made some quick and dirty changes to my code before publishing it and that killed the whole API :(
      Code updated. Please download the new code and do let me know if it works

  • Obliviux

    This is the result of my debugger:

    #1 0x32a229a2 in kill

    #2 0x32a22994 in raise

    #3 0x32a37640 in abort

    #4 0x32c6b3b6 in __gnu_cxx::__verbose_terminate_handler

    #5 0x3031185e in _objc_terminate

    #6 0x32c6977c in __cxxabiv1::__terminate

    #7 0x32c697d0 in std::terminate

    #8 0x32c6989c in __cxa_throw

    #9 0x3031071a in objc_exception_throw

    #10 0x32b88b8c in +[NSException raise:format:arguments:]

    #11 0x32b88b2a in +[NSException raise:format:]

    #12 0x3068ea8a in -[SKPaymentQueue finishTransaction:]

    #13 0x000053a6 in -[MKStoreObserver paymentQueue:updatedTransactions:] at MKStoreObserver.m:36

    #14 0x3068e300 in __NotifyObserverAboutChanges

    #15 0x32bafc86 in CFArrayApplyFunction

    #16 0x3068fc9c in -[SKPaymentQueue _notifyObserversAboutChanges:]

    #17 0x3068e5de in -[SKPaymentQueue _addLocalTransactionForPayment:]

    #18 0x3068ec98 in -[SKPaymentQueue addPayment:]

    Anyone have ideas why I give SIGABRT error?

  • Obliviux

    I have a question:

    I have an application in AppStore, with bundleID com.mycompany.imath

    Now i have added to this app, with iTunes Connect, the relative In App Purchase, with ID 0001.

    But when I go to modify my AppID to enable In’App Purchase, it give to me “Unavailable”.

    So i have created a new AppID: com.mycompany.imath2 and I have enabled the In-App Purchase.

    In the xcode application, I work with the new AppID (imath2) but in iTunes Connect the In App Purchase is registered only for the old AppID…

    It can work anyway??

    How I can do? If I create a new App in iTunes Connect I can’t publish the update for my App!! :(

  • mugunthkumar

    Code updated. Please download the new code and do let me know if it works

    • http://twitter.com/marcopapa @marcopapa

      I am not sure what you mean by "code updated". BOTH version 1 and version 2 of MKStoreObserver's failedTransaction: still call finishTransaction:.

      failedTransaction should be changed as follows:

      - (void) failedTransaction: (SKPaymentTransaction *)transaction
      {
      if (transaction.error.code != SKErrorPaymentCancelled)
      {
      // Display an error here.
      [[MKStoreManager sharedManager] failedTransaction: transaction];// fix to bug below
      }
      //[[SKPaymentQueue defaultQueue] finishTransaction: transaction];BUG!!!! need to be removed, see posting
      }

  • http://www.highvoltagehotrods.com Andrew M

    I am not getting any errors, but it also doesn't appear to be hitting iTunes.

    Going to trace through and see what is going on.

  • mugunthkumar

    Did u log out of AppStore on ur phone before trying it?Sent from my iPhone

  • http://www.highvoltagehotrods.com Andrew M

    Yes. It is running through your code and triggering – (void) failedTransaction:

  • http://www.highvoltagehotrods.com Andrew M

    Yes. I am logged out.
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
    // Optionally, display an error here.
    NSLog(@"failedTransaction- Transaction Failed %@",transaction.error.code);
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

    This added NSLog is firing with
    failedTransaction- Transaction Failed (null)

    • http://twitter.com/marcopapa @marcopapa

      transaction.error.code is an NSInteger not an NSString.

  • mugunthkumar

    I fixed the bug today… Please pick up the latest code. If you fail to call finishtransaction, it will continue to ping apple servers and they may block u out (ddos)Sent from my iPhone

  • http://www.highvoltagehotrods.com AndrewMcc

    I am still trying to get this new version to work without sucess. It keeps returning a failed transaction.

    - (void) failedTransaction: (SKPaymentTransaction *)transaction
    {
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
    // Optionally, display an error here.
    NSLog(@"failedTransaction- Transaction Failed %@",transaction.error.code);
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    }
    in "- (void) buyFeature:(NSString*) featureId"
    featureId is equal to com.rockvideomobile.mobiRockVideo.mobirock_30day

    I can see my two in app products in iTunes Connect. Is it possible I am missing something there and they aren't setup correct?

    I did sign out of the App Store in Settings as well and do have a sandbox user setup. But I never even get a message from iTunes.

    Help. This is the last part I need to get working and then I can call this project done.

  • mugunthkumar

    did u try NSLogging your transaction that is passed to "failedTransaction"? I updated the code yesterday and people have reported that their's work properly now. Reply with your NSLog of transaction object.

  • http://www.highvoltagehotrods.com AndrewMcc

    transaction.error.code=NULL

    transaction = <SKPaymentTransaction: 0x1ba0a0>

  • mugunthkumar

    Aww.. tricky… What's NSLog([transaction.error description]); ?

    • http://www.highvoltagehotrods.com AndrewMcc

      Error Domain=SKErrorDomain Code=0 UserInfo=0x1c5b20 "Cannot connect to iTunes Store"

  • http://www.highvoltagehotrods.com AndrewMcc

    I have wifi and 3G. The app is completely fed by the net and all the data is populating.

  • mugunthkumar

    Are you behind a proxy that blocks iTunes?

    • http://www.highvoltagehotrods.com AndrewMcc

      Nope. Just tried turning off wifi just in case. 3G connected to AT&T should have worked. I have downloaded a lot of apps before from App Store and songs from iTunes.

  • Ramakant Kulkarni

    Hi Mugunth Kumar,

    I see a need to register a call-back with the Observer. Not sure how it's usually done, but when the observer receives a transaction notification (processed/failed…), an action needs to be taken to flip a flag within your application and/or save to properties file. I was wondering, without any reference to objects outside the MKStoreManager & MKStoreObserver, how would one achieve the same? The easy option is to add a reference into the StoreManager or StoreObserver and trigger a method on it. That, to me, would be an unclean approach. Will be nice if that part of the code is generic too and upon initialization of the StoreManager, the source program can register the object and method name in certain way, so that folks don't have to make any source code changes to the API. This intent here is to make the API generic. Also, the hard coded feature names/identifiers in file MKStoreManager, could be part of a properties file. I know all of this takes time, but again, my sole intent was to make the API generic.

    Regards,
    Ramakant

    • mugunthkumar

      From ur app u don't have to register any callback. U only have to instantiate the mkstoremanager object. The class automatically loads any product ids and checks if it has been purchased. It also “remembers” ur purchase back into the userdefaults. All those flipping a flag is automatically done for u. What u have to do is just set the product id properly. I didn't read it from the property file because, it's anyway developer who is going to add products. Adding a product and failing to add the corresponding access function will create versioning issues (hey my code worked yday, but crashes today).Sent from my iPhone

      • Ramakant Kulkarni

        Yes, I understand that part, where you convert the received notification into a "FeatureAPurchased" method call. But that converts the model from event driven to a spool. May be, my understanding is incorrect here, but now after submitting a request to buy a feature, I have to keep checking with the MKStoreManager to see if it has been purchased (as opposed to the event notification mechanism exposed by apple). I hope, I was able to explain the issue here. Having my ViewController as the Observer solves a lot of issues as I will have full control of the UI flow (needed for the UI of the in-app purchase store) and can steer it based on notifications.

        BTW, good news is, the code is still working for me :)

        • mugunthkumar

          Aww got it… I'll add a delegate to the kit and publish it probably my this weekend :) thanks for ur input!!!Sent from my iPhone

  • http://www.highvoltagehotrods.com AndrewMcc

    Thanks for the email. Below are the values of the properties.

    It looks like I'm not the only one with this problem.
    http://episteme.arstechnica.com/eve/forums/a/tpc/…
    The guys on this page describe similiar issue.

    Below is the output from the NSLog statements I added.

    2009-10-23 21:40:32.542 mobirock[5636:207] Buy Feature A Clicked
    2009-10-23 21:40:32.545 mobirock[5636:207] sharedManager Called
    2009-10-23 21:40:32.548 mobirock[5636:207] buyFeature Called com.rockvideomobile.mobiRockVideo.mobirock_30day
    2009-10-23 21:40:32.550 mobirock[5636:207] SKPaymentQueue canMakePayments is True
    2009-10-23 21:40:32.551 mobirock[5636:207] featureId=com.rockvideomobile.mobiRockVideo.mobirock_30day
    2009-10-23 21:40:35.581 mobirock[5636:207] SKPayment Transaction Failed
    2009-10-23 21:40:35.582 mobirock[5636:207] failedTransaction- transaction.error.code = (null)
    2009-10-23 21:40:35.585 mobirock[5636:207] transaction:<SKPaymentTransaction: 0×136480>
    2009-10-23 21:40:35.589 mobirock[5636:207] transaction.payment=<SKPayment: 0x11fe00>
    2009-10-23 21:40:35.592 mobirock[5636:207] transaction.transactionIdentifier=E13E2AA9-C486-499F-8882-854C3C9F6D74
    2009-10-23 21:40:35.596 mobirock[5636:207] transaction.transactionReceipt=(null)
    2009-10-23 21:40:35.599 mobirock[5636:207] transaction.transactionDate=(null)
    2009-10-23 21:40:35.603 mobirock[5636:207] Error Domain=SKErrorDomain Code=0 UserInfo=0×141580 "Cannot connect to iTunes Store"

    • mugunthkumar

      I sent u an email from a email id ending with @mk.sg (MKSG contact) u can send me code logs there instead :) Sent from my iPhone

  • Mina

    hi
    i get a bi problem, when i debugging code with iphone , it never come in to – (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response , do you know why?please help me

  • Dominik

    I did the Steps from your tutorial. I linkes a UITableCells didSelect Action with the method "buyFeatureA". I added the Observer, but nothing happens if I tap onto the Cell. I just added some NSLogs, the method – (void) buyFeature:(NSString*) featureId is getting triggered and the Log NSLog(@"buyFeature-canMakePayments: %@ : %@", featureId, payment);
    returns the following String: buyFeature-canMakePayments: com.*****.****.pack1 : <SKPayment: 0×180510>
    Can somebody help me please?

  • pooja

    This is a great tutorial but dont get how to start as there are 4 files but not any UI part.
    Pls tell how to start with UI.
    Or with screen shots.

    • mugunthkumar

      The UI should be developed by you. You can call the function to buyFeatureA or something like that from your code.

      • http://kmcgrady.posterous.com Kieran

        How do I call the requestProductData method from my view controller? I tried [class requestProductData] (with class being the instance variable pointing to the class) but it does not get called.

        • Rajiv

          Dont call requestProductData from anywhere. Just call [MKstoreManager sharedManager] method as it calls requestproductData.
          As far as view is concerned, u can create a method like -(IBAction)buyButton:(id)sender and this method should invoke [MKstoreManager sharedManager] . And finally link the buyButton method to a button in ur view.

  • prince

    Hi mugunth Kumar,

    I have used your code in my application.
    my app Id is : com.riseuplabs.apn
    my product id are: com.riseuplabs.apn.01 and com.riseuplabs.apn.02
    i have create a dummy application in my iTune connect, also it has enable for 'inApp purchase"

    i change the following line in your requestProductData method:

    SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
    [NSSet setWithObjects: @"com.riseuplabs.apn.01",@"com.riseuplabs.apn.02",@"01", nil]];

    but in response i got that all of my product id are invalid??
    plz reply, i am stuck with it since 2 days :-(
    ——–
    waiting for your reply
    prince

    • http://twitter.com/marcopapa @marcopapa

      Not sure what the @"01" is there for. shouldn't it be just:

      SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
      [NSSet setWithObjects: @"com.riseuplabs.apn.01",@"com.riseuplabs.apn.02", nil]];

  • fred

    AndrewMcc did you solve your problem. Got the same here and i'm stuck. It is rather frustrating because the exaclty same piece of code was working 2 weeks ago …
    Can it be linked to Apple decision to unlock in-app purchase for free apps ?

    • mugunthkumar

      Can both of you elaborate the problem? I got this code working in my app. Many have reported success. Some say it worked previously and now it doesn't. I want to isolate the issue and weed out bugs if any. :)

    • Ganesh Adiga

      you need to sign paid agreement contract with all your bank details etc.

      i was facing the same problem. got it resolved after doing this

  • Jack

    Hi, Thanks for this tutorial.
    I’ve submitted the app’s meta data to iTunes connect but have not uploaded my app yet. In iTunes Connect I go to Manage your Applications. The app appears there and I click Manage In App Purchases. My App Id shows up but there is no Bundle Id. I try to Create New but it needs a Bundle Id so I click the link “register here” and it takes me to the main iphone dev page. Any suggestions on how to get the bundle Id registered? I could upload my App (I think) but I’m afraid apple will begin reviewing it and I’m not ready for that yet.

    • anon

      i was able to associate a bundle id by uploading my binary and then immediately rejecting it. however, i have not gotten successful responses from my product request calls, so maybe this isn't the right way to do it…

      • http://twitter.com/marcopapa @marcopapa

        I did NOT upload any binary and successfully set up BOTH a Bundle ID and a Product ID.

  • Gene

    I have 3 apps that I can create a free version by the data contained in a plist. What then I would like to do is have the in app purchase just simply deliver the full plist contents, replacing the free version's plist file.

    Is this possible ? I am just trying to understand what the delivery product would be in this case. I understand the coding process of the store, but do not know if something as simple as a plist replacement could be accomplished.

    Thank you for any advice.

  • mugunthkumar

    U can't replace the apps plist within the bundle. To change anything that's “signed” you have to resubmit your app. But in your case, you can isolate those entities in the plist into a separate list and activate/deactivate features based on those the users choices. Thisnew list could contain feature names and a bool key stating whether it's purchased or not. You can toggle these values from the storekit.Sent from my iPhone

    • Gene

      Would then the deliverable, be simply a new app with the new feature enabled ? I am just trying to understand the physical deliverable.

      Thank you again,
      Gene

  • mugunthkumar

    the same app with the new feature enabled. not a new app. The physical deliverable should be streamed from your own server after checking the purchase.

    • Gene

      Ok, so I have a free app, I place inApp purchase within it. When they want to buy it will then get the full featured version of the app as an inApp purchase as defined in my Manage inApp purchases in iTunes Connect and will come from what I uploaded to the iTunes Connect in the inApp upload section. This full featured app will then replace the free version.

      Thank you for your time, I am just trying to understand the process of deliverables. The differences in the app that I plan to use this on, is simply a difference in the contents of a plist. The programming logic is identical. The free will have limited data and the full will have complete data.

  • Avi

    Thanks a lot, worked great! hope my app will be approved soon.

  • Abhishek trivedi

    Hi …I have followed all the steps discussed in your blog but i am getting no data in delegate method:-

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

    above method is getting called ,but no data is received from i tunnes connect.I tried to create the array from response using “NSArray *myProduct = response.products;” but in array nothing is coming.I am not finding any method to get even the reason of problem from response. What could be the problem and how to get more information from the SKProductsResponse about failure? .

    Thanks in advance …

    regards

    Abhishek.

  • navac

    Hi,

    Thanks for saving us hours of coding! works like a charm. But one strange thing is happening when i try to test the in app purchase:

    I created a test user account through the Manage User Accounts in iTunes Connect. When you create such an account you have to select a valid storefront for your account. I chose US Store. Now I signed out from the store in App Store settings on my device. Ran the application and tried to perform a purchase. I successfully login with my test account. After I press Confirm when entering my credentials I get an alert, that comes from SKPaymentTransactionStateFailed from the observer. It says "Your account is only valid for purchases in the US iTunes Store". The error state = 0 unknown. Second time when i try to perform the purchase, StoreKit only asks me for a password like the previous login was successful. After entering a password I can perform a purchase. My question is whether it's only because it's a testing account and the application is not actually on AppStore? What should I do to avoid this message or at least to continue the purchasing process?

    Any help is appreciated and again thanks for the great post!
    Nava

    • mugunthkumar

      Whenever u change stores u have to reconfirm ur purchases. This applies to apps u purchase thru AppStore as well (not just in app purchases). Try logging out and initiate a purchase from AppStore and type in ur test credentials, the behaviour will be exactly same. :) Sent from my iPhone

      • navac

        But why it fails the transaction after the login and how i prevent it? My biggest concern, that it will happen to the user… Anyway to test I need to sign out the AppSore settings on the device, so it's like i'm changing the store… How do i catch/prevent it?

        Thanks a lot for your help!

        • mugunthkumar

          Yes it will happen to your users also. But it's not a bug. Even iTunes on mac/windows behaves like that. Login using, say Canadian store. Then sign out and try initiating a purchase. Give ur us store credentials. Ur purchase will fail and u will be taken to us store home page.This is by design I think.Sent from my iPhone

          • navac

            So basically you say, that i should leave it as is or point the user to try again? The original alert from AppStore doesn't let user know what he should do next? I mean saying try again and so on…
            How to handle it logically?

            Thanks a lot!

  • navac

    I'd like to share from my experience and may be it would be a useful addition to your tutorial:
    1. The application's bundle identifier should match the App ID we've created in the developer portal. Say you've create the following App ID: HD5583.com.yourCompany.YourApplication.
    In Xcode project the bundle identifier should be com.yourCompany.YourApplication. Without the HD5583 prefix. It's very important. In iTunes when you mention bundle id, it also should say com.yourCompany.YourApplication.
    2. Now another very important thing: Mobile provisioning profile. We need to create a new mobile provisioning profile for App ID we've created to sign with it the application.
    3. ProductID in your sources. In the example you define the productID as "com.yourcompany.featureA". When preparing array of products, you need to state only "featureA" as product identifier. Other string combinations will result invalid product identifiers.

    These are things I would need to figure out by myself. Hopefully somebody will benefit from this as I did from your great tutorial and answer.

    • anon

      >> 3. ProductID in your sources. In the example you define the productID as "com.yourcompany.featureA". When preparing array of products, you need to state only "featureA" as product identifier. Other string combinations will result invalid product identifiers.

      Not sure whether this matters. When I finally got it working, i was using the full productID. Another thing I found was that you can't upload the binary then reject it; it won't get reviewed anyway till you've tested/approved the in-app purchase.

      • anon

        also, if you DID reject the binary, you'll need to create a new productID (different one as it won't let you use the old one) after uploading again or it won't work.

  • http://drinkbrainjuice.com Benjamin Jackson

    StoreKit doesn't seem to work in Ad Hoc distribution. Any idea why?

    • mugunthkumar

      Yes. I usually comment out the code that calls the store kit while preparing a adhoc build. Not only that, your free app that uses in app purchases cannot be used freely by reviewers. That's promo codes doesn't give free copies of the in app purchase as well :-( Sent from my iPhone