Corrado Corrado - 4 months ago 28
iOS Question

Restoring in-app purchases bug: restores even if not purchased

One (honest) user has just informed me that it's possible to "buy" the in-app purchase of my app by simply pressing the RESTORE button even if the in-app purchase has not been purchased.
This is the code I'm using (that I thought it was sufficient ... but I was wrong):

- (IBAction) restore {

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}


- (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *) transactions {

for (SKPaymentTransaction *transaction in transactions) {

switch(transaction.transactionState){

case SKPaymentTransactionStatePurchasing:

NSLog(@"Transaction state -> Purchasing ...");

break;

case SKPaymentTransactionStatePurchased:

NSLog(@"Transaction state -> Purchased");

[self activatePurchase];

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

break;

case SKPaymentTransactionStateRestored:

NSLog(@"Transaction state -> Restored");

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];


break;

case SKPaymentTransactionStateFailed:

NSLog(@"Transaction state -> Cancelled");

if(transaction.error.code == SKErrorPaymentCancelled) {

//the user cancelled the payment ;(
}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

break;

case SKPaymentTransactionStateDeferred:

NSLog(@"Transaction state -> Deferred");

break;
}
}
}




- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {

[self activatePurchase];

}


Is there something that I'm missing?

Answer

The payment observer method paymentQueueRestoreCompletedTransactionsFinished is called when the payment queue is finished restoring transactions, regardless of whether any transactions were actually restored. You will get one call to paymentQueueRestoreCompletedTransactionsFinished every time you call restoreCompletedTransactions even if there were 0 calls to updatedTransactions.

As a result, you are calling activatePurchase even if there was no purchase.

The correct place to activate the purchase is in the SKPaymentTransactionStateRestored case in updateTransactions; just as you do for the SKPaymentTransactionStatePurchased case.