restoreCompletedTransactions broken? - iphone

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.

Related

How to restore purchases using MKStoreKit

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.

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

finishTransaction: isn't removing the transaction from queue...

I have several non-consumable inApps.
The payment is done once (correctly: SKPaymentTransactionStatePurchased), then the download of the inApp is correctly done (and finished) and finnaly I call
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
however the transactions remain in the queue... why? aren't they supposed to be removed? also the following isn't being called:
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
I've also tried to implement in my finishtransaction method:
for (SKPaymentTransaction *aTransaction in [[SKPaymentQueue defaultQueue] transactions])
{
[[SKPaymentQueue defaultQueue] finishTransaction:aTransaction];
}
(in case the transaction object i was trying to finish wasn't the correct one, but this also fails to remove the transactions - well most of the times, sometimes it does remove but the behaviour is inconsistent, I can't figure out why they are removed the few times they are actually removed)
From your question, it appears that you are trying to download something when purchase completes. In this case, you should probably call finishTransaction before the download starts.

iPhone inApp Purchase Queue won't clear out

I have InApp purchasing setup in my app. I am having some weird behavior though. Each time I start up the app I call
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
to setup the initial observer. However this immediately triggers
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
with a full array of every transaction. I have tried just calling
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
on each of these transaction then restarting the app again but paymentQueue is still trigger as soon as I call addTransactionObserver. My main goal right now is just to flush the transaction queue and start clean. I don't know how I got into this state, nor how to get out of it.
Make sure you have implemented this method:
-(void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions;
From SKPaymentTransactionObserver:PaymentQueue.. call:
SKPaymentQueue.default().finishTransaction(transaction)
Note that you can not call for all types. Calling finishTransaction for .purchasing will crash with error. So a for through all transactions is not a complete solution.
When you Call finishTransaction for .purchasing:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot finish a purchasing transaction'
Put this somewhere to flush the queue (possibly your load method, but remove it in your final app):
for (SKPaymentTransaction* transaction in [[SKPaymentQueue defaultQueue] transactions]) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
When finishTransaction is called, the observer's removeTransaction method will be called when each one is moved.
I had 30 in my queue and I ended up getting 30 calls to that method.
I think your issue has to do with the Sandbox User accounts in iTunes Connect. If you don't have any sandbox users, this guide was really helpful getting them setup:
https://support.magplus.com/hc/en-us/articles/203809008-iOS-How-to-Test-In-App-Purchases-in-Your-App
If you have Sandbox Users:
You can try calling finishTransaction: however, you need to make sure the SKPaymentTransactionState of the transaction is acceptable.
Per the documentation on finishTransaction :
// Asynchronous.
// Remove a finished (i.e. failed or completed) transaction from the queue.
// Attempting to finish a purchasing transaction will throw an exception.
So to safely remove completed transactions:
for transaction in SKPaymentQueue.default().transactions {
guard
transaction.transactionState != .purchasing,
transaction.transactionState != .deferred
else {
//Optionally provide user feedback for pending or processing transactions
return
}
//Transaction can now be safely finished
SKPaymentQueue.default().finishTransaction(transaction)
}
The documentation on .purchasing and .deferred is fairly vague:
case purchasing // Transaction is being added to the server queue.
case deferred // The transaction is in the queue, but its final status is pending external action.
From what I understand, handling pending and/or processing transactions should be fairly passive. The app has done everything it has needed to and is waiting on a response from the iTunes Store server, or some other dependency(i.e. payment authorization).
paymentQueue: updatedTransactions: will get called on the queue's SKPaymentTransactionObserver when the transaction is updated.
As far as how your transaction queue got stuck in limbo, I'm willing to bet all the transactions in your queue are in state .purchasing. This is most likely a bug within iTunes Connect/Sandbox users/Production iTunes Accounts. Others, including myself, have had this issue as well. There is a bug report filed for it. Try recreating/changing the password of your sandbox user, or create a new Sandbox User for testing.
More info here:
https://forums.developer.apple.com/thread/70418
You have to be sure you finish the transaction every time you make a purchase or you restore a product. If you run into this problem, you should clean up your queue and then develop the logic appropriately. A quick clean up could be obtained by running something like this in swift 3. (which is the same as the previous answer). But it is not supposed to be in your real app.
func cleanUp() {
for transaction in SKPaymentQueue.default().transactions {
SKPaymentQueue.default().finishTransaction(transaction)
}
}
Also you have to add and remove your observer in appDelegate. That is the recommendation and the best to avoid problems.

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.