MKStoreKit started off in a pet project a couple of years ago and I wrote the first version in 2009.
Since then, it has seen tremendous adoption rates that, it has been the “go-to” framework for implementing In-App purchases in any iOS app today.

On iOS 4.3, Apple added a new type of subscription framework called, auto-renewable subscriptions.

Today, I’m adding this support to MKStoreKit along with several new features. If you have been following me on Twitter (@mugunthkumar) or on Github, you would have gotten the code a week ago.
Now that, I’m done with my testing it’s finally ready for some real-world adoption in your projects.

What’s New

  1. The main feature includes support for Auto-renewable subscriptions.
  2. The second important change is to move configuration related changes to a separate file,
    MKStoreKitConfigs.h and
    MKStoreKitConfigs.plist

    The plist contains a list of products you support and MKStoreKit automatically reads this and creates StoreKit requests on your behalf. This feature was partly inspired by a issue raised here by a contributor, dfabulich. But I didn’t like adding a datasource to MKStoreKit for various reasons. I think this method is cleaner than that. MKStoreKit now behaves like a standalone drop-in package.

  3. MKStoreKitDelegate removed in favour of Blocks
  4. Keychain support using SFHFKeychainUtils

    How to use

    The most important change to MKStoreKit is the use of a plist file instead of Macro strings for your product ids.
    The following screenshot shows the plist file organization.
    MKStoreKitConfigs.plist
    There are three main sections, Consumables, Non-consumables and Subscriptions. You specify your product inside each category based on how you added them on iTunes connect.
    After that, MKStoreKit does the rest.
    Non-consumable and Subscriptions are straight forward. I think I might need to explain a bit about Consumable support.

    Every consumable entry in this plist needs to be a dictionary with “Count” and “Name” defined. Imagine that your app is a fish tank app with eggs, plants and fishes as consumables. Fishes are going to be sold on a per-fish basis. However, you might be interested in selling eggs as discounted basket. To allow this business model, I’ve added a new entry called unique name to every consumable.

    If your products, for example,

    com.myfishapp.eggbasket50 = $2
    com.myfishapp.eggbasket500 = $15
    com.myfishapp.eggbasket5000 = $ 125

    are in fact same in your business (in this case they are all eggs, just that the count is different and subsidized for bulk purchases), you can define them on plist as individual consumable mapping to the same name.
    MKStoreKit will then treat all these products as same when remembering purchases.

    So instead of you remember how many fish eggs this person has purchased, you can leave that task to MKStoreKit. You get a nice wrapper around them and call these methods on MKStoreKit

    - (BOOL) canConsumeProduct:(NSString*) productName quantity:(int) quantity;

    to check for product availability and

    - (BOOL) consumeProduct:(NSString*) productName quantity:(int) quantity;

    to notify MKStoreKit to deduct the consumable’s balance.

    This support was originally added to MKStoreKit 3.5, but it didn’t support product bundles. MKStoreKit v4 adds support for this.

    Subscriptions expiry notification

    MKStoreKit automatically posts notifications when your auto-renewable subscriptions are renewed or failed validation (expired). Observe the following notifications,

    kSubscriptionsPurchasedNotification
    kSubscriptionsInvalidNotification

    on your view controller or AppDelegate and take corresponding actions.

    A note for MKStoreKit 3 users

    MKStoreKit 4 uses keychain to remember purchases. This is partly to support In-App purchases on the upcoming operating system, OS X Lion. On iOS, all apps are sand-boxed and outside access to NSUserDefaults is not possible (unless the device is jail-broken). So remembering purchases on NSUserDefaults was *good enough*. On Lion, however, NSUserDefaults file is just another plist that could be edited on any text editor and technically “buy” our in-app feature. To circumvent this piracy, MKStoreKit 4 will use keychain instead of NSUserDefaults.
    As such if you have any consumables, they will not be automatically migrated to keychain store by MKStoreKit. You should do that part yourself. Non-consumables and Subscriptions doesn’t have this issue.

    Source Code

    The complete source is available on Github

    Demo Project

    There isn’t a demo project available because of the nature in which In-App purchases work.
    The only way would be to create a dummy project from my iTunes store account and create IAP. But that would add maintenance nightmare for me. However, since MKStoreKit is self-contained, you shouldn’t normally have trouble integrating it into the product.

    Licensing

    It’s licensed under Zlib as I feel that’s the most open license ever.

    A word on third-party components

    MKStoreKit uses the following third-party components.

    1. JSONKit by John Engelhart (BSD or Apache Licensed)
    2. NSData base64 encoding additions by Matt Gallagher (Zlib license)
    3. SFHFKeychianUtils by Buzz Andersen

    Final words

    I’m still making some touchups and final set of changes. But I think they will mostly be *cosmetic* (code refactoring). Feel free to pull/fork MKStoreKit and leave your feedback.

    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

  • Pingback: iPhone Tutorial – In-App Purchases | MKBlog()

  • Pingback: Introducing MKStoreKit – Version 3 | MKBlog()

  • Pingback: iPhone Tutorial – Enabling reviewers to use your In-App purchases for free | MKBlog()

  • PavelPerout

    MK, Do you have to set up external server for auto-reweval or will your code work without it?

    • Anonymous

      For auto renewable subscriptions you dont need a server. For normal
      subscriptions and server product model, you need it.

      Sent from my Windows Phone
      ——————————

      • Robert Ölei

        Is it possible to keep track of all user subscriptions? 

        I want to give the user access to issues that fit the period of each subscription he paid for. If I’m not misunderstanding something, your code only keeps track of the current, not expired subscription. Each time there is a new subscription for a given ID, the value in the keychain is overwritten.

        Maybe you have some idea for that or maybe it is already possible.

        • Anonymous

          Didn’t understand your question, can you be a bit more specific

        • Anonymous

          Didn’t understand your question, can you be a bit more specific

      • Robert Ölei

        Is it possible to keep track of all user subscriptions? 

        I want to give the user access to issues that fit the period of each subscription he paid for. If I’m not misunderstanding something, your code only keeps track of the current, not expired subscription. Each time there is a new subscription for a given ID, the value in the keychain is overwritten.

        Maybe you have some idea for that or maybe it is already possible.

  • VA27

    MK, If I’m only using non-consumables, do I leave everything else (i.e. consumables & auto subscriptions in .plist) as provided, and just change my non-consumable feature in the .plist & the config.h file?

  • Savetree Eatbeaver

    Hi, how this code will work with auto-renewable subscriptions on multiple devices with one App Store account? Will it be possible to restore subscription on second device?
    And what about situation when user reinstalls app – will his subscription be restored on that device?

    • Anonymous

      There is a method to restore all purchases in MKStoreManager.
      Writing this off my head, the method is this.
      [[MKStoreManager sharedManager] restore____];

  • rudeboy_

     auto-renewable subscriptions  and subscriptions are not the same product, the second is consumable and then you have to restore the transaction (how ask apple non-sense not to give a customerId), auto-renewable subscription are not consumable.

  • rudeboy_

    @26b828316c3df6107a352e5b436a71f7:disqus Savetree Eatbeaver you have to a grown and use magic server side and user side, on the first run ask the user to restore, then restore and try to grab the original_transaction_id into your apple_transaction_table_history

  • rudeboy_

    Savetree Eatbeaver you have to be a grown up and use magic server side and user side, on the first run ask the user to restore, then restore and try to grab the original_transaction_id into your apple_transaction_table_history where you keep everything on a successful transaction receipt and response, take it as free consulting 😎

  • Grandslam

    MK,
    When buying a feature using:
    [[MKStoreManager sharedManager] buyFeature:kFeatureAId onComplete:^(NSString* purchasedFeature) { NSLog(@”Purchased: %@”, purchasedFeature); } onCancelled:^ { NSLog(@”User Cancelled Transaction”); }];
    if the feature, kFeatureAId is an auto-renewable subscription, will it automatically do the subscription checks? (i.e. verify active/inactive) or do we have to do something else?
    Also, is there a way to just call the completionBlock and not the cancelBlock (when using buyFeature), a parameter or something? Because just calling it as is seems to execute both blocks or at least that’s what is displayed in the logs… sorry, still trying to wrap my head method with blocks wrap in a singleton, etc… any help, explanation would be appreciated greatly… 

    • Ndrantotiana Rindra

      I wonder toot if “buyFeature:” message/method too work if “featureId” is an “auto-renewable subscription feature id”. Is it working for you?
      Thank you in advance.

  • Savetree Eatbeaver

    Hi, i have problem with consumable purchases. I can’t make more then one purchase of one product. Trying to but something second time i get “You’ve already purchased this but it hasn’t been downloaded”. What is going on?

  • Savetree Eatbeaver

    Hi, i have problem with consumable purchases. I can’t make more then one purchase of one product. Trying to but something second time i get “You’ve already purchased this but it hasn’t been downloaded”. What is going on?

  • Savetree Eatbeaver

    Hi, i have problem with consumable purchases. I can’t make more then one purchase of one product. Trying to but something second time i get “You’ve already purchased this but it hasn’t been downloaded”. What is going on?

    • Anonymous

      This is a common problem with consumables and sandbox.
      Try with a diff test user account.

    • Savetree Eatbeaver

      Wow. Thats just great… how can I be sure my code will work in real App Store. I’m Disappointed and frustrated in Apple right now :(

  • Savetree Eatbeaver

    Hi, i have problem with consumable purchases. I can’t make more then one purchase of one product. Trying to but something second time i get “You’ve already purchased this but it hasn’t been downloaded”. What is going on?

  • Savetree Eatbeaver

    Hi, i have problem with consumable purchases. I can’t make more then one purchase of one product. Trying to but something second time i get “You’ve already purchased this but it hasn’t been downloaded”. What is going on?

  • Alxndr

    My app pulls in objects into the app, and depending on the in-app purchase the number changes. By default it comes with 200, and you can add 500, 1000, 2000, 3000, or 4000. For some reason though the app reads it as if I purchased all the features when I check with NSLog, but returns the default value. As soon as I buy one of the features though it always returns the last purchase’s number (4000). Am I doing something wrong, it worked with my other app.

    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #1b5a5e}
    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; min-height: 13.0px}
    p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #368288}
    p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #e90000}
    p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo}
    span.s1 {color: #d200a5}
    span.s2 {color: #000000}
    span.s3 {color: #368288}
    span.s4 {color: #824725}
    span.s5 {color: #3900de}
    span.s6 {color: #480085}
    span.Apple-tab-span {white-space:pre}

    if([ MKStoreManager isFeaturePurchased:kFeatureAId]) {

    totalObjectsAvailableOnServer = 500;

    NSLog(@”Feature A was bought.”);

    }

    if ([ MKStoreManager isFeaturePurchased:kFeatureBId]) {

    totalObjectsAvailableOnServer = 1000;

    NSLog(@”Feature B was bought.”);
    }

    if ([ MKStoreManager isFeaturePurchased:kFeatureCId]) {

    totalObjectsAvailableOnServer = 2000;

    NSLog(@”Feature C was bought.”);
    }

    if ([ MKStoreManager isFeaturePurchased:kFeatureDId]) {

    totalObjectsAvailableOnServer = 3000;

    NSLog(@”Feature D was bought.”);
    }

    if ([ MKStoreManager isFeaturePurchased:kFeatureEId]) {

    totalObjectsAvailableOnServer = 4000;

    NSLog(@”Feature E was bought.”);
    }

  • Singular

    Analyze in Xcode 4 has a few complaints:

    MKStoreKit/MKSKProduct.mObject sent -autorelease too many timesObject sent -autorelease too many timesMKStoreKit/MKStoreManager.mPotential leak of an object allocated on line 248 and stored into ‘request’Branch condition evaluates to a garbage value

  • In this post, you’ve said that MKStoreKit now uses keychain. You may want to update README.mdown, since it still says that user defaults are used.

     Also, README’s formatting is completely broken on GitHub’s built in README renderer. If you want me to, I could patch the formatting and set it back (perhaps in a clone).

  • Chin Petaling

    A Subscription entry in the plist has a value. In your example, it contains 7. What does it do?

    • Anonymous

      It’s the number of days the subscription is valid (in days).

  • Sreekanth Thopucherla

    Hello Thanx for your very helpful MKStoreKit, is there any way to find out how many days a subscription left(for auto renewable subscription).

  • Ghirigoro

    Is there a way to have the auto-renewable subscriptions check for renewal without requiring password input on the part of the user?

    • Anonymous

      MKStoreKit 4 does that automatically without the password prompt

      Sent from my iPad

      • Ghirigoro

        When I start the app I check for the status of the subscriptions and occasionally the password prompt appears right at the splash screen which I assume is caused by MKStoreKit trying to refresh the subscriptions (I don’t make any calls to storekit except via MKStoreKit). I’m not restoring the transactions or purchasing at that point so is there anything else that could be causing that within MKStoreKit?

        • Ghirigoro

          It’s the app store’s fault… When working with the sandbox server occasionally the pop-ups occur in situations that wouldn’t happen on the live version. Basically it’s a bug with Apple and not with MKStoreKit.

      • Ndrantotiana Rindra

        Hello. Tahnk you for your work in MKStoreKit. It is very usefull and helpfull.
        I wonder if using your framework/library:
        1- Could I disable using PList file because all my product data is generated dynamically from web service.
        2- While disabling PList file, could I directly call:

        [[MKStoreManager sharedManager] buyFeature:@”AutoRenewableSubscriptionProductId”
        oncomplete:^(NSString * purchasedFeature) {…}
        onCancelled:^{…}]

        even though the product is an auto-renewable subscription?

        Thank you in advance.

        Regards

  • Doron Katz

    HI mate, how do I set up an observer to test kSubscriptionInvalidated etc? And with restorePurchases for subscriptions, whats the logic? Do we call that in appDelegate first?

  • Hi Munguth! Thanks for this fine piece of code! Works wonderfully in XCode 4.1. One suggestion, though: i totally undestand using the keychain for storing info about past purchases, but it really makes testing on ios devices a pain, because a test purchase sets the “purchased” flag for iap regardless of itunesconnect account credentials and after just one purchase, further testing is not possible. Naturally i managed to add my own flag, so that’s not a problem, but having a config in MKStoreConfigs.h to allow NSUserDefaults instead of the keychain would be awesome sauce.

    • Anonymous

      There is a method in MKStoreKit to remove all keychain entries created by it.

    • Silly me! I’ve missed the removeAllKeychainData method 😉

    • Thanks for quick reply, Munguth :-) How about “canMakePurchase”? Is it implemented in MKStoreKit? Couldn’t find it.

      • Anonymous

        Can you be a little more specific?

        • Sorry for being vague. What i meant was to ask whether MKSK has built in parental lock check or not. Namely: [SKPaymentQueue canMakePayments].

  • MarcusBaily

    Hello, great framework! I just use these three commands and it seems to work!

    initialize: [MKStoreManager sharedManager];

    check: BOOL bought = [MKStoreManager isFeaturePurchased:kFeatureID];
    buy: [[MKStoreManager sharedManager] buyFeature:kFeatureID onComplete:^(NSString* purchasedFeature) { /*ok*/; } onCancelled:^{ /*cancel*/; }];

    Now I have some questions about understanding it correctly:

    1. Is the “bought”-information for the IAPs cached on the device, so I can use it offline too, or do I need further steps for that?

    2. At the moment I use the non-consumables list in the MKStoreKitConfigs.plist. Is there a way to define the list during runtime, so I don’t need to update the app to add extra IAPs?

    3. For Blocks to work on an old device, I had to install “GCC 4.2 (Plausible Blocks)”, so I will have to do that on every machine I use for development.
    I could use an older version, but not sure if it is worth the trouble. What would be different if I switched back to version 3 of MKStoreKit?

    Any help or advice is most welcome!

  • MarcusBaily

    Hello, great framework! I just use these three commands and it seems to work!

    initialize: [MKStoreManager sharedManager];

    check: BOOL bought = [MKStoreManager isFeaturePurchased:kFeatureID];
    buy: [[MKStoreManager sharedManager] buyFeature:kFeatureID onComplete:^(NSString* purchasedFeature) { /*ok*/; } onCancelled:^{ /*cancel*/; }];

    Now I have some questions about understanding it correctly:

    1. Is the “bought”-information for the IAPs cached on the device, so I can use it offline too, or do I need further steps for that?

    2. At the moment I use the non-consumables list in the MKStoreKitConfigs.plist. Is there a way to define the list during runtime, so I don’t need to update the app to add extra IAPs?

    3. For Blocks to work on an old device, I had to install “GCC 4.2 (Plausible Blocks)”, so I will have to do that on every machine I use for development.
    I could use an older version, but not sure if it is worth the trouble. What would be different if I switched back to version 3 of MKStoreKit?

    Any help or advice is most welcome!

  • Greatrat00

    I’m getting a crash in the buyFeature method when isAllowed boolValue is called… Has anyone experienced this? Here’s the code snippet.

    [MKSKProduct verifyProductForReviewAccess:featureId                                                             
                                       onComplete:^(NSNumber * isAllowed)
         {
             if([isAllowed boolValue])
             {
     

    • Anonymous

      Can you explain a bit more?

      • Greatrat00

        Hi MugunthKumar,

        Thanks for the development!

        I’ve been trying to get it working and running into a few issues. This is one of them. I have followed every step outlined to get my IAP setup on iTunes Connect, etc. I have also implemented the server side PHP and MYSQL changes.

        I’ll fill you in on some more info to see if you can help me get started. Literally, the only thing I do after including the “MKStoreManager.h” file is run [MKStoreManager sharedManager] as soon as my application loads. Then, to buy the feature, I call the buyFeature method, which (in debug mode) shoots me to a crash on the [isAllowed boolValue] line, as if the NSNumber *isAllowed is not pointing to a NSNumber object.

        If I comment this ‘if’ and its corresponding ‘else’ out I’m able to verify existence of my UDID registered on my server.

        Other symptoms…

        1) When I ask the Apple Server for my product info, I get a “Problem in iTunes Connect Configuration for product: XXX”. This is a problem, but I don’t see how this is causing what I outlined above.

        2) When I register my UDID on the server and comment out the aforementioned if and else the UDID being registered is detected. However, if I delete the UDID entry from the server, instead of going into the onComplete portion of the method above, it goes to the onError part and “Review request cannot be checked now: (null)” is displayed in the console. Subsequently, the feature is added to the payment queue and I get errors processing the payment I believe due to the same reason problem 1) is occurring.

        Any thoughts?

        Thanks,
        greatrat00

        • Greatrat00

          Mugunth,

          Any ideas?

        • Greatrat00

          Mugunth,

          Any ideas?

        • Jeff

          I also have the same issue now, although it finds my products and returns its price, ID and name…

  • Have you tried running analyser on your code? it appears that there are a few minor problems like, releasing an autoreleased string and stuff, I know that as before using your lib my app had zero leaks, now after purchasing is done there appear a few minor leaks in Run with Instruments… please confirm if I am wrong.

    • Anonymous

      Are you using the latest code on Github?
      I fixed the string auto release stuff there.

      • yup that might be the case, thanks for pointing out, keep it up.

  • Drag

    Hi i’m having a problem, the product is realized but when i use 

    [[MKStoreManager sharedManager] buyFeature:kFeatureAId onComplete:^(NSString* purchasedFeature) { NSLog(@”ok”); } onCancelled:^{ NSLog(@”cancel”); }];can’t see the login, just doesn’t happe nything? do you have an idea what can be my problem?thanks

  • Ghirigoro

    I’m having a problem where subscriptions that haven’t been bought yet always result as being active. I’m not sure but it seems to be a bug in how isSubscriptionActive is implemented (could be wrong about this though I can’t say I fully understand how MKStoreKit works).

    The issue is that when you call isSubscriptionActive on a MKSubscriptionProduct it uses the product’s receipt dictionary to figure out the purchase date. If the product hasn’t been purchased yet then the receipt is nil and so is the purchase date causing all the calculations for purchase date to return 0 – including the amount of time since purchase.

    Here’s a walkthrough the code that might explain this more clearly:

    -(BOOL) isSubscriptionActive
    {
        // IF THE THERE’S NO RECEIPT THE STRING WILL BE NULL    NSString *purchasedDateString = [[self.verifiedReceiptDictionary objectForKey:@”receipt”] objectForKey:@”purchase_date”];
        NSDateFormatter *df = [[NSDateFormatter alloc] init];

        //2011-07-03 05:31:55 Etc/GMT
        purchasedDateString = [purchasedDateString stringByReplacingOccurrencesOfString:@” Etc/GMT” withString:@””];    
        NSLocale *POSIXLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@”en_US_POSIX”] autorelease];
        [df setLocale:POSIXLocale];
        [df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];    
        [df setDateFormat:@”yyyy-MM-dd HH:mm:ss”];
            // THE STRING IS NULL SO THE RESULTING DATE WILL BE NULL
        NSDate *purchasedDate = [df dateFromString: purchasedDateString];
        [df release];
            // SINCE THE DATE IS NULL THIS EVALUATES TO 0/(-86400.0) WHICH PRODUCES -0
        NSTimeInterval numberOfDays = [purchasedDate timeIntervalSinceNow] / (-86400.0);    // SUBSCRIPTION DAYS WILL ALWAYS BE LARGER THAN -0 SO AN UNPURCHASED SUBSCRIPTION WILL ALWAYS BE ACTIVE
        return (self.subscriptionDays > numberOfDays);
    }

  • Jeff

    Great tutorial!

    I am still not sure how to subscribe/purchase an auto-renewable?In the buyFeature:(NSStr… method the verification call is [MKSKProduct verifyProductForReviewAccess:featureId while shouldn’t it be [MKSKSubscriptionProduct verify…Thank you

  • Ghirigoro

    Has anyone gotten MKStoreKit to notice when auto-renewable subscriptions renew. This might be the wrong way to do it (I would love to hear about a better way if anyone knows of one) but this is what I’m doing in my app: every time the app starts up it asks MKStoreManager whether a subscription is active. After the expiration MKStoreManager says that the subscription is inactive, but when I go to repurchase it the App Store informs me that the subscription is still valid (since it was renewed automatically). 

    Am I:

    A) Not using the right process for detecting autorenewals?
    B) Incorrectly assuming that MKStoreKit can detect when a subscription auto-renews?
    C) Noticing a bug in MKStoreKit?

    Any help on this would be greatly appreciated.
    Thanks

    • Anonymous

      A) You are not using it correctly.
      Once you setup Auto-renewables on MKStoreKit you can forget about it. You will get a kSubscriptionInvalidNotification when a subscription expired (because the user revoked it)
      MKStoreKit cannot detect when a subscription auto-renews. So it waits for Apple to post a renewal receipt and whenever it receives one, it remembers it in keychain. If it doesn’t receive one and the current receipt expires, you should get a NSNotification as I described before

      • Mattiaromeo1

        Thanks a lot for the response. Much appreciated. I’ll try waiting for the notifications instead.

        P.S. – Just so I understand correctly I should not ask the manager whether a subscription is active or not. I should track the state of subscriptions with separate persistent variables and then toggle them when I get a successful purchase callback or expiration notification?

        • Anonymous

          You don’t have to track the state of subscriptions. This tracking is done automatically for you by MKStoreKit. Call isSubscriptionActive
          If this returns no don’t allow access. If it returns yes, allow access. Subscribe to the two notifications and allow access accordingly. isSubscriptionActive retuns YES or NO based on “last saved receipt”

          This might be from the last run as well and could be old. To be safe, subscribe for notification and allow access if “auto-renewed” receipt was received by MKStoreKit *after* the call to isSubscriptionActive. The opposite can also happen. isSubscriptionActive returns YES and then AppStore never sends a “auto-renewal” receipt and in that case, MKStoreKit posts a SubscriptionInvalid notification.
          just change the UI state based on these variables. Persisting them is done by MKStoreKit

          • Ghirigoro

            What you describe (checking isSubscriptionActive: when testing for access) is what I am currently doing, but I think I may understand what’s causing the issues I’m having. 

            Correct me if I’m wrong but it seems like MKStoreKit only checks with the AppStore for receipts at start-up (which makes perfect sense). The sandbox subscriptions only last for 5 minutes so it’s likely that they expire while the app is running but MKStoreKit hasn’t had a chance to communicate with the AppStore and ask for receipts.

          • Ghirigoro

            How do I know if “auto-renewed” receipt was received by MKStoreKit *after* the call to isSubscriptionActive? 

            Will MKStoreKit post a subscription purchased notification?

            Thanks,

          • Ghirigoro

            Sorry I found out it does. Disregard the post.

  • Just wanted to say “Thank You!”. My app with IAP managed by MKStoreKit has been approved and everything seems to be OK :-)

  • Doug

    This is a great API, but I hit a snag.  Everything works fine in the “sandbox” but now that the app and IAP is live, I’m seeing this in the log on my phone:

    error: Error Domain=SKErrorDomain Code=0 “Cannot connect to iTunes Store” UserInfo=0x195cc0 {NSLocalizedDescription=Cannot connect to iTunes Store}

    Now, I guess I could take this message literally, but I was able to purchase the app itself on a different device but again not the IAP.

    Would you happen to know if it takes a longer for IAP’s to be enabled in the store with the release of an app or could it really be that it can’t connect to the store?

    • Anonymous

      Check if your firewall is blocking iTunes
      With the same wifi are you able to view App Store on device/mac?

      • Doug

        Yes, everything works fine otherwise.  I even turned off wifi on the phone and still receive the same error.

        • Anonymous

          The problem could be temporary.
          Check with friends in other countries.

          • Doug

            ah…works now.  i think there might be a lag from when the app is approved.  i saw it in the app store coincidentally before i received the ready for sale email.  thanks for the help.

          • Anonymous

            Could be. It often happens when you are outside of US.
            Apple uses distributed cloud servers and your IAP configured on US server through iTunes connect needs to be migrated properly to all locations worldwide.

  • It will be used for new user to write about using Secure framework by your MKStoreKit.

  • Ghirigoro

    Is there a way to know when MKStoreKit is done checking with the AppStore for an auto-renewal receipt?

    I have a situation where I need to display some information immediately after the app’s splash screen that is contingent on the user’s subscription status. If I check isSubscriptionActive: MKStoreKit may not have had time to check with the app-store and the subscription can incorrectly be considered invalid (e.g. if the user’s subscription has expired and MKStoreKit hasn’t had a chance to get the renewal receipt from the AppStore).

    What I need is a way to tell MKStoreKit to check the receipt status of the subscriptions and of letting me know when it’s done. Is there any way to do this (or something similar that lets me know when MKSToreKit has finished updating any necessary info)?

    • Anonymous

      What is isSubscriptionActive returning?

      • Ghirigoro

        isSubscriptionActive: returns the proper value it’s just that it doesn’t have time to update (ie. If the subscription has expired since the last time the app was run isSubscriptionActive will return NO even if the user autorenewed) I need a way to know that the store kit has updated its receipts and that the subscription information I have is current. I know you had mentioned the notifications in the past but those don’t let me know when the update process is done (ie. In the scenario above the subscription is invalid notification will fire immediately but I have no way of knowing how long to wait to see if the subscription is purchased notification will come and tell me that the subscription was renewed).

        Thanks in advance for any help with this

  • Damjan Ivanac

    Great work with MKStoreKit. I have just started to implement it into an existing project I have. I do have one question though. Based on the readme file and instructions all I need to do is add a few lines to make a purchase.

    But do I need to add my in-app purchases into the plist file? The reason I ask is that the app is for a catalogue of manuals/docs/etc. So the catalogue is downloaded from the internet as an XML feed. The in-app purchase id is an attribute in the xml for each item. I have tried calling 

    [[MKStoreManager sharedManager] buyFeature:kFeatureAId onComplete:^(NSString* purchasedFeature) { NSLog(@”Purchased: %@”, purchasedFeature); } onCancelled:^ { NSLog(@”User Cancelled Transaction”); }];

    but id doesnt work of any device. Do I need to add the in-app purchase id to the plist before I can call the code above?

    Any help is appreciated.

    • Damjan Ivanac

      Can anyone help?

    • Bagusflyer

      I guest you have to replay kFeatureAId to the product id. Am I right?

  • Steve

    Great job with MKStoreKit, it has been very useful with my current development. Without doubt I will be encouraging my company to donate some money to fund further development!

    One query though I have is with the actual purchase itself. I have implemented the store kit and modified it slightly to fit in with my app. However, when you end the app process, it doesn’t seem to “save” the purchase record on the device – so when checking if the feature is purchased it returns ‘NO’. Is this a restriction of the sandbox environment? Or am I missing something?

    Also ‘isSubscriptionActive’ returns yes, even before a purchase has been made – I even done this on a device I had never ran the app on before.

    Thanks.

    • Ghirigoro

      There’s a bug in iSubscriptionActive. If there’s a nil receipt (which is the case if you’ve never purchased anything) then it will always return YES. I describe the bug in detail in a comment below. The fix seems pretty easy though: just return NO if the receipt is nil.

      • Anonymous

        This bug is fixed long back.

        • Ghirigoro

          I just looked at the method and the bug is still there. Have you updated the files on Github yet?

          • Saijithendra Gogineni

            Yes, I too observed that bug. It is always returning YES if we never purchased anything.

          • Anonymous

            I just pushed the bug fix to Github.
            Please update your copies.

          • Steve

            Thanks Mugunth, much appreciated.

  • Ghirigoro

    Is there a way to restore in-app subscriptions using MKStoreKit? When I use the restore method the callback gets invoked when MKStoreKit has finished downloading the transactions from the app store but before it’s validated them. This means if you check isSubscriptionActive when the callback is called then the subscription is still inactive (i.e. it may be valid but MKStoreKit hasn’t had a chance to determine that yet). 

    Has anyone successfully used MKStoreKit to restore in-app purchases? And if so could you share how?

    Thanks in advance for any help

  • Saijithendra Gogineni

    Hi mugunth,

    I am using your framework for auto-renewal subscription. Subscription is working fine.But it is not auto-renewing my subscription. Do I need to do something when I receive SubscriptionExpired notification?

    Thanks,
    Sai Jithendra.

    • Ghirigoro

      The subscriptions probably are getting autorenewed but MKStorekit hasn’t had time to retrieve the receipts and verify it. The only solution to this is I was able to come up with is to ttrack the subscription status separately from MKStoreKit and not react immediately to the expired message (I.e. if I receive the expired notification make a note of it so that the next time the app starts I know the subscription has expired but assume the user has autorenewed and let them keep using the subscription for this instance of the app).

      • Saijithendra Gogineni

        Thanks Ghirigoro,

             I got an idea by seeing your reply. 

      • Saijithendra Gogineni

        Thanks Ghirigoro,

             I got an idea by seeing your reply. 

      • Saijithendra Gogineni

        Thanks Ghirigoro,

             I got an idea by seeing your reply. 

      • Saijithendra Gogineni

        Thanks Ghirigoro,

             I got an idea by seeing your reply. 

    • Ghirigoro

      Oh also just in case you weren’t aware of this test subscriptions are only renewed 5 times on test accounts. After taht you have to create a new test account to test auto renewal

  • Ghirigoro

    When restoring auto-renewable subscriptions MKStoreKit occasionally produces false positives where it considers valid but expired receipts as purchases. This is consistent, certain receipts will always trigger a purchase notification and be considered active even if they’ve expired and are marked with the 21006 status code (valid receipt but expired subscription).

    This is kind of a major issue since once a user gets one of these receipts that throws MKStoreKit off, they can just restore to get free subscriptions in perpetuity.

    • Anonymous

      Hmm this looks like an issue. Will have a look at this.

      • Is this still an issue? From what I can see in the code, the verification (on the device) is only checking the date, and if the code 21006 is returned, MKStoreKit does not send the notification for invalid subscription.

  • Saijithendra Gogineni

    Hi,

    MKStoreKit is checking for receipts or for active subcription only if network is available….It is returning active if no network available. So that features are getting used by users if no network available because it is returning that subscription is active. Is there anyway to get rid of this one using MKStoreKit?

    Thanks in advance.

  • lifjoy

    After calling buyFeature, the transaction sometimes fails, and my onCancelled block is called.  From my onCancelled block, how can I gain access to the transaction.error so I can notify the user of the specific issue?

  • Wonderful tutorial. Thank you for sharing.

  • K Andreas

    Hello, what do I need for MacOS Lion? I saw includes like “UIKit.h”….in keychain utils.

    But I thought on Lion this is the way its done now…..

    thx

    • Anonymous

      Im working on Lion support.

      • K Andreas

        When will it be finished roughly?

  • I started having a strange problem with MKStoreKit: after updating my app – no previos MKSK code touched – i’m getting strange error in sandbox. Namely:

    Review request cannot be checked now: (null)
    User cancelled transaction:
    error: Error Domain=SKErrorDomain Code=0 “Nie można połączyć się z iTunes Store” UserInfo=0x2daf20 {NSLocalizedDescription=Nie można połączyć się z iTunes Store}

    I didn’t change a single line of code regarding IAP – only gave more features to users who already purchased IAP.

    iTunesConnect is properly configured, no changes made since last succesful implementation of MKSK.

    Any ideas on what is happening?

    I’m using the most recent MKStoreKit from GitHub. The connection to App Store on my Mac and other iPhone is OK. I haven’t really changed anything and now this strange issue occurs.

    • Anonymous

      Not to worry about.
      Either that the sandbox servers are down now. Or that your iTunes test user account has expired. Try creating a new account. If that doesn’t work, try again tomorrow.

      • Thanks for a quick reply, Munguth :-) After i read your post i started lurking on Apple Devleoper Forums and it turned out that they’re having some issues with sandbox and iap at the moment. Creating a test user for an american app store solved my problem.

        Btw. Your library rocks and it’s easy to use! Thank you once again! :-)

  • Why is there no dictionary variable that exposes all the SKProduct objects so you can see their localizedDescription and localizedTitle values?

  • Yousef77

    hi
    first i would like to thanks you,

    i had 2 issue
    1- isSubscriptionActive should wait till MKStoreManager sharedManager] is properly initialized
    ie if you init  [MKStoreManager sharedManager] then call immediately isSubscriptionActive it will return false even though the sub. is active,you have to wait for about 10 more or less then check for isSubscriptionActive;
    2- the observer kSubscriptionsInvalidNotification not getting fired
    i init the MKStoreManager then add the observer but nothing happens when the sub. is deactivated also the sub. is active always after purchase even if i deactivate it and wait 5 minutes (1 month sub.)i do not know if this is an issue or i have problem in my codethanks

    • Yousef77

      ok about problem two had to add a few lines to support minutes for testing in sandbox
      in (BOOL isSubscriptionActive

      int numberOfMinutes = [purchasedDate timeIntervalSinceNow] / (-60.0);

      #ifndef NDEBUG
          return (self.subscriptionDays > numberOfMinutes);
      #else
          return (self.subscriptionDays > numberOfDays);
      #endifone thing if anyone could help please is how to check for latest receipt to detect if the auto-renewal occurredwhen first initializing the MKStoreManager it will check for any new receipt but after that nothing will be checked cause the app will work in bg and when the user start the app again it will not initialize the MKStoreManager Again (the init is don in the delegate method) and self.subscriptionDays will not reset to zero so isSubsribActive will always return NO??please help

  • Robert Ölei

    Is it possible to keep track of all user subscriptions? I want to give the user access to issues that fit the period of each subscription he paid for. If I’m not misunderstanding something, your code only keeps track of the current, not expired subscription. Each time there is a new subscription for a given ID, the value in the keychain is overwritten.Maybe you have some idea for that or maybe it is already possible.

  • Robert Ölei

    Is it possible to keep track of all user subscriptions? I want to give the user access to issues that fit the period of each subscription he paid for. If I’m not misunderstanding something, your code only keeps track of the current, not expired subscription. Each time there is a new subscription for a given ID, the value in the keychain is overwritten.Maybe you have some idea for that or maybe it is already possible.

  • hh1122

    Does anyone have an example of what the conditional logic would look like if the subscription is active.  ie: 

    if ([MKStoreManager isFeaturePurchased:@”com.app.subscription.1month”])
        {
            //content
        } 
        else 
        {             //Subscribe}or if ([[MKStoreManager sharedManager] isSubscriptionActive:@”com.app.subscription.1month”])    {        //content    }     else     {             //Subscribe}I’ve tried these variations, and a handful of others.The console is returning the subscription is active.  Any help would be appreciated.