I am using in-app purchase for an iPhone app. I have a class that acts as SKProductsRequestDelegate
and SKPaymentTransactionObserver
, and it\'s all
I'm wondering, is the defaultQueue
guaranteed to be the same queue passed in paymentQueue:updatedTransactions:
? If not, then perhaps the issue is with calling finishTransaction on a different SKPaymentQueue than the one the transaction originated from.
This issue was also raised in the developer forums, and the general conclusion was that it was down to a difference in the handling of transactions in iPhone OS 4.0. The problem only seems to occur when there is a significant delay between receiving a notification of the finished transaction and calling finishTransaction
on the payment queue. In the end we didn't find an ideal solution, but what we did was this:
As soon as the transaction arrives, process it and record a value in the user preferences if processing was successful.
The next time that transaction appears in the queue, which may not be until the next launch of the app, immediately call finishTransaction
on it.
Our products are "non-consumable" so it's enough to check that the product paid for is valid and error-free to safely ignore any 'undead' repeated transactions from iTunes. For consumable products one would need to save more information about the purchase, such as the original payment date, to make sure that future transaction notifications can be matched to purchases that were already processed.
I had this problem too. The bug turned out to be on my side. The problem was that there was a past transaction lurking around that had been executed (the content provided) but not cleaned up using finishTransaction
. Unfortunately, on asking at several places including an Apple TSI, I discovered that there was no way to poll such 'undead' transactions - you just had to register for notifications and wait for the corresponding paymentQueue:updatedTransactions:
. This complicated my code, but not by much.
What I do now, which has been working fine:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self]
[[SKPaymentQueue defaultQueue] addPayment:payment]
paymentQueue:updatedTransactions:
check the state variable. If it has not been set, it means you have received a notification for a past payment. In that case, honor that payment rather than pushing a new one.This method naturally assumes that you have the time to wait for old transactions to show up before starting a new transaction.
I had the same issue but I solved it.
Here's my code:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
break;
default:
break;
}
}
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSLog(@"completeTransaction... %@", [[transaction class] description]);
[self provideContentForProductIdentifier:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
NSLog(@"restoreTransaction...");
[self provideContentForProductIdentifier:transaction.originalTransaction];
}
I was then calling finishTransaction: method inside provideContentForProductIdentifier: method. And in case of restore transaction I was calling finishTransaction: to the originalTransaction object not the transaction itself.
I solved my issue by this code (method restoreTransaction:)
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
NSLog(@"restoreTransaction...");
//Pass the main transaction object.
[self provideContentForProductIdentifier:transaction];
}
Check that you're not simply adding the observer multiple times. I had the same problem with multiple updatedTransactions, but then I noticed I was adding a new observer each time in didBecomeActive. And it was called once each time I for example restored purchases in sandbox.
I using this code and it working for me
if ([[SKPaymentQueue defaultQueue].transactions count] > 0) {
for (SKPaymentTransaction *transaction in [SKPaymentQueue defaultQueue].transactions) {
@try {
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
} @catch (NSException *exception) {
NSLog([NSString stringWithFormat:@"%@", exception.reason]);
}
}
}