Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting user canceling SKReceiptRefreshRequest to sign in

When we need to refresh an iOS7 receipt, the appropriate way to do that is to use SKReceiptRefreshRequest. This brings up an Apple dialog for the user to sign in using their Apple ID. It also allows them to Cancel. How can we detect if the user presses Cancel? (Of course, in iOS6, this can be done using catching the cancel event for [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]).

The SKReceiptRefreshRequest object has a delegate with two methods:

- (void)requestDidFinish:(SKRequest *)request;
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error;

In my code the requestDidFinish delegate method is called if the user does or does not press cancel.

The reason this is important to me is that I want to cancel the restore process if the user presses Cancel. If there was simply no receipt present after the refresh request, this might be relatively easy. However, sometimes there is a receipt (with some purchases) present in the app before the SKReceiptRefreshRequest, and so it stays in the app if the user cancels from the sign-in dialog.

I have two ideas for how to do this:

1) Delete the receipt from the bundle prior to the refresh request. The apparent problem with this is that the app can't remove files from the bundle (e.g., see Delete file from bundle after install). I tried. Nope.

2) Check the bytes of the receipt before the refresh request and after; if they differ, then this should indicate the user didn't press Cancel. If they don't differ, I'm not sure that does indicate they pressed Cancel. If the receipt contains purchases, I think that the bytes will differ because a refreshed receipt should have different transaction id's (but the same "original" transaction id's). If the receipt contains no purchases, I'm not sure about that.

Update, 11/9/15; I've just noticed that the delegate response to the user pressing Cancel appears to have changed. Now, didFailWithError is called. The problem of detecting user cancellation remains, however. How can we distinguish between the user pressing Cancel and a genuine error? I've been testing on iOS8.4 and iOS9.2 (beta). I have now reported this lack of distinguishability as a bug to Apple (bug #23476210).

Update, 11/10/15; This issue does not appear with iOS 9.0.2! I just tried this now with all three systems, with the same app binary, in the same approximate interval of time (within 20 minutes for all three systems): (A) iOS9.2 (13C5050d): Issue does occur (didFailWithError is called and can't distinguish between a real error and user pressing cancel), (B) iOS9.0.2, issue does not occur (requestDidFinish is called), and (C) iOS8.4.1, issue does occur. With all three system versions, this is running on real hardware, not the simulator.

like image 256
Chris Prince Avatar asked Nov 21 '13 18:11

Chris Prince


1 Answers

iOS 9.2.1, Xcode 7.2.1, ARC enabled

I understand you asked this 2 years ago, but I recently had my run in with this issue and found a solution, and wanted to share here so others can save some time.

1) You may elect to store the receipt into keychain and access a copy of it, this allows you to delete it or refresh it as you see fit.

2) Yeah you can definitely check if it changed, I think the simplest way to do that is to use:

[receipt isEqualToData:(NSData *)(copyReceiptObject)]

My suggestion is as follows:

The key is to expect the error you get from the method:

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error;

that gets called when the user taps cancel after the log in dialogue comes up to sign in to iTunes Store. There are many custom was you could do the refresh request, so the results may vary, but here is the different error you get for my request:

When there is no connection to iTunes:

Error Domain=SSErrorDomain Code=110 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store, NSUnderlyingError=0x13c76d680 {Error Domain=NSURLErrorDomain Code=-1009 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store, NSErrorFailingURLStringKey=

{ your product ids and corresponding URIs here }

, _kCFStreamErrorCodeKey=8, _kCFStreamErrorDomainKey=12, NSLocalizedDescription=The Internet connection appears to be offline.}}}

When the user taps cancel:

Error Domain=SSErrorDomain Code=16 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store, NSUnderlyingError=0x13c6ac7b0 {Error Domain=AKAuthenticationError Code=-7003 "(null)"}}

The easiest I think is to check the error.code, if you want to get more complicated you may choose to parse the full error string to get more details about the error.

In my case, no connection error code is 110 and when the user cancels the log in the error code is 16. Handle the error.

like image 174
serge-k Avatar answered Oct 15 '22 23:10

serge-k