Refreshing iOS app receipt: How to determine if user will need to sign in for app store?

15,430

Solution 1

It is being very complicated to deal with this, so this answer may not be completely satisfactory (and I'm late - I'm aware of that), but still, hopefully this will help you a bit with this particular problem.

There's a chance I'm not doing this right either, but so far everything works and makes sense to me, but please if I'm "talking BS" here, feel free to correct me.

But anyway, you cannot really prevent the authentication alert from showing up, but there are a few ways you can minimize how many times it shows up.

You don't need to re-download a receipt that you know doesn't exist. If the user didn't buy or hasn't restored the receipt, you can avoid attempting to redownload a receipt and avoid the alert view altogether. This is something I have done:

if([[NSFileManager defaultManager] fileExistsAtPath:[[[NSBundle mainBundle] appStoreReceiptURL] path]] != YES)
{
    SKReceiptRefreshRequest *refresh = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
    [refresh start];
}

I put this on my main.m file, before the main application loop, as my app has many "pro" features that get unlocked with IAP, but you can really put this where you find it relevant.

The main idea is that the receipt is an actual file stored in your app's directory. So everything we are doing here is checking if the receipt actually exists. If it doesn't, we save us the trouble of re-freshing it and therefore saving the trouble of showing the auth screen to the user.

If you want, you can put this code anywhere where the user might use a "pro" feature of your app. SKReceiptRefreshRequest inherits from SKRequest and because of that it will call the SKRequestDelegate methods. So the moment a user navigates to a screen with a pro feature, you can refresh the receipt, and then enable the feature when the delegate methods get called (and after doing the extra work of checking the receipt's contents).

The big downside with this approach is that it does require an internet connection. If your app works offline, the user will expect all its IAPs to work offline as well, so redownloading the receipt will be a problem under certain scenarios.

Solution 2

In an App Store app the receipt usually exists. It's downloaded from the App Store along with the app. If the user loads your app onto their device with iTunes (i.e. from a backup) there won't be a receipt.

The main use case for the receipt not being there is when you are debugging in Xcode and haven't done anything to cause the receipt to be downloaded (like refreshing the receipt or buying something).

In production, you can only get a new (fresh) receipt after some user action (they buy something, you refresh the receipt and they sign in, they restore purchases and sign in). There's no way to silently refresh the receipt.

If you want to get a new receipt without user action, you have to go the server route. Send the old receipt via your server to iTunes, get the latest receipt back.

Solution 3

I'd like to share my experience. I have been working in the Sandbox and the app receipt missing after deleting the app. (And then re-Command-R-ing) I do not know if this happens in production, but it sounds as though it does. Asking for a refresh upon the first ever app boot, and prompting the user for their password is startling. This is an issue, of course.

It seems as though [[SKPaymentQueue defaultQueue] restoreCompletedTransactions] also silently refreshes the app receipt without popping a dialog box. Meaning, after transaction have been restored, asking for the appReceiptURL + Data returns a non-nil value. This is just from my short amount of testing. Please, do your own testing.

Share:
15,430
Jason
Author by

Jason

Updated on August 10, 2022

Comments

  • Jason
    Jason almost 2 years

    I am implemeting Apple's "Grand unified receipt" on iOS 7, which allows the app to check an app's purchase receipt locally without having to contact Apple's servers for validation & verification. This works great if the user has a receipt stored in the app. Iin the case that the app is missing the receipt, the best practice is to request the app to refresh its receipt, as such:

    SKReceiptRefreshRequest *request = [[SKReceiptRefreshRequest alloc] init];
    [request setDelegate:self];
    [request start];
    

    The issue is that calling this code will ask the user to log in with his or her Apple ID. I am not 100% sure if this happens all of the time, or only if the user's app store login has timed out. I don't want to display the Apple ID login screen to users unless it is really necessary - I don't want people to be concerned that they will be charged incorrectly. I would like to show a display telling the user why they will be asked for their Apple ID password, but only if they will actually be required to enter their password. If they don't need to enter their password I want it to be a seamless and hidden process. What's the best way to proceed? I think the best way would be to check if the user will need to sign in for the app store, but I'm not sure if that is possible.