21

I have implemented receipt validation locally on the device using OpenSSL and the asn1c compiler with help from Apple's Receipt Validation Programming Guide. My app only supports iOS 7 and up.

As recommended by Apple I call, [[NSBundle mainBundle] appStoreReceiptURL] to get the app store receipt. I also do this when the app is 'first' launched before displaying any UI. This first launch call is needed as Apple recommends refreshing the receipt if its not there on first try. As a result of this call (SKReceiptRefreshRequest) the app asks the user to enter their iTunes log in information.

Now the problem is Apple keeps rejecting the app saying I am making calls to their production servers instead of the sandbox servers. But that according to what I understand from the Receipt Validation Programming Guide is only valid if you use the second approach of validation and send data to Apple via your own secure server. I am however doing everything locally and am very confused about how to differentiate between the production and sandbox environments so that my app may pass review.

Any pointers or suggestions would be very helpful.

Anuj Seth
  • 673
  • 6
  • 15
  • This doesn't make sense. Why would Apple complain that the app you've submitted for review is using the production servers? Why would they say to use the sandbox servers for a production app? – rmaddy Apr 30 '14 at 04:36
  • I don't think I'm allowed to post Apple's resolution center messages externally else I would have posted it here. – Anuj Seth Apr 30 '14 at 09:01
  • Just curious: after calling refresh and, I am assuming, having it fail, what do you do in your app afterwards? Reading through the devforums, it seems that you would get this type of rejection only if, failing validation, you block the reviewer from continuing to e.g. make an IAP purchase. – user1459524 May 02 '14 at 14:57
  • In case it fails, for example the user presses cancel on the iTunes login prompt, just load the basic free version of the app. No blocking or anything. – Anuj Seth May 03 '14 at 03:55
  • Anuj, have you resolved this issue with Apple yet? – platypus May 03 '14 at 04:39
  • @platypus as I mentioned in the comment yesterday which someone deleted, have resubmitted the binary with no receipt refreshing on first launch even if no receipt is found. waiting for review. – Anuj Seth May 03 '14 at 05:46
  • We do not refresh any receipt or try to do receipt validation until the user clicks "Restore" button in our "Store" page. I'm glad that we are on the same boat as someone else. May I ask what do you mean by "no receipt refreshing?" I am assuming that if the receipt is missing, you skip the receipt validation phase entirely without crashing the app? – platypus May 04 '14 at 04:01
  • check this out .. http://stackoverflow.com/questions/19943183/a-complete-solution-to-locally-validate-an-in-app-receipts-and-bundle-receipts-o – damithH May 05 '14 at 06:09
  • @platypus yes, so if there is no receipt on first launch do not ask for one, just set up the app to use limited free version features. – Anuj Seth May 05 '14 at 08:14

5 Answers5

13

Alright so here is what worked for me, Apple approved the app last night after multiple rounds of review appeals and re-submissions spanning almost a month.

Do NOT try and refresh the receipt when the app launches and do not block the UI. What I was doing was not showing any UI on launch until a receipt was found, so when prompted for the iTunes password on launch pressing cancel would show the limited version of the app, entering a correct password would try and download a new receipt and act according to whether one was found.

So on launch if you find a receipt thats fine, if not do not try and refresh it.

DO however refresh it when the user presses the Restore Purchases option.

Hope this helps.

Anuj Seth
  • 673
  • 6
  • 15
  • 2
    People who keep down voting the answer, would be helpful if you mention why. – Anuj Seth May 13 '14 at 18:47
  • This is FACT. Dialog for enter user's psw is shown if the receipt is invalid or does not exist. So RATHER then ask for psw at the beginning (for user is confusing bcs he just launch the app and it asks for no reason) PROVIDE Restore btn which you have to provide ANYWAY and w8 till the user pressed it. – Jakub Truhlář Mar 21 '15 at 20:51
5

I've deleted my previous response, I had misunderstood the question.

I believe you are doing everything right and Apple is confused about its own guidelines, to be honest. After all, in the Receipt Validation Programming Guide, they advise clearly: "If validation fails in iOS, use the SKReceiptRefreshRequest class to refresh the receipt" and there is no way to influence to what server this call is made (SKReceiptRefreshRequest reference)

According to http://asciiwwdc.com/2013/sessions/308, what server is called depends on how the app is signed, and it needs to be signed for production upon submission, obviously.

user1459524
  • 3,613
  • 4
  • 19
  • 28
4

The difference between production and sandbox environments is based on the link that you are calling.

