How to restore purchases using MKStoreKit - iphone

My app was rejected from apple because it does not have a restore button, but I am using MKStoreKit, so if the app is purchased and the the device is wiped and the user click my purchase button again, it does re-download and 'restore' the app.
So, can someone explain what they are asking me to do? I thought MKStoreKit handled this for me.
Thanks

Just call restorePreviousTransactionsOnComplete
#import "MKStoreManager.h"
-(void)restorePreviousPurchase{
[[MKStoreManager sharedManager]restorePreviousTransactionsOnComplete:^{NSLog(#"RESTORED PREVIOUS PURCHASE");} onError:nil];
}

Somewhere in your app you need to add a restore button, which will allow the user to restore there previous purchases.
MKStoreKit does all this, but you will need to add the button to UI your self.
Then you can call the restorePreviousTransactionsOnComplete:onError: method on MKStoreManager

- (IBAction) RestoreCompletedTransactions:(id)sender
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
Not sure about the button though, I guess mine is always displayed.

Related

In-App purchase auto renew transaction restore problem

I am developing an application in which user can purchase auto-renewable subscription. Buying part is woking alright but problem occurs when user deletes the application and tries to restore his purchases. following is the code I wrote to handle that.
I have given a button titled "Already a Subscriber". When user tap that I call following code.
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
And this is how I handle restore in - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions method.
case SKPaymentTransactionStateRestored:
duration = [strDuration intValue];
if(transaction.transactionReceipt != nil){
receipt = [[NSString alloc] initWithData:[b64 dataByBase64EncodeFromData:transaction.transactionReceipt] encoding:NSASCIIStringEncoding];
[userDefault setObject:transaction.transactionReceipt forKey:#"LastReceipt"];
[queue finishTransaction:transaction];
[self callReceiptInfoImpl];
}
break;
Following is delegate method when restore transaction is completed.
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
NSLog(#"COMPLETED TRANSACTION RESTORED");
}
Problem is that, when user clicks button "Already a subscriber" then Step 1. gets called but Step 2. never gets called. Finally I can see message "COMPLETED TRANSACTION RESTORED" on screen.
If anybody has faced similar problem then please guide.
Thanks for reading.....
Within your 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.
from this link

restoreCompletedTransactions broken?

Is restoreCompletedTransactions broken in SDK 4.3 ?
I am trying to restore my auto-renewable subscription. It is not resulting in callback to updatedTransactions. Here is my code.
{
....
[appDelegate.inapp loadStore];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
....
}
Expecting callback to updatedTransactions, but do not receive it.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
NSLog(#"IN updatedTransactions, transaction.transactionState");
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
...
...
case SKPaymentTransactionStateRestored:
NSLog(#"IN updatedTransactions, SKPaymentTransactionStateRestored");
[self restoreTransaction:transaction];
break;
}
}
}
But I do receive call to this at the end.
-(void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
}
This can still(1) happen if you're testing in the Sandbox environment and your Test User is broken. Create a new Test User in iTunes Connect, and try again.
It's not entirely clear what makes Test Users go bad; from what I gather, using them once in a non-sandbox environment can do this, but there may be other reasons as well.
(1) Xcode 4.3.1, iOS SDK 5.1
Make sure to add observer before using [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
Your code should be something like:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
I was able to get around this but its very odd. On my testing device im getting bugged by iTunes to log in to multiple test user accounts. I usually just hit cancel and test my restore with the account that I want to restore. This time I decided to enter the password for these other annoying login boxes. It worked and next time I relaunched my app and hit restore it did not bug me to log in to the other test accounts, just the one I was testing. I proceeded with the restore and it did restore the items I wanted to see.
Id say were experinecing stupid sandbox issues and also we know apple has changed in app purchasing in general. Keep playing around with it, you'll get it to work out.
Hope this helps you out.
Products Consumable cannot be restored.
Products Non-Consumable can be restored.
Check if your products are type Non-Consumable.
You should not need the updatedTransactions callback if you are getting paymentQueueRestoreCompletedTransactionsFinished. The "queue" has a list of your transactions and you can loop thru those.
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
for (SKPaymentTransaction *transaction in queue.transactions)
if ([myItem.productID isEqualToString:transaction.payment.productIdentifier])
myItem.purchased = YES;
}
Make sure your build number in Xcode does not have a space in it. For example "1.0 Beta". With a space updatedTransactions will not be called. Also receipt verification can fail.
Two additional points which weren't mentioned here:
If you are using Firebase make sure you add an observer before you initialize Firebase, https://firebase.google.com/docs/analytics/get-started?platform=ios
Make sure that you call finishTransaction method while handling the purchase. You won't be able to restore not completed transactions.

regarding inapp purchase

I have developed one app on iphone.
I have loaded its lite version on app store.
Now i cant to load full version from app itself, how it can be done.
I know this is possible but how this can be done.
You can do it in two ways. You have have lite and full as two separate apps, or you can have in-app upgrade. Depending on how you implement the restrictions of Lite, either one or the other might be a better choice.
In the two-app scenario, you develop Full as a separate bundle, with a separate bundle id. Xcode targets and conditional compilation might come in handy for this. In the Lite version, you want to hard-code a link to the full version in the App Store. Find out the link by copy-pasting from iTunes Connect.
In the second scenario, full/lite is a run-time setting, and the upgrade package is an in-app purchase. Implement StoreKit API, provide the hard-coded in-app product ID. Once the upgrade purchase is noticed (it's an asynchronous process), flip the setting to Full.
I'd recommend the second approach. For one thing, all reviews would go under Lite version, as opposed to splitting between Lite and Full. Also, if your app has settings or data files, those won't be lost on upgrade.
EDIT: here's how implementation of in-app purchases might look like. Somewhere in the app there's an Upgrade button (link, image, whatever) that goes like this:
if(![SKPaymentQueue canMakePayments])
{
//Display the "cannot pay here" message
return;
}
#try
{
[[SKPaymentQueue defaultQueue]
addPayment:[SKPayment paymentWithProductIdentifier: #"com.mydomain.myproduct.full"]];
}
#catch (NSException * e)
{
//Sorry...
}
Meanwhile, you need a class in your app that handles the StoreKit notifications. I mentioned that it's async; well, it still is. In my case, I used the AppDelegate for that:
#interface MyAppDelegate : NSObject <..., SKPaymentTransactionObserver>
And you designate the app delegate as one:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
//...
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
And then you implement the transaction processing method:
- (void)paymentQueue:(SKPaymentQueue *)q updatedTransactions:(NSArray *)tr
{
int i, n = [tr count];
for(i=0;i<n;i++)
{
SKPaymentTransaction *t = [tr objectAtIndex:i];
if(t.transactionState == SKPaymentTransactionStatePurchased)
[self ProcessPurchase: t.payment.productIdentifier];
//Support for restored transactions
if(t.transactionState == SKPaymentTransactionStateRestored)
[self ProcessPurchase: t.originalTransaction.payment.productIdentifier];
//Failed/purchased/restored
if(t.transactionState != SKPaymentTransactionStatePurchasing)
[q finishTransaction:t];
}
-(void)ProcessPurchase:(NSString*)ProgID
{
if([ProgID compare:#"com.mydomain.myproduct.full"] == 0)
{
//It's an upgrade! Change the settings, enable hidden content, and stuff...
}
}
Something like this.
You need to create separate bundle id for your full version. as these are two different application.

iPhone consumable product is behaving like a non-consumable product (already purchased...)

Our app has a list of locked products that share the same consumable product id (i.e. one consumable product id for many products). Our server provides me with a list of products and the product id associated with them:
item name="itemA" iphoneProductId="consumable.test.1"
item name="itemB" iphoneProductId="consumable.test.1"
item name="itemC" iphoneProductId="consumable.test.1"
We chose consumable because our items are created dynamically and need to be available to the user instantly (please don't reply suggesting that we use non-consumable, there are a lot of other reasons that are too hard to explain without me giving away private details about the company we are working with, as to why we are using consumable). This allows us to have multiple products share the same price.
When the user purchases itemA (for example), the item is unlocked. However, sometimes, when the user then tries to be itemB, Apple return with 'You have already purchased this but it hasn't been downloaded. Tap OK to download it now’. This should surely never happen for a consumable item. I know our system is quite complex but as far as the apple store kit is concerned, are simply just buying the same product again.
Could this just be a sandbox issue? We can't test in live as the app isn't released yet. In fact, this whole problem is holding off the release as our client is as concerned as we are about this problem.
I've followed the same code from the iphone documentation and the few in app purchase tutorials out there. I see that a lot of people on the forums seem to have witnessed the 'already purchased' dialog above for consumable products, but none of them ever get answered.
Please help! Thanks
The problem is that you are never finishing the transaction. You need to remove it from the queue.
Like:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
I'll assume you've called -[SKPaymentQueue finishTransaction:].
Off the top of my head, there are a few possibilities:
The store hasn't received the "finish" message yet (I don't know what ordering guarantees there are).
You're trying to purchase a second item before calling finishTransaction: on the first, and the App Store thinks it's a replay.
The App Store detects when you appear to be purchasing the same item repeatedly, and assumes that something must have gone wrong.
A hack is to cycle through a list of several (10? 100?) consumables, assuming that when you get back to one earlier in the list, it will have finished processing.
An alternative solution is to pre-allocate a lot of non-consumables ("product.1", "product.2", ..., "product.100") and assign ones with the appropriate price to products on the server. Changing prices can then be done on iTunes Connect, or by assigning additional product IDs as necessary.
Thanks for your quick response, but my app does seem to be finishing the transaction, etc.
My project's in app purchase classes have become quite complex so I reverted to creating a new basic project with a test consumable product and the standard implementation of in app purchase manager/observer taken from this open source:
http://blog.mugunthkumar.com/coding/iphone-tutorial-%E2%80%93-in-app-purchases/#idc-cover
The same problem occurs. This is the order:
1. Buy the consumable product for the first time (below is the debug I printed out)
_MKStoreManager: buyFeature:test.consumable.1
__MKStoreObserver: SKPaymentTransactionStatePurchased
__MKStoreObserver: completeTransaction
_MKStoreManager: provideContent
2. "Thank you for your purchase" dialog is displayed by Apple
3. Buy the consumable product for the second time:
_MKStoreManager: buyFeature:test.consumable.1
__MKStoreObserver: SKPaymentTransactionStatePurchased
__MKStoreObserver: completeTransaction
_MKStoreManager: provideContent
__MKStoreObserver: SKPaymentTransactionStateFailed
__MKStoreObserver: failedTransaction
4. "You've already purchased this but it hasn't been downloaded. Tap OK to download it now. [Environment: Sandbox]" dialog is displayed by apple.
It just doesn't make sense in step 3 that the transaction is both purchased then failed at the same time. Do you have any ideas.
It is not worth using consumable product for non-cosumable, because Apple will reject it. Here is what happened to me:
I have had the same issue. The message explaining that you have already purchased the item appears only once in a while and mostly if you purchase a lot of things - one after the other immeadiately.
We have put it for review in the App Store anyway and got the following answer:
.....
We have completed the review of your in-app purchase but cannot post it to the App Store because the Purchasability Type is not set correctly. For information on Purchasing and Currency guidelines, please see section 11 of the App Store Review Guidelines [ https://developer.apple.com/appstore/resources/approval/guidelines.html ].
.....
The purchase of a [magazine issue] is set to "consumable", however based on product functionality it should be set as non-consumable instead.
.........
You are required to create a new in-app purchase product with the correct purchasability type.
..........
I hope this saves someone time and headaches.
I was facing the same problems. The mistake I did was deallocating the purchase before the finishTransaction was sent. Make sure you handle the transaction result after finishing the transaction
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
BOOL success = YES;
for (SKPaymentTransaction *transaction in transactions){
switch (transaction.transactionState){
case SKPaymentTransactionStatePurchased:
success = YES;
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code == SKErrorPaymentCancelled){
if(DEBUG) NSLog(#"Transaction failed => Payment cancelled.");
}else if (transaction.error.code == SKErrorPaymentInvalid){
if(DEBUG) NSLog(#"Transaction failed => Payment invalid.");
}else if (transaction.error.code == SKErrorPaymentNotAllowed){
if(DEBUG) NSLog(#"Transaction failed => Payment not allowed.");
}else if (transaction.error.code == SKErrorClientInvalid){
if(DEBUG) NSLog(#"Transaction failed => client invalid.");
}else if (transaction.error.code == SKErrorUnknown){
if(DEBUG) NSLog(#"Transaction failed => unknown error.");
}else{
if(DEBUG) NSLog(#"I have no idea.");
}
success = NO;
break;
case SKPaymentTransactionStateRestored:
success = YES;
break;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"transaction finished: %#", transaction);
}
if(success){
// do something
}
}
Hope that helps some of you.
Cheers,
K.
You should put this line [[SKPaymentQueue defaultQueue] finishTransaction:transaction] after the transaction over whether you are doing fresh purchase or restore purchase.
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
make sure you have added this method on the controller handling requests.

SKPayementQueue: restoring transactions finishes without calling 'updatedTransactions' in release config but not debug config

I'm debugging restoring transactions and in my debug configuration everything works normally:
IE I call:
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
sometime later the queueCalls:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
and sometime after that it calls:
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
and everyone is happy.
BUT in my release configuration, I never see the call to updatedTransactions and so I never actually restore the purchases.
possibly related, after I attempt the restore and it doesn't work. I restart the application and I find that I don't get a response when I ask the store for a product list.
This error condition had nothing to do with the configuration. It is an intermittent bug in store kit.
Carl, as you said it seems to be an intermittent bug.
However, I have also found out that it doesn't happen (or at least I haven't seen it yet) if I test it using the US Store. I've been using a UK test user and today it was failing miserably every single time. Created a US test user, and, after being switched to the US store automatically, it works perfectly again.
It is not a fix, but it may be useful ;)
Do you add your payment object in the payment queue in this way?
SKPayment *payment = [SKPayment paymentWithProductIdentifier:"Your Product identifier"];
[[SKPaymentQueue defaultQueue] addPayment:payment];
If you are using this way,then the UpdatedTransactions is called right after adding the payment object in the payment queue.You dont have to call it explicitly.It is handeled by store kit.