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.

Around last year this time, I wrote a singleton class, MKStoreKit, and a blog post, wrapping Apple’s StoreKit framework and it has been quite popular. From the email responses I receive, I could say, it’s been in use in more than thousands of apps on the AppStore. It has been very successful toolkit that, it was referred as the “goto” toolkit for in-app purchases.

A WordPress Statistics showing that MKStoreKit is a more popular keyword for Search

 

Developers even search for MKStoreKit (might be they heard it through word-of-mouth). Now, I think, it’s time to re-write it from the scratch. Since MKStoreKit is quite successful, I think it’s time to introduce some “standards” into the framework.

What’s new in 3.0

MKStoreKit 3.0 adds support for product availability notifications, cancelled transaction notification, restore transactions, server product model support, super easy consumable support and built in ability to display product titles along with their localized price. We will look at them one by one in this post.

Products available notification

MKStoreKit 3.0 can notify the delegate handler when Apple’s storekit framework returns

- (void)productFetchComplete;
// variable that tracks the state of StoreManager's product availability
BOOL isProductsAvailable;

Handle this delegate and refresh your views that shows your list of in-app products. There is also a variable called isProductsAvailable. If this is false, you can show a spinner till you get the productFetchComplete delegate returns. If it’s “YES”, you can call necessary functions.to display your products. MKStoreKit 3.0 has another function to format the product name with their localized name, description and currency.

- (NSMutableArray*) purchasableObjectsDescription;

When you call [[MKStoreManager sharedManager] purchaseableObjectsDescription] you wil get a array of strings which you can straight away use it for displaying on your table view. With little tweaks to that method, you can customize it to display however you want. Note that this returned array will be empty when isProductsAvailable is “NO”

Restore transactions with ease

First and foremost request was for adding the ability to restore purchases.

- (void) restorePreviousTransactions;

Just call [[MKStoreManager sharedManager] restorePreviousTransactions] from your IBAction for the restore button.

Super easy consumable support

The new version of MKStoreKit has a very easy and slick support for Consumables. It automatically keeps track of the quantity of the consumable quantity and you can use helper methods within MKStoreKit to check if the user has enough quantity of the consumable for use.

- (BOOL) canConsumeProduct:(NSString*) productIdentifier quantity:(int) quantity;
- (BOOL) consumeProduct:(NSString*) productIdentifier quantity:(int) quantity;

Now you don’t get all these features for free. You need to customize MKStoreKit by telling which of your product is a consumable and which one is a non-consumable. And for each consumable product id, you must configure it to tell MKStoreKit the actual virtual count associated with it.

For example, you might have a “Fish Tank” where users can buy “eggs”. You have a “small” basket, “medium” basket. You need to tell MKStoreKit, how many “eggs” each consumable contain so that you can use the built in helper methods to consume them and to check if the user has “enough” items to consume. While it all looks complicated outside, I have introduced some naming convention standards that makes life easy here.

Naming a product identifier with a trailing number tells MKStoreKit to treat it as a consumable.

So in this fish tank case, you can have
com.mycompany.myfishtankapp.fish1 as a non-consumable and
com.mycompany.myfishtankapp.eggs.small.5 as a consumable that gives “5″ eggs to the user to consume.

If you follow this simple naming convention when you create your consumables, it will be super easy to use with MKStoreKit 3.0

Support for cancelled transaction delegate

The next big request was to add support for cancelled transactions. The time it takes for Apple’s storekit framework to show the In-App purchase prompt. While developers show a simple spinner, it wasn’t possible to stop or hide the spinner when the user cancels the transaction. MKStoreKit 3.0 adds a new delegate method that notifies you of a cancelled transaction.

- (void)transactionCanceled;
// as a matter of UX, don't show a "User Canceled transaction" alert view here
// use this only to "enable/disable your UI or hide your activity indicator view etc.,

Support for server product model

The new and improved MKStoreKit 3.0 also supports Server Product Model and improved support for consumables. Configuring your server for server product model is super easy with the PHP scripts bundled with MKStoreKit. Just change the SERVER_PRODUCT_MODEL macro to 1 and MKStoreKit will ping your servers for receipt verification. What’s more? The PHP code for verifying receipts is also bundled with MKStoreKit 3.0.

#define SERVER_PRODUCT_MODEL 0

Links to my old posts

  1. iPhone Tutorial – In-App Purchases
  2. iPhone Tutorial – Enabling reviewers to use your In-App purchases for free

As a matter of fact, the source code links on the old posts will be removed 1 month from the today and MKStoreKit 3.0 will be the only supported version (if everything goes well). In case you need the old code, you can drop me an email, but it will not be supported.

Source code