#define ITMS_PROD_VERIFY_RECEIPT_URL        @"https://buy.itunes.apple.com/verifyReceipt"
#define ITMS_SANDBOX_VERIFY_RECEIPT_URL     @"https://sandbox.itunes.apple.com/verifyReceipt";

ITMS_PROD_VERIFY_RECEIPT_URL is production server. ITMS_SANDBOX_VERIFY_RECEIPT_URL is sandbox server.

  1. Make sure that you create the right provisioning cert from Apple provisioning portal. Know the difference between Ad-Hoc vs Distribution.
  2. When you are using a Test iTunes user account that you created from iTunes Connect to buy, you will have to test it under sandbox server. Under the Release of Code Signing Identity, you should choose Ad-Hoc provisioning rather that distribution provisioning.
  3. But when you want to release to app store, you have to choose distribution provisioning and also the production server (ITMS_PROD_VERIFY_RECEIPT_URL). You can not use test user account on this server. You will have to use a real iTune user account to buy it (after Apple approves it) to make a real purchase.

To Learn how to implement IAP locally and verify the receipt locally, learn from:- 1.http://www.raywenderlich.com/21081/introduction-to-in-app-purchases-in-ios-6-tutorial

2.http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation

You may download the completed sample project here:- 3. http://cdn1.raywenderlich.com/downloads/InAppRagePart2Finished.zip

NOTE: There might be another way of verifying the receipt that I do not know.

I found something that might help:- 1. https://developer.apple.com/library/ios/documentation/StoreKit/Reference/SKReceiptRefreshRequest_ClassRef/SKReceiptRefreshRequest_ClassRef.pdf

- (id)initWithReceiptProperties:(NSDictionary *)properties

It says "In the production environment, set this parameter to nil." properties In the test enviroment, the properties that the new receipt should have. For keys, see “Receipt Properties” (page 4). In the production environment, set this parameter to nil.

Ricky
  • 10,485
  • 6
  • 36
  • 49
  • No this isn't what the question is about, these things apply when you use Apple's servers for receipt validation and in app purchase parsing. When doing it locally, on the device completely, there is no way to tell whether you are in the sandbox or the production environment. – Anuj Seth May 05 '14 at 08:17
  • Can you show me the code to verify the receipt? As far as I know, there are 2 ways to verify the receipt. 1. Verify locally on device, Yes we use the device to call the receipt verification URL like I stated above. 2. Verify on the server. The server will verify the receipt and the pass the parameters back to the device. I have done both of these methods. If you want to verify locally, you may follow this good tutorial: http://www.raywenderlich.com/21081/introduction-to-in-app-purchases-in-ios-6-tutorial I used that sometimes ago when I first learned about verify receipt on mobile (locally). – Ricky May 05 '14 at 08:28
  • After you learn the first tutorial, learn this http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation – Ricky May 05 '14 at 08:31
  • Found a link that might help: https://developer.apple.com/library/ios/documentation/StoreKit/Reference/SKReceiptRefreshRequest_ClassRef/SKReceiptRefreshRequest_ClassRef.pdf properties In the test enviroment, the properties that the new receipt should have. For keys, see “Receipt Properties” (page 4). In the production environment, set this parameter to nil. – Ricky May 05 '14 at 09:01
  • @voyage11, when we are talking about verifying receipt locally, there is no URL that we can just call. We just use SKReceiptRefreshRequest. So your understanding of how to do local receipt validation is not on the same page as the person who asked the question. – platypus May 08 '14 at 21:43
  • Yes, I know that. I already mentioned in the **NOTE**: "There might be another way of verifying the receipt that I do not know." After I found out that there is a third method of verifying the receipt. and I have found a link that must help in term of differentiating between production environment vs test environment asked by the user – Ricky May 08 '14 at 23:29
1

Dialog for enter user's psw is shown if the receipt is invalid or does not exist.

So RATHER then ask for psw at the beginning if receipt does not exist (for user is confusing bcs he just launch the app and it asks for no reason) PROVIDE Restore btn which you have to provide ANYWAY and w8 till you get receipt which you can reasonably varify.

So put your refresh code into if statement:

if([[NSFileManager defaultManager] fileExistsAtPath:[[[NSBundle mainBundle] appStoreReceiptURL] path]] == YES) {

        self.receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        self.receiptRefreshRequest.delegate = self;
        [self.receiptRefreshRequest start];
}
Jakub Truhlář
  • 20,070
  • 9
  • 74
  • 84
0

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.

John
  • 706
  • 2
  • 10
  • 16