restoreCompletedTransactions not working on real device

I have a nonConsumable subcription item for skpayment.


I tested with sandbox environment for restoreCompletedTransactions.

It works perpect and I submitted new version to appstore, approved.


So I generated promo code for that and redeem it on appstore. But the real device never called "updatedTransactions"


I put nslog to check it from real device.

But the nslog stopped at (void)restoreCompletedTransactions


- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers {

if ((self = [super init])) {

/

_productIdentifiers = productIdentifiers;

/

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

}

return self;

}

- (void) removeTransactionObserver {

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

}

- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {

/

_completionHandler = [completionHandler copy];

/

_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];

_productsRequest.delegate = self;

[_productsRequest start];

}

- (void)buyProduct:(SKProduct *)product {

NSLog(@"Buying %@...", product.productIdentifier);

SKPayment * payment = [SKPayment paymentWithProduct:product];

[[SKPaymentQueue defaultQueue] addPayment:payment];

}


#pragma mark SKPaymentTransactionOBserver

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

{

NSLog(@"Entering payment queue");


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];

default:

break;

}

};

}

- (void)completeTransaction:(SKPaymentTransaction *)transaction {

NSLog(@"completeTransaction...");

[self ValidateReceipt:^(NSString *err, NSDate *purchaseDate, NSString *tID) {

NSDictionary *resultDict;

if(err == nil) {

resultDict = @{@"TID" : tID,

@"DATE" : purchaseDate

};

} else {

resultDict = @{@"ERROR" : err

};

}


[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:resultDict userInfo:nil];

}];


[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction {

NSLog(@"restoreTransaction...");


[self ValidateReceipt:^(NSString *err, NSDate *purchaseDate, NSString *tID) {

NSDictionary *resultDict;

if(err == nil) {

resultDict = @{@"TID" : tID,

@"DATE" : purchaseDate

};

} else {

resultDict = @{@"ERROR" : err

};

}


[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:resultDict userInfo:nil];

}];


[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

- (void)failedTransaction:(SKPaymentTransaction *)transaction {

NSLog(@"FaiuledTransaction...");

if (transaction.error.code != SKErrorPaymentCancelled)

{

NSLog(@"Transaction error: %@", transaction.error.localizedDescription);

}


NSDictionary *resultDict = @{@"ERROR" : transaction.error.localizedDescription

};


[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:resultDict userInfo:nil];


[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}

- (void)restoreCompletedTransactions {

NSLog(@"RestoreCalled...");

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

}

Replies

Did you first do a

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

Yes, I did it

Are you running the 'real device' using the app that was downloaded from the App Store or using the app that was installed from Xcode?


Do you have a

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error{

or a

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

Yes I downloaded from the app store.

But I didnt implement "restoreCompletedTransactionsFailedWithError" and "paymentQueueRestoreCompletedTransactionsFinished" method

So it is reasonable to speculate that the App Store is not restoring the purchase because it has no record of the purchase. Are you sure the currently logged in iTunes Account has actually made a purchase (with or without the redeem code)?

Yes, I logged in itunes and it is possible to make a payment.

But restoring of redeemed promo code is not responding.


Is it because I'm trying to restore Non-Renewable subscription?

Yes. Good information to add to your quiuestion. A non-renewing subscription is not a non-consumable item.

You are responsible for restoring a non-renewing subscription to other devices owned by the user as per:

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html


You can use the user's iCloud key-value file.

Thank you PBK,


I have a question about promo code.

Isn't it possible to use apple's promo code to redeem Non-renewing subscription item?

Should I change the item to Auto-renewing subscription to provide promo codes for someone else?

I think that PBK hit tha nail on the head with his question about installing the transactionObserver. The way that a promo code works -

1. the user redeems the promo code and the App Store sets up an incompleteTransaction for the itunes user/app bundle ID. This is the same way that an auto-renewing subscription is renewed.

2. the user launches the app, and assuming that the app calls addTransactionObserver early on in the app launch process, the transactionObserver checks with the app store based on the current iTunes user and app bundle ID to see if there is an incompleteTransaction. If there is, then the user authentication alert is presented, then the updatedTransactions delegate method is called to process the transaction much like the user just purchased the "Buy" button.


Where this could fail is in the case that the promo code was redeemed by a different user account than what is current on the device. Your code above shows that the addTransactionObserver is called when initWithProductIdentifiers is called. Does the app call initWithProductIdentifiers from the didFinishLaunchingWithOptions app delegate method??


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

//Rich


Yes


"addTransactionObserver" and "initWithProductIdentifiers" are called from appDelegate.m

I checked it from the console using nslog.



Plus

I just realized, I was wrong about Promo Code proceduers from the start.

I thought, when the user redeemed promo code, the transaction is completed and the app has to start the restore operation.

One aspect of the question I forgot to address. The restoreCompletedTransactions method will restore "completed" transactions. A "completed" transaction is one which the application process has been notified cia the updatedTransactions delegate method and which the app uses the finishTransaction call on. The call to finishTransaction results in a URL request to the App Store which marks the "incompleteTransaction" as "completed". If there is a network failure during the URL request to finishTransaction, the App Store may still have the transaction marked as incomplete. In this case, the "incompleteTransaction" should be detected by the next transactionObserver check to detect such incompleteTransactions.


The transactionObserver checks for incompleteTransactions in the following cases.

1. When the addTransactionObserver call is made and

2. when the StoreKit app with the transactionObserver active moves from the background to the foreground environment.


It could be that the App Store is not properly responding to the transactionObserver check for incompleteTransactions, but this would be a bug report issue for the iTunes Server engineering QA team to investigate. The one time I recall this happening, other developers were affected as well.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

Yes but remember....

If you ever call that 'removeTransactionObserver' method you will need to call initWithProductIdentifiers: again!

Post not yet marked as solved Up vote reply of PBK Down vote reply of PBK