The source code for MKStoreKit 3.1 is attached here.
MKStoreKit v3.1
Update 3: (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.
Update 2 (4th Dec 2010): MKStoreKit will not initialize and use up memory when you run from simulator.
Update 1 (24th Nov 2010): Minor compilation bug kFeatureBId missing fixed. Please download latest files again.

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


Follow me on Twitter

  • http://seriousfunapps.com Samuel Tremblay

    Great job Mr. Kumar!! Thanks to help this community of iOS developer!

  • Ding

    Thank's it looks great but….

    When trying to compile I get the following errors:
    1. kFeatureBId undeclared
    2. kProductPrefix undeclared

    suggestions?

    • http://mugunthkumar.com Mugunth Kumar

      Fixed this minor compilation issue and re-uploaded the code. Please try again

      • http://twitter.com/geekcsm Claudio S Mattos

        Mugunth…implemented MKStorekit successfully (non-consumable app) here but my app has been rejected by Appstore because it needs to implement restore transactions.

        I have only one button in my app and i would like to use it to buy a new product, how can i check if a transaction was already done? 

        Is this correct?

        -(IBAction)buttonclicked:(id)sender
        {   
            [[MKStoreManager sharedManager] restorePreviousTransactionsOnComplete:^(void) {
                NSLog(@”Restored.”);
                /* update views, etc. */
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@”Your purchase has been restored!” 
                                                               delegate:self cancelButtonTitle:@”OK” otherButtonTitles:nil];
                [alert show];
                [botao setHidden:YES];
                [bannerView_ setHidden:YES];
                botao.hidden=YES;
                bannerView_.hidden=YES;
                
            }
                                                                          onError:^(NSError *error) 
             {
                 NSLog(@”Restore failed===: %@”, [error localizedDescription]);
                 // If not restore, then prompt to buy
                     [[MKStoreManager sharedManager] buyFeature:kFeatureAId 
                                                     onComplete:^(NSString* purchasedFeature, NSData* purchasedReceipt)
                      {
                          NSLog(@”Purchased: %@”, purchasedFeature);
                          [botao setHidden:YES];
                          [bannerView_ setHidden:YES];
                          botao.hidden=YES;
                          bannerView_.hidden=YES;
                      }
                                                    onCancelled:^                        
                      {
                          NSLog(@”User Cancelled Transaction”);
                      }];

                 }
                     
                      
             }];
            
        }

        • MugunthKumar

          Yes, it is correct.

  • rkitting

    Thank you very much for this update! I have a question about the ownServer variable.
    What must I set this to when using the Server Product Model?
    Does restoring previous transactions work for Subscriptions, since a user must be able to use the product on many devices?

    • http://mugunthkumar.com Mugunth Kumar

      Restore transactions work only for non consumables. For consumables and subscriptions its upto your server to restore transactions.

      For ownServer configurations check the attached readme file and the associated PHP files.

      Sent from my Windows Phone

      • rkitting

        when I purchase an item with (void) buyFeature:(NSString*) featureId, then canCurrentDeviceUseFeature is called…. what if I don’t want reviewers to test the app temporarily, but instead want users to be able to use the app for an additional 30 days?
        I would like to use the DB Setup you provided to verify UDIDs with a Login.

      • http://nukemhill.wordpress.com/ NukemHill

        Okay, I’ve got a follow-up question about this. I have both non-consumables and subscriptions that I’m selling through my app. I’m trying to understand the logic involved in how (and when) MKStoreObserver is called. If I execute restorePreviousTransactions for someone who has purchased a non-consumable, what gets the call-back in MKStoreObserver? And, if I execute restorePreviousTransactions for someone who has purchased a subscription, is there a call-back? And what happens if someone has purchased both? How is that handled?

        I’d appreciate any help you can give me. I’ve dug through the docs, but haven’t found anything that really addresses this. I’m also going to post a question to the dev forums. If I get a response back there, I’ll post the results here.

        • http://nukemhill.wordpress.com/ NukemHill

          Okay, I’ve figured it out. But I’ve got an even more interesting issue. How can you tell if an app user is actually logged into the app store? I ask because I’m still testing my In App purchases, and have run into an issue where I’m logged out, but I’m testing to see if a product has already been purchased. That test can’t happen unless the user is logged in.

          It’s a bizarre chicken-and-egg loop that I can’t seem to work my way out of. Any thoughts?

  • jake

    I am trying to use this class,, and it works fine but i have to click on the purchase button TWICE.. i keep getting the error
    User canceled transaction:

    right of the bat, but I am not canceling it .. I dont know why that error is getting called

    • http://mugunthkumar.com Mugunth Kumar

      this is mostly because of a wrong test user configuration. delete your test user and re-create and test. Hope that helps.
      Sent from my Windows Phone

    • nsih

      I am also getting same eror.

      • http://www.facebook.com/micah.seneshen Micah Seneshen

        the reason that happens is because, first, you have to initialize MKStoreKit in your app’s delegate by putting [MKStoreManager sharedManager];

        If you did that, you have to wait until your products have been loaded, and you can see that in the log. MKStoreKit will show that it is done by NSLogging the name, price, and other info.

        • Astouder

          I’m not sure that is the only solution for this problem. I am having the same problem. But my MKStorKit is initialized. I am getting my products info in the NSLog. My problem is when I trigger the purchase after about 4 seconds the log displays the “the user has cancelled transaction” twice. even tho nothing told it to do that.
          I believe the problem has something to do with the Delegate. I understand what a delegate is supposed to do. However I do not know how to use it. Could somebody please explain to me what MK means by “handle the delegate”?

          • frankiech

            I’m having the exact issue that Astouder is having. Does anyone have any tips?

          • Mainul

            Hi,

            Did you get the solution. I’m also facing same problem

          • SwaggedOut

            p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Arial}
            p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Arial; min-height: 14.0px}
            p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 18.0px; font: 12.0px Arial; background-color: #eeeeee}
            p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 18.0px; font: 12.0px Arial; background-color: #eeeeee; min-height: 14.0px}

            Try cleaning your project, removing the app from your device, and make sure you have all this checked off”

            * Have you enabled In-App Purchases for your App ID?

            * Have you checked Cleared for Sale for your product?

            * Have you submitted (and optionally rejected) your application binary?

            * Does your project’s .plist Bundle ID match your App ID?

            * Have you generated and installed a new provisioning profile for the new App ID?

            * Have you configured your project to code sign using this new provisioning profile?

            * Are you building for iPhone OS 3.0 or above?

            * Are you using the full product ID when when making an SKProductRequest?

            * Have you waited several hours since adding your product to iTunes Connect?

            * Are your bank details active on iTunes Connect? (via Mark)

            If you answered no, then there’s your problem.

          • Mainul

            Thanks for your reply.
            Answer of all questions is yes. I don’t find out what is the problem.
            And also i used this code with my various project and didn’t face any
            problem. One question, How many hours i need to wait? 2 days passed that the inappurchase product created. Please check my console output:

            1970-03-18 02:56:22.-997
            paint[2387:307] Feature: Full Version of iPaint Studio, Cost: 0.990000, ID:
            com.shaonlab.iPaintStudioiPhoneVersion.FullVeriPaintiPhone

            1970-03-18 02:56:29.-934
            paint[2387:307] User cancelled transaction:

          • Mainul

            At last i found out what was the problem. Actually the date & time of my iPod Touch was not current time. See the previous message, the date was shown 1970. After correct the date & time, my in app purchase work. Thank to mugunthkumar for his mkstorekit.

          • Mainul

            At last i found out what was the problem. Actually the date & time of my iPod Touch was not current time. See the previous message, the date was shown 1970. After correct the date & time, my in app purchase work. Thank to mugunthkumar for his mkstorekit.

          • Mainul

            At last i found out what was the problem. Actually the date & time of my iPod Touch was not current time. See the previous message, the date was shown 1970. After correct the date & time, my in app purchase work. Thank to mugunthkumar for his mkstorekit.

  • Merlin

    Hi. Thanks for this helpfull class.

    I have a paid app which selling about a month and some people already bought it.
    For now I decided to add an ads to it with ability to disable it via in-app purchase. Also I don’t want to make the second version of it – it will be an upgrade.

    But I have no idea yet what is the best way to not show the ads to existing clients… Could you point me to the right direction?

    Thanks

  • http://blog.willwinder.com/ Will Winder

    Thanks for providing this class. I was able to integrate it and in-app purchases with my app in 3 hours — and most of that was trying to get a test user enabled!

    A blog post on how to test in-app purchases would be appreciated. I was stuck because I didn’t know that the “username” is supposed to be the email address (honestly, why does it say “Username” when you have to type in the email address???).

    I did make one small addition to your code for testing purposes. In the configuration section of MKStoreManager.h I added an additional setting called “#define UNLOCK_EVERYTHING 1″ which I used in isFeaturePurchased to override the actual values (i.e. always false on the simulator). I will probably add another at some point called “LOCK_EVERYTHING”.

    This works and is very easy to do, but is there a better way to do this?

    • Anonymous

      The only problem with your lock and unlock is one mistake you do there, your customers will not be able to unlock anything even after purchasing. Be very careful here.

  • commentor

    Isn’t it dangerous to save purchased items in the NSUserDefaults?
    People could just use software like ‘iPhoneExplorer’ and add the needed keys and thus are able to unlock everything?

    I’d suggest using the iPhones-Keychain (I’m using these classes for easy access: https://github.com/ldandersen/scifihifi-iphone/tree/master/security )

  • Ted

    Thanks for this!
    Even before I’m trying to implement it, I have a question:
    It seems to me that the app has to «know» about inAppPurchases? How would I implement it, if I would add more and more «stuff» that people can purchase?
    Let’s say, I have an app with a barbie-doll. In-App purchases would be clothing for the doll. I have x products that a user can buy – now I add n more products. Would I have to write an update for the app or is there another was to tell the app, that there are more products available now?

    Thanks for any help

    • Anonymous

      You have to fetch the product ids from your server at launch instead of
      hard coding within the app

      Sent from my Windows Phone From: Disqus
      Sent: Sunday, 12 December, 2010 1:28 PM
      To: me@mk.sg
      Subject: [mkblog] Re: Introducing MKStoreKit – Version 3
      ======

  • http://twitter.com/mramin05 Ruhul Amin

    Thanks a lot. But Every time it show the error..
    “Payment requests are restricted to products returned as valid via Store Kit’s didReceiveResponce methods”

    I have check the product id several times and the UI is populated by the product id by calling
    [MKStoreManager sharedManager]

    I am using 4.2.1 iOS and xcode 3.2.5
    Please help me.
    Thanks again.

    • Anonymous

      Try creating a new test user.

      Sent from my Windows Phone From: Disqus
      Sent: Monday, 13 December, 2010 3:46 PM
      To: me@mk.sg
      Subject: [mkblog] Re: Introducing MKStoreKit – Version 3
      ======

      • http://twitter.com/mramin05 Ruhul Amin

        Thanks for reply.

        I have tested with new user. but the same result. Same error as previous.
        I also tested in 4.1 device in xcode 3.2.5.
        in iOS 4.1 produces error “Problem in iTunes connect configuration for product”
        But this error(configuration error ) is not display in iOS 4.2.1 device.

        I have my app purchase in “Pending Developer Approval”, “Approved By Developer”, and “Waiting for Review” state. 2 products are in each state. and I have tasted all products( in all state) by turn.

        But same error.
        is it a problem to have product in different state? or any other problem?

        • Rob

          I have the same problem. Did you ever get it figured out?

        • Rob

          never mind, figured it out myself. I had to use com.asdf.app.thing insted of just thing as the product identifier.

          • Rob

            in both itunesconnect and the app

          • Dell

            Do you mean that you set your kFeatureId to the same as the Product ID in connect?

            Mine is already like that but im still getting the error with every new test user :(

      • mrjazzguitar

        I have two test user accounts and when i switch between them using the “Existing Account” login selection during purchase I also get the “Payment requests are restricted to products returned as valid via Store Kit’s didReceiveResponce methods” error.

        The only way to clear it is to exit the app and reenter. The next attempt to buy works. First login purchases do not work. :(

    • Dell

      Thanks a lot for this, its been very easy to implement so far..I just wanted to mention that I too am getting this error with IOS 4.2.1 on the iPad.

      It seems to error the first time you login with a new user, and will continue to do so on further purchase attempts until the app is re-opened, at which point it is successful first time.

      Another interesting thing is that it doesnt even need to be fully re-opened, sending the app to the background with the home key and bringing it back again also fixes the problem.

      Anyone any ideas?

      Thanks again for this guide & code!

    • http://twitter.com/zorro2b Andrew Zahra

      According to this technote you must use the response from productsRequest to the paymentWithProduct method. It seems this library was not designed with that in mind:
      http://developer.apple.com/library/ios/#qa/qa2010/qa1691.html

  • http://twitter.com/ThisIsChrisO Chris

    Awesome bit of time saving code! i Have an issue though, im still new to programming, i have your files set up and can correctly “Purchase items”.

    From my StoreViewController i invoke

    [[MKStoreManager sharedManager] buyFeature:@”dummyData”];

    But how does my StoreViewController know if the transaction is complete so that it can dismiss its self?

    • http://twitter.com/Vladdmeir James Burns

      Any news on this?

    • http://craigjolicoeur.com/ Craig Jolicoeur

      same issue. is there any kind of delegate method or callback we can check when a transaction has been successfully completed?

    • http://craigjolicoeur.com/ Craig Jolicoeur

      I found the answer after reading the source myself.

      There is a productPurchased: delegate method that will be called on a successful purchase. Just declare your viewcontroller as the MKStoreManager delegate

  • soch

    Does MKStroreKit 3.1 support Subscription Products?

    Does your php file provide subscriptions to on all devices associated with a user? You must provide the infrastructure to deliver subscriptions to multiple devices. How do you authenticate that it is a valid user. I am guessing the server needs to store a userid, udids of all the devices (to limit piracy), appstore receipt.

    Also does the MStoreKit 3.1 store the app preferences in the iphone key-chain to avoid app deletes (to avoid piracy)?

  • http://www.gamma-point.com Gamma Point

    You need to use SKPayment paymentWithProduct:(SKProduct *)product instead of SKPayment paymentWithProductIdentifier: otherwise you will get the dreaded error related to “payments are restricted …”

  • http://www.gamma-point.com GammaPoint

    Also make sure you sign out of the app store via settings. Every time you enter the sandbox test user credentials the app store gets signed in but not out.

  • nsih

    Getting this message in console – User cancelled transaction:

    • nsih

      how to solve this error.

      • http://www.facebook.com/micah.seneshen Micah Seneshen

        the reason that happens is because, first, you have to initialize MKStoreKit in your app’s delegate by putting [MKStoreManager sharedManager];

        If you did that, you have to wait until your products have been loaded, and you can see that in the log. MKStoreKit will show that it is done by NSLogging the name, price, and other info.

  • http://twitter.com/davandermobile Davander Mobile

    Hey!
    This is a great bit of code! I’m currently working it into an app.
    I just wanted to let you/everyone know that I’ve put a copy of it up on github.

    https://github.com/coryalder/MKStoreKit

    Hopefully this will encourage people to use, fork and enhance the framework.

  • Iron Helmet

    Thanks for this, very helpful.

    I had one issue though, it was treating my non-consumables as consumables and I had to edit provideContent to assume they were non-consumables (which all of mine are). I used the naming convention above (i.e. no number at the end), so not sure why.

    Thanks again, works great.

    • Anonymous

      That naming convention should be used only for consumables.
      MKStoreKit treats a product as consumable if it ends with a number at last.

      • Iron Helmet

        Yep, I meant I didn’t put a number at the end of it.

        • http://www.paikia.com paikia

          I also have the same similar issue and it crashes at MKStoreManager.m – line 292
          NSString *countText = [productIdentifier substringFromIndex:range.location+[kConsumableBaseFeatureId length]];

          • Anonymous

            Can you explain more? Can you list me your product ids?

          • http://www.paikia.com paikia

            The id I used com.testcode.testballoon.balloon1

            The error I’m getting is
            *** Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘*** -[NSCFString substringFromIndex:]: Range or index out of bounds’

            So my own solution method is adding @try to catch the error exception and set countText value to 0

          • http://arsenius.blogspot.com/ Tim

            I had the same problem, except that I am not even using consumable purchases. Thank you paika for your solution!

            My ids were

            #define kConsumableBaseFeatureId @”com.mysite.myapp.removeads”
            #define kFeatureAId @”myapp.removeads”

            Initially I only setup kFeatureAId. Then when I got the error, I changed kConsumableBaseFeatureId also. Starting the transaction seemed fine, but it crashed once the transaction went through.

  • http://twitter.com/Vladdmeir James Burns

    So, to detect a canceled transaction you do;

    if ([[MKStoreManager sharedManager]transactionCanceled:(SKPaymentTransaction *)transaction

    or something to that effect, I get an error ” ‘transaction’ undeclared.
    and a warning that MKStoreManager may not respond to transactionCanceled

    So, a lil help?
    Thanks :)

  • Alex

    Hello

    Just a small question:
    Did anyone thinking about what happens if on a jailbroken device the following code will be changed from:
    > if([MKStoreManager isFeaturePurchased:kFeatureID])
    to
    > if(![MKStoreManager isFeaturePurchased:kFeatureID])
    with a hex editor ?
    I even don’t know if it is possible at all but i was thinking about a little because i save everything in the keychain instead of userdefaults at all…

  • http://twitter.com/ManboloGames Manbolo

    Hello Mugunth,

    Thank you very much for this code, very simple to use and works perfectly.
    I’ve implemented basic purchase in one of our game in less than an hour!
    Nevertheless, I’ve encoutered an issue with my code.

    Let’s say the app is in flight mode (no connexion). The app will launch and the singleton for MKStoreManager will create in the applicationDidFinishLaunching selector. As there is no connexion, the requestProductData will fail but the MKStoreManager class is not aware of an error happen.
    If you try to check isProductsAvailable on the manager, you will never get a YES answer because the requestProductData has failed. If the network go on, shouldn’t you try to do another requestProductData?

    I’ve simply implement the – (void)request:(SKRequest *)request didFailWithError:(NSError *)error selector from SKRequestDelegate implemented by MKStoreManager. If there is an error, I try to perform a new requestProductData in a few minutes.

    In MKStoreManager.m, my patch is

    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error
    {
    DebugLog(@”request fail with error:%@”, [error description]);
    [request autorelease];

    // some error, try to connect in one minute
    [self performSelector:@(requestProductData) withObject:nil afterDelay:60.0];
    }

    What do you think of this?

    Best regards,

    Jicé

    • Anonymous

      Cool! I’ll add it into the code bro. Thanks

      • http://twitter.com/ManboloGames Manbolo

        just a typo
        // some error, try to connect in one minute
        [self performSelector:@selector(requestProductData) withObject:nil afterDelay:60.0];
        }

  • Dylan

    Impressive and generous. If this app sells 1/10th of what I’m hoping for, I’ll be visiting your PayPal inbox. ;) One question so far– How do I use #define SERVER_PRODUCT_MODEL 1? I thought could set that flag to 1, and just change: static NSString *ownServer; in the MKStoreManager.m file, but that’s not working out. I have the line currently as: static NSString *ownServer = @”http://www.mycompany.com/iap/”; (with my real name in there…) and I’d swear I’ve set all the right things correctly on the web server itself. Anything obvious I’m missing?

    If I don’t set that flag, does that mean it won’t be validating receipts?

    • Anonymous

      Did you upload the server code folder properly?

      • Dylan

        I thought so. I created the database using the DBSetup.sql file (and I can see that the database is there)… made a new user for MKStoreKit stuff on the server, and gave it full permissions to everything in that database. I edited the php files to show the user/pass they would need and they seem to execute when I browse to them directly in Safari…. I’ll keep troubleshooting and report back. As it is, with the SERVER_PRODUCT_MODEL flag set to 1, in app purchasing seems to work only to the point of getting the Apple alerts about logging in, and confirming the purchase, and even reporting that the purchased succeeded… but my code: if([MKStoreManager isFeaturePurchased:kFeatureAId]) { will always resolve false. As soon as I set the SERVER flag to 0, it works fine.

        The server is an OSX Server (10.5.x I believe) on an XServe Xenon, if it seems remotely relevant.

  • http://twitter.com/ManboloGames Manbolo

    Hi Mugunth,

    I’m using consumable In App Purchase, within the Sandbox. Everything works perfectly except sometimes I’ve the following error “You’ve already purchased this In App Purchase but it hasn’t been downloaded”. I’m not able to reproduce it but it seems to happen more often when I’ve just tried to connect with the sandbox user.

    I’m using the following selector
    [[MKStoreManager sharedManager] buyFeature:productId_];
    [[MKStoreManager sharedManager] canConsumeProduct:productId_ quantity:1]
    [[MKStoreManager sharedManager] consumeProduct:productId quantity:1]
    [[MKStoreManager sharedManager] isProductsAvailable]

    I will try to dig into more details into this and try to have reproductible steps,but if you have any idea…

    Thanks for you very helpfull code

    Jicé

    • Anonymous

      It mostly happens for consumables on test accounts. Not to worry much
      about. I’m actually talking to Apple regarding this and I’m aware of
      this problem. Thanks

      • Mark Krenek

        I get the “hasn’t been downloaded” error a lot with consumables. Is it really just an issue in the sandbox. It’s kinda nerve racking to release an app where that might happen.

  • Astouder

    Can somebody please explain the proper way to use the setDelegate method?

    • http://www.blog.willandnora.com/ eSpecialized

      in @interface //add the delegate handler

      in your @implementation, in your initWithNibName or viewDidLoad etc..
      MKStoreObserver *observer = [[MKStoreObserver alloc] init];
      [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];

      [MKStoreManager setDelegate:self]; //do this after the above.

  • Anon

    Hi nice work ! Just one question, if my in app is not an one time feature eg if its credits how can i alter the code to get the quantity of the credits the user has purchased?

  • Cteel

    For some reason I’m getting a user canceled transaction method. I have [MKStoreManager sharedManager] being called in my didFinishLaunchingWithOptions implementation right before adding my subViews to my window.

    The products are being verified properly when I call [MKStoreManager sharedManager].

    Any ideas?

  • Mcampolese

    Hi, I’ve used your last storeKit and I followed all the instruction. I declare in applicationDidFinishLaunching the “[MKStoreManager sharedManager];” but I’ve found this: when I run my app from my device (disconnected from the Mac) and I’ve already purchased the in-app content previous, it keeps on asking to myself the password for my account at every execution of my App. Did I forget something? I hope that customers won’t have troubles by that..

  • Mcampolese

    Hi, I’ve used your last storeKit and I followed all the instruction. I declare in applicationDidFinishLaunching the “[MKStoreManager sharedManager];” but I’ve found this: when I run my app from my device (disconnected from the Mac) and I’ve already purchased the in-app content previous, it keeps on asking to myself the password for my account at every execution of my App. Did I forget something? I hope that customers won’t have troubles by that..

  • Evgeniy Gt

    Hello MugunthKumar!
    I’m using consumable In App Purchase, within the Sandbox.
    Before this day everything is working…
    Today error “You’ve already purchased this In App Purchase but it hasn’t been downloaded”
    Help me please…

    Sorry for my english

  • Tim

    Thanks for this. Grat work, but I muss some points:

    1. informative documentation for MKStoreKit-properties
    2. informative documentation for CONFIGURATION hints like “kFeatureAId”: What do we need for different in-app-purchase-models (consumable, non-consumable …)
    3. hints where exactly your code may be extended for your own app: e.g. “define multiple kFeatureXXXId” for all your non-consumable products.
    4. consistent naming or better documentation: productId/featureId – what is the difference?

    However, I really appreciate your work so far. Keep going that project, please.
    Tim

  • Doug

    I might be doing something wrong, but I am getting a warning in Xcode when I call [[MKStoreManager sharedManager] isProductsAvailable] stating that isProductsAvailable is not found. Should isProductsAvailable be declared as a property so it can be accessed to check for products? I just added @property BOOL isProductsAvailable; to the .h and @synthesize isProductsAvailable; to the .m. Should I be checking this a different way?

    Thanks,
    Doug

  • Ian

    Thanks for sharing. The download says version 3.0 but the text says ‘The source code for MKStoreKit 3.1 is attached here.’
    Is the upload correct?

    cheers

    Ian

  • Tom

    Hi Mugunth, thanks for the the nice code,
    I have a question: how will I check On my app loop if the consumable was purchased ?

    i have this 2 consumables:

    com.mycompany.myapp.coins.10000
    com.mycompany.myapp.coins.50000

    Let’s say The app calls a BuyFeature(@”com.mycompany.myapp.coins.10000),
    How do I check / get the 10000 units when purchase is success?

    Coins will be neverending, so i don t need to decrease that amount from no where, may you explain what should be the consumable featureID step in this case?

    Thank you in advance

  • http://nukemhill.wordpress.com/ NukemHill

    I’m trying to use this library to enable In App purchasing, but am having problems. When MKStoreManager initializes (in +(MKStoreManager *)sharedManager), and – (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response is called, the response comes back with both of my productIDs marked as invalid. I’m really at a loss as to how this is happening.

    Any suggestions for where I should be looking? They certainly appear to match the IDs I’ve set up in iTunes Connect.

    • http://nukemhill.wordpress.com/ NukemHill

      Just an FYI. I got it to work. I had to completely rip out the AppID and ProductIDs and create brand new ones (thanks, Apple!) before I could get it all to work.

      • Anonymous

        That is the best way to do it.

  • http://twitter.com/aSadhankar Aaron Sadhankar

    Hi MK. Thanks for making your MKStoreKit available, it has made things easier for me.

    Just a comment, the following causes my app to crash (in the ‘productsRequest’ method):

    [request autorelease];

    Is there a reason for this; I don’t think the request input needs to be autoreleased, but I could be wrong. Thanks.

  • BD

    Problem in In-App purchase subscription module.

    Hi,

    I have problem in In-App purchase server model (Subscription model). I an vert much confuse about last step in servermodel after finished receipt verification (“Step # 14 : The server delivers the purchase content to the application”). Which contents are given by server to device and how it manage subscriptions? I know all receipts data are store into server but how this service is giving to device and how device get notify about its subscription’s expire date?

    1 to 13 all the step are completed only this is the problem how the device will notify the now you can start download the product(book) form our server.

    please any one have idea then please share..

    Thanks in advance….

  • Dimple

    Thanks for such an understandable article and code.
    I want to use this for my application.But how to find “feature Id” for features? Do i have to create it from iTunes?Please let me know.

  • kamleshwar

    Hi great effort and excellent work. i am implementing InApp with help of you Code. Still no issue.

    Thanks

  • Omen001

    Hi,

    Thanks for the code, it is very useful. I was able to integrate but get following error, can you help?

    *** Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘*** -[NSCFString substringFromIndex:]: Range or index out of bounds’
    *** Call stack at first throw:
    (
    0 CoreFoundation 0x3587a987 __exceptionPreprocess + 114
    1 libobjc.A.dylib 0x34a8249d objc_exception_throw + 24
    2 CoreFoundation 0x3587a7c9 +[NSException raise:format:arguments:] + 68
    3 CoreFoundation 0x3587a803 +[NSException raise:format:] + 34
    4 Foundation 0x31178a5d -[NSString substringFromIndex:] + 92
    5 LSTGH_Lite_iPad_v1 0x0000eed7 -[MKStoreManager provideContent:forReceipt:] + 82
    6 LSTGH_Lite_iPad_v1 0x0000fb81 -[MKStoreObserver completeTransaction:] + 108
    7 LSTGH_Lite_iPad_v1 0x0000fa0f -[MKStoreObserver paymentQueue:updatedTransactions:] + 246
    8 StoreKit 0x307d8ce5 __NotifyObserverAboutChanges + 44
    9 CoreFoundation 0x358079e9 CFArrayApplyFunction + 40
    10 StoreKit 0x307da72d -[SKPaymentQueue _notifyObserversAboutChanges:] + 92
    11 StoreKit 0x307da28d -[SKPaymentQueue _processUpdates:trimUnmatched:] + 860
    12 StoreKit 0x307d90c7 -[SKPaymentQueue _transactionsRefreshedNotification:] + 34
    13 Foundation 0×31175623 _nsnote_callback + 142
    14 CoreFoundation 0×35801123 __CFXNotificationPost_old + 402
    15 CoreFoundation 0x35800dc3 _CFXNotificationPostNotification + 118
    16 Foundation 0x31164d23 -[NSNotificationCenter postNotificationName:object:userInfo:] + 70
    17 AppSupport 0x3082c869 -[CPDistributedNotificationCenter deliverNotification:userInfo:] + 44
    18 AppSupport 0x3082db95 _CPDNDeliverNotification + 204
    19 AppSupport 0x3082c5eb _XDeliverNotification + 122
    20 AppSupport 0×30823387 migHelperRecievePortCallout + 138
    21 CoreFoundation 0x3580f6ff __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 26
    22 CoreFoundation 0x3580f6c3 __CFRunLoopDoSource1 + 166
    23 CoreFoundation 0x35801f7d __CFRunLoopRun + 520
    24 CoreFoundation 0x35801c87 CFRunLoopRunSpecific + 230
    25 CoreFoundation 0x35801b8f CFRunLoopRunInMode + 58
    26 GraphicsServices 0x320c84ab GSEventRunModal + 114
    27 GraphicsServices 0x320c8557 GSEventRun + 62
    28 UIKit 0x341dc329 -[UIApplication _run] + 412
    29 UIKit 0x341d9e93 UIApplicationMain + 670
    30 LSTGH_Lite_iPad_v1 0x000024cf main + 70
    31 LSTGH_Lite_iPad_v1 0×00002484 start + 40
    )
    terminate called after throwing an instance of ‘NSException’
    Program received signal: “SIGABRT”.

  • Marcie

     THanks so much for this class, it really helps, and your explanations are great too. Although things aren’t as rosy when trying to tie this in to a server download for in-app paid content… but I’m getting there. One isue I have is that the paymentQueue:updatedTransactions: method gets called twice, forcing my download to happen twice… I’m not yet sure why it is this way, but I cannot see where it would be called twice, or why. I’m testing with content which was purchased already and having the App Store ask the user to download again.updatedTransactions: method gets called twice, forcing my download to happen twice… I’m not yet sure why it is this way, but I cannot see where it would be called twice, or why. I’m testing with content which was purchased already and having the App Store ask the user to download again.

  • dreamer76

    I have used MKstore kit for my inApp purchase. It has worked flawlessly during the sandbox testing and environment. I have not customized the MKStore kit except for adding my own consumable information. 

    The only change I have made is in the function -(void) provideContent: (NSString*) productIdentifier forReceipt:(NSData*) receiptData

    I have commented out your NSUserDefault section and have used my own NSUserDefaults to indicate that the user has successfully purchased the app and to unlock features. All this worked without a problem in the test/sandbox environment.

    My app with the inApp purchase got approved this afternoon and I immediately downloaded the app and tried to use the inApp purchase option. I can see the products for sale, however when I tap the buy button, nothing happens. I do not see a pop up or anything to all to confirm the inApp purchase. Nothing happens at all.

    -(IBAction)purchaseItem:(id)sender {
            
        [[MKStoreManager sharedManager] buyFeature:(@”com.N***.**.**”)];
        
    }That is all the code, I have behind the Buy button. This worked fine in sandbox, but in real app. Nothing happens. Note: I have commented out the bundle identifier for the inapp purchase.Is there a lag for the Apple servers after inApp purchase has been enabled? If so, why can I see the product description but not buy the app?Please let me know as soon as possible, my app is online and i need a fix.thanks

    • Marcie

       Have not been there yet, but quite interested. I read somewhere that the ctual in-app purchases need a bit of time to migrate over from the sandbox to the real environment. Perhaps waiting 24 hours will make this work. Please post back when it does work so we can be sure of this. Thanks!

    • Anonymous

      Did you submit your IAP for review?

      Sent from my Windows Phone From: Disqus
      Sent: Thursday, 19 May, 2011 8:21 AM
      To: me@mk.sg
      Subject: [mkblog] Re: Introducing MKStoreKit – Version 3
      ======

      • dreamer76

        yes, I did. This was one of the first things, I did.

      • dreamer76

        Update:

        I have been able to pull up the “Buy the app purchase” dialog box on one of my iphones. I installed the app on an older ipod touch and it was also able to bring up the “buy the app” dialog box from apple. 

        A few minutes later, I try tapping the buy button again and nothing happens.

        It does appear that there is some kind of lag with apple’s server with inapp purchases. Some iphones can pick it up the change, some cannot??

        • Anonymous

          Check your internet connection on those devics. If you are on edge you
          might have to wait for at least a minute in some cases

          Sent from my Windows Phone From: Disqus
          Sent: Thursday, 19 May, 2011 8:38 AM
          To: me@mk.sg
          Subject: [mkblog] Re: Introducing MKStoreKit – Version 3
          ======

          dreamer76 (unregistered) wrote, in response to MugunthKumar:

          Update:

          I have been able to pull up the “Buy the app purchase” dialog box on
          one of my iphones. I installed the app on an older ipod touch and it
          was also able to bring up the “buy the app” dialog box from apple.

          A few minutes later, I try tapping the buy button again and nothing happens.

          It does appear that there is some kind of lag with apple’s server with
          inapp purchases. Some iphones can pick it up the change, some cannot??

          IP address: 173.18.110.211
          Link to comment: http://disq.us/1zsu2i

          • dreamer76

            Mugunth:

            I have wifi turned on all my devices and I am not on edge anywhere. 
            I had called a friend of mine to check the inApp purchase. My friend was unable to buy anything, tapping the buy button did not do anything, my friend had downloaded the app within 2 hours of it unable on the App store.

            my friend called again after a few hours and said, the inapp purchase button now works. So it does appear that there is a lag from Apple Servers.

            Question:
            If there is a lag, how can the app read the in app products and display them for sale, but tapping the buy button does nothing? If the lag were present, would not the lag also not show the products for sale?

            I just don’t want the inApp purchase to work on and off.

            Please let me know your thoughts.

            thanks,

  • Nico

    This is a great tutorial, thanks!

    I am having an issue though. I am very (!) new to programing so I am sure that my problem is extremely easy to solve.

    I will sum up my case in a very simple example:

    Basically, I have an App with two views:
    1. “ViewController”  (=welcome view with some content)
    2. “PurchaseContentView” (=where the user can click on a UIButton to make a purchase and unlock local content) 

    I successfully implemented a UIButton and a test user can make the purchase.
    However, once a user has successfully made the purchase in “PurchaseContentView”:
    > I need him to be redirected automatically to a new view = “ViewControllerWithPurchasedContent”.
    > “ViewControllerWithPurchasedContent” would then have to be the new view that launches at app launch.

    Could someone please help me out?

  • cv186868

    Thanks for all your work in on this! However, I made an app and published it in the app store. I even submitted the in-app with my app, however it says it’s still says it’s in sandbox mode? I had to pull it just for that. Please help so I can fix it!

  • Coulton

    Thanks so much for you work into making it easy for developers to use it! 

    I used this in one of my apps and it worked great in sandbox mode. However, after getting accepted by apple is still says it’s in sandbox mode. This is not good. I had to pull it and I am loosing money! My app was paid before and now it’s pulled for the app store because of this. I have lost my place in the top charts, and yadi yadi yadda. 
    So did I have to do anything else with the code to take it out of sandbox mode? I submitted the inapp payment with the new version, however I am still getting this ‘[environment sandbox]‘ message at the bottom of the UIAlertView. 

    Please help!
    Coulton

  • Gurumahesh Vunna

    i had one non-consumable and one auto-renew product .i worked with non-consumed product it is o.k.
    my problem is when i purchase auto-renew product which i need to add to my app to validate that is renewed or not?
    and after purchasing a product  i did n’t verified the receipt till now. is needed now to verify the receipt . can anybody tell me what is the solution for me?

    thanks in- advance.

  • Leal

    Dumb question: How I received the complete or cancelled action in my class? When the user click on Buy or Cancel, the mkStoreManager give a msg, but how I listen it from my class in order to perform the next action? (update UI for instance).

    thank you

    • Leal

      I don’t want to put my code inside MKStoreManager in transactionCanceled and buyFeature method.

      • Wolee_2000

        I am having the same problem and don’t want to write code inside MKStoreManager.  I found a workaround by having user clicks “ok” button again after confirming the purchase.  Not the best user experience, but that’s the only way I can think of.

  • Wolee_2000

    Does MKStoreKit 4.0 support earlier version like 3.1?  It seems to work for IOS 4.0 and above.  I am having problem with earlier IOS.

    Thanks.

  • pravin uttarwar

    Hi,

    I have one scenario where i was unable to get callback from mkstorekit.

    Lets say,

    Use started purchase and got Confirm Dialog from apple , dont do anything just click center button of ipad twise and switch to other app.
    You still see the popup, now cancel it.

    Switch back to our original app and see that you dident received the callback from observer or apple. 

    This is causing me the issue where i am unable to hide the loading that shown to user.

    Please help me to solve this out.