Last week, Apple announced that in-app purchases will be available for free apps as well. This could probably free developers from creating “lite” and “pro” versions of the app and allow developers to “unlock” features inside the app and create business models that the AppStore haven’t seen. This model could be a great boon for developers like us to upsell our apps (provided they are of good quality) and to reduce piracy. In this article, we will look at how to incorporate StoreKit to do In-App purchases for your iPhone Application.
Update: Did it really free you from creating “lite” and “pro” versions? How did you manage to give away free copies of your in-app purchases for reviewers? Read my tutorial on how toenable in-app purchases for free
Apple allows three types of purchases within the app and Apple terms them as consumables, non-consumables and subscriptions.
Consumables are products that are “consumed” immediately. This means, if the purchase is made today, and the user wants to purchase it again tomorrow, he will be charged again when he attempts a purchase.
Non-Consumables are features that are purchased exactly once. Apple automatically takes care of “remembering” those purchases and allows the user to purchase them again for free, just like downloading apps you already purchased.
Subscriptions are the most complicated part, They behave like non-consumables during the subscribed period and like consumables after that. You as a developer have to ensure that anything that is subscribed by the user is available across all of his iTunes synced devices when they are purchased from one device. Hence, do not lock in-app purchases to UDIDs. This might even get your app rejected. The StoreKit, as on date, doesn’t have any built-in mechanism to do it automatically which is why subscriptions are a bit tougher to develop.
One important point to note is that, in-app purchases cannot be used to deliver product updates. Changes to the binary has to be separately submitted. However, if you are a game developer, game data, maps, levels and other “data files” are allowed for in-app purchase.
In this post, we will focus on how to prepare your app for enabling features for the “pro” version from the “lite”. Or technically, we will focus on how to bring in, consumables and non-consumables into your app. We will leave the subscriptions part to another blog post as it’s quite complicated and involves some server side programming as well.
Preparing your iTunes Connect
To start with, in-app purchases, you need to do some ground work on your iTunes Connect account. A three step process that Apple thinks every developer should know. (Unfortunately I couldn’t find any official documentation for this)
Step 1:
First is to create an App ID and enable in-app purchases for that. This App ID shouldn’t have any wild card characters or else, the in-app purchases option will be grayed. I would always recommend to use a different App ID for every application you create.
Step 2:
Create provisioning profiles (Development and Distribution) using this App ID. Again it’s a good practice to create different provisioning profiles for every app. There are many tutorials on how to create this here and Apple’s own documentation here (This link will prompt you to login).
Step 3:
You need to create product references in your iTunes account. Each individual in-app purchase should be uniquely identifiable. Apple recommends using the reverse DNS notation, something like, com.mycompany.myiproduct.ifeature Before creating product ids, you need to associate it with a existing application in AppStore. If your app is not yet live, you can create a dummy, placeholder application, fill in the metadata (which you can anyway change it later) and check “upload binary later”.
To create a new in-app purchase, open your itunes connect and choose “Manage In-App purchases”. Choose the app for which you want to setup in-app purchases and click next. You should see a screen like this.
The reference name is the name that appears during the in-app purchase prompt. Any name like, Levels 10 – 50 will be fine. The Product ID should be unique. This is used for reporting as well as within your app for requesting a purchase (more on this later). You can select the type as consumable or non-consumable. When you say an in-app purchase is consumable, your users will be charged everytime they purchase it. This is perfect for a radio app that requires users to pay for listening to a song everytime. If it’s a product feature, set it as non-consumable. Non-consumable products are purchased exactly once. When the user attempts to purchase it again, it will be delivered to him for free.
Type in the other required detail in this page and click save.
Step 4:
The fourth and final step is to create test user accounts. After you program the app, you might want to test the app. You can use these accounts to login to the App Store. The purchases will be processed as if it were real but no financial transactions will take place.
This completes your iTunes connect configuration. Take a deep breath. We have just started. A lot more to go.
Writing the StoreKit Code
Now that you have setup the iTunes account, let’s start by writing the actual code to interact with the AppStore and allow users to make purchases. For coding help, nothing beats the Apple’s official StoreKit programming guide. However, the guide has one bug that crashes the program. We will see how to circumvent it properly here.
Step 1: Adding StoreKit.Framework
The first step here is to add the StoreKit.Framework to your project.
Step 2: Parental Controls
It’s important to check whether the iPhone/iPod doesn’t have parental control restrictions. When you try to initiate a purchase when parental controls are on, you might crash your app. The apple docs code seems to have an error here. The function, canMakePayments is a static method of class SKPaymentQueue and not a member function. The working code looks like this.
if ([SKPaymentQueue canMakePayments]) { ... // Display a store to the user. } else { ... // Warn the user that purchases are disabled. }
Step 3: Retrieving the product information and populating the UI
Now the you have designed a gorgeous looking view, you can show it to the user using, say presentmodalviewcontroller or any similar method. Do note that StoreKit doesn’t provide the UI to be displayed. It’s upto the developer to design the UI. The first step is to query your “In-App Purchases” and show the user, the available list of options. Retrieving the product information from AppStore is a couple of lines of code as illustrated here.
- (void) requestProductData { SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: kMyFeatureIdentifier]]; request.delegate = self; [request start]; } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response { NSArray *myProduct = response.products; // populate UI [request autorelease]; }
The kMyFeatureIdentifier you pass to initWithProductIdentifiers is the uniquely identifiable product id you created in your AppStore account. Remember the reverse DNS product id? You CANNOT specify a wild card character here. So, if you have multiple products, you have to initialize the request object with a list of product using
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObjects: myGreatFeature1, myGreatFeature2, nil]];
You can pass as many features as possible to setWithObjects and be sure to end the last object with nil.
Setting the delegate to self and calling the start method will invoke the “didReceiveResponse” delegate. The delegate will give you an array of products and the request itself. Use the array to populate your store UI and release your request here. (This is the same request you created initially).
Step 4: Adding a Transaction Observer
This is a very important step. You can do this as soon as your app is open or when you start a “In-App” purchase session. Do note that, in-app purchase requests are continued even if the user quits the app in between. So imagine a case, when user buys an item, but before the transaction is processed, he gets a phone call that interrupts everything. Though the actual transaction is not interfered (as it happens on Apple servers), your application will never know what happened. To ensure that you get all transaction notifications, (completed/ pending/restored), you have to register a class that receives the callbacks from AppStore. This class should implement and it’s delegate methods.
This is the code for registering the your store observer.
MKStoreObserver *observer = [[MKStoreObserver alloc] init]; [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
(If you can’t follow anything here, just head below for the real source code, but it’s advised that you read through this, understand and then use the code in your app).
Step 4: Implementing the callback
Within your MKStoreObserver class, you have to implement the callback function paymentQueue:updatedTransactions:
This function will receive updates on the transactions as and when it’s made. Because your transactions take place even when the app is closed, you should be ready to receive these notifications as soon as you open the app. So the best place is to initialize it in applicationDidFinishLaunching or equivalent method.
Now in the updatedTransactions functions, handle the three types of transactions, purchased, failed and restored.
- (void)paymentQueue:updatedTransactions(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: // take action to purchase the feature [self provideContent: transaction.payment.productIdentifier]; break; case SKPaymentTransactionStateFailed: if (transaction.error.code != SKErrorPaymentCancelled) { // Optionally, display an error here. } // take action to display some error message break; case SKPaymentTransactionStateRestored: // take action to restore the app as if it was purchased [self provideContent: transaction.originalTransaction.payment.productIdentifier]; default: break; } // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } }
Purchase and failed are seemingly straightforward. You will receive a restored transaction message when your app was quit before the transaction was completed. You should always do the “same” thing when your purchase is new or restored. If you want to charge your users for every download, then probably, set the in-app purchase to be a “consumable” item. But be sure to use that only for purchases that are really “consumable”. Like a live radio show or a podcast and not for unlocking additional levels. Users expect that a level they have unlocked will stay forever.
Three things to note here
1) You should remove the transaction from the payment queue after the transaction is complete. Otherwise, the transaction will be re-attempted, which is not what the users expect (and your app will most likely get rejected).
2) You should provide the content (or unlock the feature) before completing the transaction. When you receive the message SKPaymentTransactionStatePurchased, it means that the users’ credit card has been charged. It’s high time that you provide the feature.
3) You should not display an error when the transaction fails because the user rejected it. Display any error message only when,
if (transaction.error.code != SKErrorPaymentCancelled)</code>
Apple recommends you not to display an error, because that is not an error. The user has purposely cancelled the transaction (probably because the price was too high?). You can audit it and go on. But for heaven sake, don’t display an error like, “Unable to process transaction because user cancelled operation!”. It will be soooo Windozy…
Step 5: The actual purchase
Now that your architecture is ready, you can go ahead and initiate the purchase by calling the function below when the user clicks your “Buy” button on the UI.
SKPayment *payment = [SKPayment paymentWithProductIdentifier:myGreatFeature1]; [[SKPaymentQueue defaultQueue] addPayment:payment];
Optionally, you can add something like payment quantity for “consumable” items.
payment.quantity = n; // number of "items" that user wishes to purchase.
Finally, after providing the feature, you should “remember” that the user has purchased the app. Apple’s recommended way is to use NSUserDefaults, the same way you store your settings.
Testing your app
Alright. Now let’s test the app. Note that, the app cannot be testing from iPhone simulator. The StoreKit communicates with AppStore.app to complete transactions which is not present in the Simulator. So connect your iPhone and run the app.
Remember step 3 you did in iTunes connect? You created some test accounts? Well, they are used for testing your app. Before using that, sign out of AppStore by opening Settings.app -> Store -> Sign Out.
Start the app. (You should NOT sign into the AppStore with the test user account. It will anyway ask you to provide your credit card which we are not interested in)
Open the Store UI and initiate the purchase. You will get a prompt like, “Do you want to buy 1 “ABC feature” for 2.99$? Tap Buy. You will be prompted to login. Provide your test account login. The AppStore provides a secure connection to the iTunes account and notifies you whether the purchase was successful through the callback paymentQueue:updatedTransactions. When you use the test account, the scenarios will be exactly same expect that no one will be charged. These test accounts run inside a sandbox.
In case your app got quit by a phone call, the transactions continue and you will get a restoreTransactions the next time your app is opened. This is why you should start listening to transactions as soon as you open the app (not just when the user opens the store UI)
That’s it. I wouldn’t say its easy. But for developers who have some intermediate knowledge in iPhone development and Objective C, it shouldn’t be a big deal. Remember that when your app is free, you get 10 times more downloads (as reported by admob statistics). When people actually use your app, there is a high chance that they buy your in-app features. That’s upselling becomes easy. Given that this is a very strong business model for the already saturated AppStore, you should start incorporating this model into your code soon.
Source code
The source code, MKStoreKit, contains four files. MKStoreManager.h/m and MKStoreObserver.h/m. The StoreManager is a singleton class that takes care of *everything* Include StoreKit framework into your product and drag these four files into the project. You then have to initialize it by calling [MKStoreManager sharedStorageManager] in your applicationDidFinishLaunching. From then on, it does the magic. The MKStoreKit automatically activates/deactivates features based on your userDefaults. When a feature is purchased, it automatically records it into NSUserDefaults. For checking whether the user has purchased the feature, you can call a function like,
if([MKStoreManager featureAPurchased]) { //unlock it }
To purchase a feature, just call
[[MKStoreManager sharedManager] buyFeatureA];
It’s that simple with my storekit. The source code will be uploaded when this post is read by at least 1000 people. Please spread the word.
As always, all my source code can be used without royalty into your app. Just make sure that you don’t remove the copyright notice from the source code if you make your app open source. You don’t have to attribute me in your app, although I would be glad if you do so
Downloads:
Version 2:MKStoreKit V2.0
Version 1:MKStoreKit.zip
Troubleshooting
Despite all this, In-App Purchases remain a biggest and the most PITA situation for any iPhone developer. If you can’t get it work, check whether your “didReceiveResponse” delegate returns the product id you passed as invalid. You can confirm this by adding a NSLog statement inside the delegate. Double check if your invalid product id returned here matches the product id you created in iTunes connect. If they are same, check if your product id on iTunes connect says “Cleared for Sale”. For this, you have to provide a screenshot and “Developer Approve” it.
Another case is, if this is your first app, then chances are that, your “Paid Applications Contract” isn’t yet in effect. If this is the case, you have to wait till Apple approves your bank details.
If these two doesn’t work, you might have to wait for 12-24 hrs, till Apple propagates your iTunes connect information to all it’s servers. See the last line in this documentation for more details.
If you have issues adding this to your application, you can hire me to do it for you. I charge a nominal fee of 30$ per hour and this work shouldn’t take more than a couple of hours.
Update 1: (22-Oct-2009): Code changes to fix a crash that occured in most cases.
Update 2: (7-Dec-2009): Added link to MKStoreKit v2.0.
–
Mugunth
Related posts:
- iPhone Tutorial – Enabling reviewers to use your In-App purchases for free In-App purchases is a great way for developers to upsell...
- iPhone Tutorial – UISearchDisplayController with NSPredicate Though UISearchDisplayController is seemingly easy (and yes it’s easy), apart...
- iPhone Tutorial: Follow Cost API and a open source wrapper What is Follow Cost? Follow Cost is a interesting and...
- iPhone Tutorial: Elegant way to send formatted In-App email By now, most of you know how to send emails...
- iPhone Tutorial – In-App Email codeproject Sending an email from your iPhone application is...

Social comments and analytics for this post…
This post was mentioned on Twitter by mugunthkumar: New post on my blog: #iPhone #Tutorial In-App Purchases http://mk.sg/1m (Please RT)…
Please enable the source. your tut is incomplete.
The source is up!
[...] a tutorial for the same… iPhone Tutorial – In-App Purchases | blog.mugunthkumar.com __________________ Developer of iPhone Apps, Windows Apps, Web Apps… [...]
Really thanks for that.
Regards,
nottoday
Hi ,
Thanks for wonderful explanation. As per Step mentioned above:
To create a new in-app purchase, open your itunes connect and choose “Manage In-App purchases”. choose the app for which you want to setup in-app purchases and click next. You should see a screen like this.
As my app is still underdevelopment so how can i create the the product IDs for testing the code.
Thanks
Sam
You can actually create a dummy \”placeholder\” app in iTunesConnect and don't upload binary. Then create product id for that app. Thanks for your question, I'll update the blog post accordingly.
Sent from my iPhone
In [MKStoreManager sharedStorageManager]; where is sharedStorageManager defined?
I'm sorry, it should be [MKStoreManager sharedManager]; Thanks for pointing it out.
Hi,
Thanks for the post really coverts the basics to get started.
I am just a little confused when in comes to testing this out. "…Open the Store UI and initiate the purchase…" do I do this on the device or on with iTunes? ( not sure how the test account that I created comes into effect) if you could maybe provide a little more screenshots that would he me out a bunch.
You have to try this on the device. Store UI is the UI within your own App. Try an "in-app" purchase from your app and see the sequence of steps.
Hi, Itried this code but just get this error and app crashes:
'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
Any ideas?
Regards iain.
Sorry, it crashes when it hits this line:
[[SKPaymentQueue defaultQueue] addPayment:payment];
Are you running this from your device? It *will* crash on simulator.
did you get all of your product id in 'response of product id request'
No, I am running it from the device.
Is your payment object nil?
I am getting the same error at the same point.
NSLog(@"buyFeature-canMakePayments: %@ : %@", featureId, payment);
[[SKPaymentQueue defaultQueue] addPayment:payment];
NSLog outputs :
com.rockvideomobile.mobiRockVideo : <SKPayment: 0×1a0d00>
then
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
The problem is that finishTransaction will assert when attempting to finish a transaction that is in the SKPaymentTransactionStatePurchasing state. I fixed my problem by not calling finish transaction in this case
That's a great tutorial for In App Purchase. Just a little question regard [MKStoreManager sharedStorageManager].
I read the readme file, and it guide me to put [MKStoreManager sharedStorageManager] at applicationDidFinishLaunching.
My question is "If I'm using a TarBarApplication, and I have many ViewController. I want to call [[MKStoreManager sharedManager] buyFeatureA] from one of the ViewController (i.e. FoodViewController.m) not from AppDelegate. Shall I put [MKStoreManager sharedStorageManager] at applicationDidFinishLaunching or put it at viewDidLoad of the FoodViewController.m.
Thanks
[[MKStoreManager sharedManager] buyFeatureA] this can be called from any view controller. But when the app starts you should initialize the class by calling [MKStoreManager sharedManager]. (You can assign the shareManager to some variable or leave it like that) That function call will ensure that purchases restored from a interrupted sessions come to your app, when the user opens it again. Like, when the user attempts a purchase, and it's interrupted by a phone call, you will receive notifications only when shareManager is not nil. And only with those notifications can you update your internal variables that keep track of the purchase statuses. When you put it ONLY in FoodViewController.m, you will not be notified of restored purchases till the user actually opens FoodViewController.
Hope that helps
OK, thanks for your clear guide. In this same case, if I put [MKStoreManager sharedManager] at applicationDidFinishLaunching. And I want to call [[MKStoreManager sharedManager] buyFeatureA] from FoodViewController.m, shall I call [MKStoreManager sharedManager] again at FoodViewController.m?
you don't have to.
You have to call [MKStoreManager sharedManager] only once. Even if you call multiple times, it returns the same instance.
Actually MKStoreManager is a singleton class.
In your foodviewcontroller.m, you can just call [[MKStoreManager sharedManager] buyFeatureA] directly. Just change buyFeatureA to be something more meaningful and avoid calling buyFeature:(NSString*) productId directly.
The reason why I say this is, if you call buyFeature directly, you will be littering your product id's through out the code. My idea is to collectively have all product ids into the singleton manager class MKStoreManager
Clear!! Understand you design concept.
Thanks for the great work.
Thanks for your post. I'm new to iPhone dev and the StoreKit portion of my app is that last part I am dealing with.
I setup your code but am getting the following error when I do a build.
Check dependencies
[WARN]Warning: Multiple build commands for output file mobirock.app/Settings.bundle
! link
"_OBJC_CLASS_$_SKPaymentQueue", referenced from:
__objc_classrefs__DATA@0 in MKStoreManager.o
__objc_classrefs__DATA@0 in MKStoreObserver.o
"_OBJC_CLASS_$_SKProductsRequest", referenced from:
__objc_classrefs__DATA@0 in subscribe.o
__objc_classrefs__DATA@0 in MKStoreManager.o
"_OBJC_CLASS_$_SKPayment", referenced from:
__objc_classrefs__DATA@0 in MKStoreManager.o
What am I missing?
You have to link StoreKit.Framework in your app and then build it
Sent from my iPhone
OK, sorry. Dumb newby question. Isn't that what #import <StoreKit/StoreKit.h> does? Do I need to "link" somewhere else in xCode?
Yes,
You have to add StoreKit.Framework to your project. The line #import will show an error if you haven't added the framework.
Sorry, another dumb question. Where do you add StoreKit.Framework? I have searched all over xcode. I found frameworks listed but thats not one of them. I have the latest xCode. Do I need to download it from somewhere?? Did a search in the iPhone Dev center and it revealed nothing. I am using the 3.1.2 iphone SDK.
I am an experienced javaScript, Flash ActionScript and PHP developer and new to the xCode.
I am building an application, which shows a couple of picture from my server.
user can purchase a ‘pixel area’ to upload there photo, replacing the existing picture in my application.
i am stuck @ a point, “how this purchasing will be done”.
plz tell me a stepwise direction.
Thanks. Opened up the list but Storekit isn't in there. Do I need to download it from somewhere? Doesn't it come with the SDK?
It comes with the SDK. Are you on the right folder? Your folder path should show you which SDK you are browsing in. Even if you have the latest XCode, chances are that, you might be opening a 2.x SDK folder (which will not be removed by installing the latest XCode). Look into the correct folder. I'm not on my mac now. I can't tell exactly which folder it will be present
Thanks. Found it. Had to click other and then really search. Was really buried deep.
I have done the preparation, such as define a special AppID, a specific Developer provision for this In App Purchase application, define my In App Purchase Item at iTunes Connect, include StoreKit to framework, import storeKit.
While I test my In App Purchase, my program get SIGABRT, and after trace the program, the program crash at
MKStoreManager.m line 139,
[[SKPaymentQueue defaultQueue] addPayment:payment];
The debugger console show: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
I only change MKStoreManager.m at line 18 to
static NSString *featureAId = @"com.mycompany.FoodSearch.enableSendLocation";
Looks there was a same post similar to my issue.
the name com.mycompany.FoodSearch.enableSendLocation should be exactly same as your AppId.
can you post your App ID?
Is it com.mycompany or com.XXX (In my code, mycompany is only a placeholder)
If this name is not EXACTLY same you will get a SIGABRT error.
I have define a dummy application and set two In App Purchase feature rpoducts, the first feature ID is "enableSendLocation", the other one is "enableSaveLocation".
These two ID are well defined except approved because the guide mention that "When you have tested your in app purchase and are ready to approve it and submit it for review, upload a screenshot below. This is for review purposes only and will not be displayed in the Store."
Since my App ID is M93JNT2TV4.com.jasoncom.FoodSearch,
So I define static NSString *featureAId = @"com.jasoncom.FoodSearch.enableSendLocation";
I think your bundle id in your info.plist doesn't match \”com.jasoncom.FoodSearch\”. Though I'm not sure, do try this and let me know. If it works, I'll update my blog. (You can update bundle id by wading through some project properties window or by editing the plist)
Thanks
I think your bundle id in your info.plist doesn't match \”com.jasoncom.FoodSearch\”. Though I'm not sure, do try this and let me know. If it works, I'll update my blog. (You can update bundle id by wading through some project properties window or by editing the plist)
Thanks
My bundle id formally is "com.jasoncom.${PRODUCT_NAME:rfc1034identifier}".
After change it to "com.jasoncom.FoodSearch", the result still same, SIGABRT.
Ha Ha. My current environment/config work for my other applications, but just In App Purchase didn't work.
Am I missing something at configuration?
When I was going through iTunes Connect setup, it specifically said that the "Product ID" needs to be "numeric", not "alphanumeric". Could that be your problem?
Sure, The SKPayment *payment is "<Variable" , very strange.
Below are console log.
2009-10-21 16:36:40.227 FoodSearch[1643:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
2009-10-21 16:36:40.236 FoodSearch[1643:207] Stack: (
844776241,
843056877,
844331917,
844331819,
849152651,
208313,
849150721,
844491911,
849157277,
849151455,
849153177,
206801,
206937,
191677,
844537573,
851058789,
851058693,
851058647,
851057969,
851060293,
851056221,
851054649,
851040559,
851039143,
848378745,
844528685,
844526429,
848374975,
848375147,
850798447,
850793587,
11663,
11596
)
terminate called after throwing an instance of 'NSException'
Program received signal: “SIGABRT”.
kill
quit
run the app in debug mode. Otherwise every variable is shown like \”<Variable\”. Try NSLog([payment description]);
It show <SKPayment: 0×14cd60>, is it what you need?
Yes,
which means SKPayment is not nil. what does NSLog([SKPayment description]) tell? (Email me, my email in CC)
When I continue trace the program, the SIGABRT finally happend at
line 34 of MKStoreObserver.m
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
And the error message is Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
Any advise?
My app to give me "SIGABRT"…i don't know what is wrong…
..if everyone can leave the source code of an Application that already use the In-App Purchase will be my hero!!
this source code works in my app…
After 15 long minutes of debugging, found the answer to the above errors that folks are getting.
If you read the documentation of the SKPaymentQueue class, especially the finishTransaction method, it clearly says "Calling finishTransaction: on a transaction that is in the SKPaymentTransactionStatePurchasing state will throw an exception.". Hence, I made the below change to the switch statement in the MKStoreObserver class to move the finishTransaction method call only if the transaction is Purchased or StateRestored. I must say this is a well written API and masks a lot of complexity around In-app purchase. Thanks to the author.
The switch statement (in file MKStoreObserver.m) now looks like:
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[[MKStoreManager sharedManager] provideContent: transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code != SKErrorPaymentCancelled)
{
[[MKStoreManager sharedManager] failedTransaction: transaction];
}
break;
case SKPaymentTransactionStateRestored:
[[MKStoreManager sharedManager] provideContent: transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
default:
break;
}
I tried putting this code in but am still getting the same error.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
Thanks for the pointers… Made some quick and dirty changes to my code before publishing it and that killed the whole API
Code updated. Please download the new code and do let me know if it works
This is the result of my debugger:
#1 0×32a229a2 in kill
#2 0×32a22994 in raise
#3 0×32a37640 in abort
#4 0×32c6b3b6 in __gnu_cxx::__verbose_terminate_handler
#5 0×3031185e in _objc_terminate
#6 0×32c6977c in __cxxabiv1::__terminate
#7 0×32c697d0 in std::terminate
#8 0×32c6989c in __cxa_throw
#9 0×3031071a in objc_exception_throw
#10 0×32b88b8c in +[NSException raise:format:arguments:]
#11 0×32b88b2a in +[NSException raise:format:]
#12 0×3068ea8a in -[SKPaymentQueue finishTransaction:]
#13 0×000053a6 in -[MKStoreObserver paymentQueue:updatedTransactions:] at MKStoreObserver.m:36
#14 0×3068e300 in __NotifyObserverAboutChanges
#15 0×32bafc86 in CFArrayApplyFunction
#16 0×3068fc9c in -[SKPaymentQueue _notifyObserversAboutChanges:]
#17 0×3068e5de in -[SKPaymentQueue _addLocalTransactionForPayment:]
#18 0×3068ec98 in -[SKPaymentQueue addPayment:]
Anyone have ideas why I give SIGABRT error?
I have a question:
I have an application in AppStore, with bundleID com.mycompany.imath
Now i have added to this app, with iTunes Connect, the relative In App Purchase, with ID 0001.
But when I go to modify my AppID to enable In’App Purchase, it give to me “Unavailable”.
So i have created a new AppID: com.mycompany.imath2 and I have enabled the In-App Purchase.
In the xcode application, I work with the new AppID (imath2) but in iTunes Connect the In App Purchase is registered only for the old AppID…
It can work anyway??
How I can do? If I create a new App in iTunes Connect I can’t publish the update for my App!!
Code updated. Please download the new code and do let me know if it works
I am not sure what you mean by "code updated". BOTH version 1 and version 2 of MKStoreObserver's failedTransaction: still call finishTransaction:.
failedTransaction should be changed as follows:
- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
if (transaction.error.code != SKErrorPaymentCancelled)
{
// Display an error here.
[[MKStoreManager sharedManager] failedTransaction: transaction];// fix to bug below
}
//[[SKPaymentQueue defaultQueue] finishTransaction: transaction];BUG!!!! need to be removed, see posting
}
I am not getting any errors, but it also doesn't appear to be hitting iTunes.
Going to trace through and see what is going on.
Did u log out of AppStore on ur phone before trying it?
Sent from my iPhone
Yes. It is running through your code and triggering – (void) failedTransaction:
Yes. I am logged out.
if (transaction.error.code != SKErrorPaymentCancelled)
{
// Optionally, display an error here.
NSLog(@"failedTransaction- Transaction Failed %@",transaction.error.code);
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
This added NSLog is firing with
failedTransaction- Transaction Failed (null)
transaction.error.code is an NSInteger not an NSString.
I fixed the bug today… Please pick up the latest code. If you fail to call finishtransaction, it will continue to ping apple servers and they may block u out (ddos)
Sent from my iPhone
I did the Steps from your tutorial. I linkes a UITableCells didSelect Action with the method "buyFeatureA". I added the Observer, but nothing happens if I tap onto the Cell. I just added some NSLogs, the method – (void) buyFeature:(NSString*) featureId is getting triggered and the Log NSLog(@"buyFeature-canMakePayments: %@ : %@", featureId, payment);
returns the following String: buyFeature-canMakePayments: com.*****.****.pack1 : <SKPayment: 0×180510>
Can somebody help me please?
This is a great tutorial but dont get how to start as there are 4 files but not any UI part.
Pls tell how to start with UI.
Or with screen shots.
The UI should be developed by you. You can call the function to buyFeatureA or something like that from your code.
How do I call the requestProductData method from my view controller? I tried [class requestProductData] (with class being the instance variable pointing to the class) but it does not get called.
Hi mugunth Kumar,
I have used your code in my application.
my app Id is : com.riseuplabs.apn
my product id are: com.riseuplabs.apn.01 and com.riseuplabs.apn.02
i have create a dummy application in my iTune connect, also it has enable for 'inApp purchase"
i change the following line in your requestProductData method:
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
[NSSet setWithObjects: @"com.riseuplabs.apn.01",@"com.riseuplabs.apn.02",@"01", nil]];
but in response i got that all of my product id are invalid??
plz reply, i am stuck with it since 2 days
——–
waiting for your reply
prince
Not sure what the @"01" is there for. shouldn't it be just:
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
[NSSet setWithObjects: @"com.riseuplabs.apn.01",@"com.riseuplabs.apn.02", nil]];
AndrewMcc did you solve your problem. Got the same here and i'm stuck. It is rather frustrating because the exaclty same piece of code was working 2 weeks ago …
Can it be linked to Apple decision to unlock in-app purchase for free apps ?
Can both of you elaborate the problem? I got this code working in my app. Many have reported success. Some say it worked previously and now it doesn't. I want to isolate the issue and weed out bugs if any.
you need to sign paid agreement contract with all your bank details etc.
i was facing the same problem. got it resolved after doing this
Hi, Thanks for this tutorial.
I’ve submitted the app’s meta data to iTunes connect but have not uploaded my app yet. In iTunes Connect I go to Manage your Applications. The app appears there and I click Manage In App Purchases. My App Id shows up but there is no Bundle Id. I try to Create New but it needs a Bundle Id so I click the link “register here” and it takes me to the main iphone dev page. Any suggestions on how to get the bundle Id registered? I could upload my App (I think) but I’m afraid apple will begin reviewing it and I’m not ready for that yet.
i was able to associate a bundle id by uploading my binary and then immediately rejecting it. however, i have not gotten successful responses from my product request calls, so maybe this isn't the right way to do it…
I did NOT upload any binary and successfully set up BOTH a Bundle ID and a Product ID.
I have 3 apps that I can create a free version by the data contained in a plist. What then I would like to do is have the in app purchase just simply deliver the full plist contents, replacing the free version's plist file.
Is this possible ? I am just trying to understand what the delivery product would be in this case. I understand the coding process of the store, but do not know if something as simple as a plist replacement could be accomplished.
Thank you for any advice.
U can't replace the apps plist within the bundle. To change anything that's \”signed\” you have to resubmit your app. But in your case, you can isolate those entities in the plist into a separate list and activate/deactivate features based on those the users choices. Thisnew list could contain feature names and a bool key stating whether it's purchased or not. You can toggle these values from the storekit.
Sent from my iPhone
Would then the deliverable, be simply a new app with the new feature enabled ? I am just trying to understand the physical deliverable.
Thank you again,
Gene
the same app with the new feature enabled. not a new app. The physical deliverable should be streamed from your own server after checking the purchase.
Ok, so I have a free app, I place inApp purchase within it. When they want to buy it will then get the full featured version of the app as an inApp purchase as defined in my Manage inApp purchases in iTunes Connect and will come from what I uploaded to the iTunes Connect in the inApp upload section. This full featured app will then replace the free version.
Thank you for your time, I am just trying to understand the process of deliverables. The differences in the app that I plan to use this on, is simply a difference in the contents of a plist. The programming logic is identical. The free will have limited data and the full will have complete data.
Thanks a lot, worked great! hope my app will be approved soon.
Hi …I have followed all the steps discussed in your blog but i am getting no data in delegate method:-
“-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response.”
above method is getting called ,but no data is received from i tunnes connect.I tried to create the array from response using “NSArray *myProduct = response.products;” but in array nothing is coming.I am not finding any method to get even the reason of problem from response. What could be the problem and how to get more information from the SKProductsResponse about failure? .
Thanks in advance …
regards
Abhishek.
Hi,
Thanks for saving us hours of coding! works like a charm. But one strange thing is happening when i try to test the in app purchase:
I created a test user account through the Manage User Accounts in iTunes Connect. When you create such an account you have to select a valid storefront for your account. I chose US Store. Now I signed out from the store in App Store settings on my device. Ran the application and tried to perform a purchase. I successfully login with my test account. After I press Confirm when entering my credentials I get an alert, that comes from SKPaymentTransactionStateFailed from the observer. It says "Your account is only valid for purchases in the US iTunes Store". The error state = 0 unknown. Second time when i try to perform the purchase, StoreKit only asks me for a password like the previous login was successful. After entering a password I can perform a purchase. My question is whether it's only because it's a testing account and the application is not actually on AppStore? What should I do to avoid this message or at least to continue the purchasing process?
Any help is appreciated and again thanks for the great post!
Nava
Whenever u change stores u have to reconfirm ur purchases. This applies to apps u purchase thru AppStore as well (not just in app purchases). Try logging out and initiate a purchase from AppStore and type in ur test credentials, the behaviour will be exactly same.
Sent from my iPhone
But why it fails the transaction after the login and how i prevent it? My biggest concern, that it will happen to the user… Anyway to test I need to sign out the AppSore settings on the device, so it's like i'm changing the store… How do i catch/prevent it?
Thanks a lot for your help!
Yes it will happen to your users also. But it's not a bug. Even iTunes on mac/windows behaves like that. Login using, say Canadian store. Then sign out and try initiating a purchase. Give ur us store credentials. Ur purchase will fail and u will be taken to us store home page.
This is by design I think.
Sent from my iPhone
So basically you say, that i should leave it as is or point the user to try again? The original alert from AppStore doesn't let user know what he should do next? I mean saying try again and so on…
How to handle it logically?
Thanks a lot!
I'd like to share from my experience and may be it would be a useful addition to your tutorial:
1. The application's bundle identifier should match the App ID we've created in the developer portal. Say you've create the following App ID: HD5583.com.yourCompany.YourApplication.
In Xcode project the bundle identifier should be com.yourCompany.YourApplication. Without the HD5583 prefix. It's very important. In iTunes when you mention bundle id, it also should say com.yourCompany.YourApplication.
2. Now another very important thing: Mobile provisioning profile. We need to create a new mobile provisioning profile for App ID we've created to sign with it the application.
3. ProductID in your sources. In the example you define the productID as "com.yourcompany.featureA". When preparing array of products, you need to state only "featureA" as product identifier. Other string combinations will result invalid product identifiers.
These are things I would need to figure out by myself. Hopefully somebody will benefit from this as I did from your great tutorial and answer.
>> 3. ProductID in your sources. In the example you define the productID as "com.yourcompany.featureA". When preparing array of products, you need to state only "featureA" as product identifier. Other string combinations will result invalid product identifiers.
Not sure whether this matters. When I finally got it working, i was using the full productID. Another thing I found was that you can't upload the binary then reject it; it won't get reviewed anyway till you've tested/approved the in-app purchase.
also, if you DID reject the binary, you'll need to create a new productID (different one as it won't let you use the old one) after uploading again or it won't work.
StoreKit doesn't seem to work in Ad Hoc distribution. Any idea why?
Yes. I usually comment out the code that calls the store kit while preparing a adhoc build. Not only that, your free app that uses in app purchases cannot be used freely by reviewers. That's promo codes doesn't give free copies of the in app purchase as well
Sent from my iPhone
Hi kumar,
I am in a strange problem!
In my application I used your MKStore class.
And it successfully worked.
but today i change the price of my product in storekit. From then, My application shows “SKPaymentTransactionStateFailed:”
why this happening, I don not know. But I did not change any line of code.
please help……
Wait for about 2-5 hrs after changing price. this happens to Apps as well. When you change the price of the app, your users will not be able to download it for another 2-5 hrs
Hi, purchaseableObjects = NULL when I log it. I have an in-app purchase set up and the correct reverse DNS id. What could the problem be?
I have been attempting an in app purchase for hours now.
Every time, I don't get a log in prompt and the error being passed to the observer is coming in as null! I have no information to debug by.
Here's what I did:
* Set up a new App ID with purchases enabled
* Created the application in iTunes and added an in app purchase
* Logged out of my iTunes account on the device
The strange thing is that my request for the product works great – I get a response back as expected. However, when I add a Payment to the queue, the callback always receives a failed transaction. More frustrating is that the error provided is null!
Here's my observer code:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for(SKPaymentTransaction *transaction in transactions) {
switch(transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[SpeakHereAppDelegate setIsUpgraded:YES];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(@"%@", [transaction.error localizedFailureReason]);
if(transaction.error.code != SKErrorPaymentCancelled) {
[self showAlertMessage:@"Your purchase could not be completed. Please try again later." withCaption:@"Oops!"];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[SpeakHereAppDelegate setIsUpgraded:YES];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
}
Again, the transaction state is always a failure. Anybody have any ideas? This is incredibly frustrating….
Did you try with my source code attached here?
Sent from my iPhone
I have tried your source code (which is great, by the way) and I get the same result – a null error returned and a transaction failure.
The only reason I can think of that it wouldn't be working for is this:
* My bundle identifier is com.mydomain.myapp
* I am testing the app with a provisioning profile specifically for that app id with in-app purchases enabled
* However, the uploaded binary is "v1" of my app and was built with a wildcard provisioning profile – the bundle id of the uploaded app is still com.mydomain.myapp though
Would the fact that the uploaded binary uses a wildcard provisioning profile even have an effect on the ability to test this out? Again, the bundle IDs are a perfect match.
Thanks!
I tried uploading a new application built with a provisioning profile with in-app purchases enabled and still have the same issue.
Not sure where to go from here…
Hi
Thanks for the great code.
I have a question which seems weird. When logging the [[NSUserDefaults standardUserDefaults] boolForKey:featureAId] directly, I get the correct item BOOL, but when following your suggestion of [MKStoreManager featureAPurchased], I dont.
Any idea why or what I am doing wrong??
If you have refactored my code to change variables, check them thoroughly again. The only possibility could be that some other variable is getting set when the feature is purchased.
Sent from my iPhone
Hi, great example and thanks for the great code! I've been able to implement and use the class successfully. However, I'm very confused about how to manage the display of a successful view page. Specifically, the provideContent function simply updates the NSUserDefaults and does not send any other type of notification, nor does it offer a delegate method. My question is 2 fold:
1) how does the view controller know when the payment is complete so it can push a new payment complete view.
2) because the class is instantiated in applicationDidFinishLaunching it can pick up on an interrupted purchase process, but then what view controller gets called upon to push the completed payment view, and how would you go about doing that?
There is a delegate which you can set to receive product purchased notifications.
hmm… do you mean SKPaymentTransactionObserver protocol which is used in the MKStoreObserver which has the method:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
If not, I have no idea what you mean by your response. And if you do mean that function, are you suggesting using it elsewhere… other than in the MKStoreObserver… like in a view controller _as well_?
Maybe my question was confusing. I have a view controller, lets call it checkoutView, with a button that says checkout now, which calls [[MKStoreManager sharedManager] buyFeatureA]. Because the singleton was instantiated in applicationDidFinishLaunching, when the callback from apple is received by your class, and the provideContent function is called, there is no direct way for the singleton class to notify checkoutView so the app can update its display. The same goes for when the app starts up, and the MKStoreManager discovers there is a pending transaction, at that point how are you passing that message to a specific viewController?
I believe there are a few ways to go about this, I'm looking for suggestions, improvements, standard way to communicate with view controllers from a singleton class instantiated in the appDelegate… What I've done for now is use the NSNotificationCenter to blast a notification to the application, and then in the checkoutView and my very first view loaded, I listen for the notification and handle the view logic accordingly.
I added something like this to MKStoreManagers function 'provideContent'(note i'm also passing 'state' into that function now from MKStoreObserver):
NSLog(@"sending notification of purchase");
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:state forKey:@"state"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"Purchased" object:self userInfo:dict];
[dict release];
And then in the view controllers that I want to handle the notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(PurchasedNotification:) name:@"Purchased" object:nil];
- (void) PurchasedNotification:(NSNotification *)notify {
id ndict = [notify userInfo];
id val = [ndict objectForKey:@"state"];
NSLog(@"recieved notification: %@",val);
NSString* errorMessage = @"Your purchase went through but was interrupted, click ok to continue with your purchase";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Interupted Purchase" message:errorMessage delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
[alert show];
[alert release];
}
Does this method follow best practices? Is there a better way? How would you, or anyone else reading this, do it?
Are you using MKStoreKit version 2.0? It has a callback delegate.
So in your view, prior to calling buyProductA, set the delegate to self
MKStoreManager.delegate = self;
Implement theMKStoreKitDelegate (see MKStoreManager.h for its definition) in your checkoutview.
This delegate was added in V2 along with this article.
http://blog.mugunthkumar.com/coding/iphone-tutori...
AH! I was not using V2. And using a protocol/delegate is exactly what I was after. My first attempt to accomplish this was to create a protocol and delegate methods, but I failed in trying to create and implement the protocol within a singleton, so I went with the NSNotification center. But I think delegate methods are much cleaner. Thanks for everything, V2 works great!
maybe update this article with a link to the version2 code. I got here through google search, and didn't notice there was a version 2 or separate blog post. Thanks so much!
Hi Mugunth Kumar,
You have done a great job with this code. Way better than what Apple provides. Thanks for taking the time to do this service for all of us. You can be sure that it is appreciated.
I have it working fine in my application on a test basis. I have not approved the in-app for review yet because when I run the Apple Analyzer on the code, it flags a Potential memory leak.
Here is where the error is flagged:
+ (MKStoreManager*)sharedManager
{
@synchronized(self) {
if (_sharedStoreManager == nil) {
[[self alloc] init]; //THIS ALLOC CAUSES THE POTENTIAL LEAK OF OBJECT MESSAGE IN THE ANALYZER
[MKStoreManager loadPurchases];
_sharedStoreManager.storeObserver = [[MKStoreObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:_sharedStoreManager.storeObserver];
}
}
return _sharedStoreManager;
}
I do not get a leak there running instruments. Is the analyzer just not understanding how the code works at that point?
Also, do you know if having that analyzer message will affect acceptance by Apple. This will be my first submission so I would like to have everything as ready as possible. As I mentioned, except for that message the code compiles and runs fine.
This shouldn't be a problem.
The static analyzer cannot understand where this is deallocated as this is a singleton class and lives throughout the lifecycle of the app.
You can ignore this error safely.
Hi ~
I have the same problem…
but I try your method "turning off wifi just in case. 3G connected to AT&T should have worked"
it doesn't work
my NSLog:
2009-12-21 17:22:21.836 test01[2161:207] transaction.transactionState = 2282224
2009-12-21 17:22:21.840 test01[2161:207] err = (null)
2009-12-21 17:22:24.437 test01[2161:207] response.products.count=(null)
2009-12-21 17:22:24.440 test01[2161:207] response.invalidProductIdentifiers = (
"com.mygame.test01.item01",
"com.mygame.test01.item02"
)
2009-12-21 17:22:25.504 test01[2161:207] transaction.transactionState = 2287488
2009-12-21 17:22:25.507 test01[2161:207] err = Error Domain=SKErrorDomain Code=0 UserInfo=0×22eb70 "無法連接 iTunes Store"
2009-12-21 17:22:25.509 test01[2161:207] failedTransaction- Transaction Failed (null)
P.S. "無法連接 iTunes Store" means "Cannot connect to iTunes Store"
I am stuck from 2days …..
if have solution please tell me
I am very appreciate you
U haven't created product ids in iTunes connect. Or your product ids are incorrect. Remember that product ids are case sensitive.
Sent from my iPhone
Ummm….
I have purchased my product before
but I didn't change anything…(I swear…)
my transaction.state become null…
so…I don't know what happened..
but thank you for reply me so fast
In that case u have to write it to apple. There are cases when they take sandbox servers for non us stores out for maintenance for really long periods of time
Sent from my iPhone
Hi ~
I am thank you somuch
I will write it to apple or redo everything @@
thanks a lot
I got SKPaymentQueue was not declared in this scope… Have I to import a file?