iOS 7 Local (on device) Receipt Validation and In-

2019-03-09 14:09发布

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.

5条回答
淡お忘
2楼-- · 2019-03-09 14:14

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.

查看更多
迷人小祖宗
3楼-- · 2019-03-09 14:14

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];
}
查看更多
我欲成王,谁敢阻挡
4楼-- · 2019-03-09 14:25

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.

查看更多
Lonely孤独者°
5楼-- · 2019-03-09 14:26

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.

查看更多
Summer. ? 凉城
6楼-- · 2019-03-09 14:37

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.

查看更多
登录 后发表回答