Is Receipt Validation Mandatory?

So, my first In-App Purchase App got rejected. My app has a GET button to start the purchase process. It has been running without issue during my Sandbox testing. I got this line from Apple Review.



"We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 12.1.4 on Wi-Fi. Specifically, your app does not initiate the in-app purchase process when we tap the GET button. When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead."



I am confused by this statement because my app does not do any receipt validation in the app at all right now. My servers occassionally do receipt validation when I am polling for subscription statuses, but Apple would be unaware of this process during App Review. Also, I have a PHP script that listens for Status Update Notifications but I don't have to differentiate between Sandbox and Production for that. From the initial statement, it sounds as if they are saying that the in-app purchase process is not being initiated when they tap the GET button, but I don't think that is exactly what they meant. I think that the button works just fine, but there might be something wrong with receipt validation. So my question is, is receipt validation MANDATORY or optional? Is that perhaps what I am missing? Trying to figure out where to start in fixing this problem.

Replies

Receipt validation is not mandatory.


Wild guess here - App Review knows that you would not submit a binary for approval if your IAP code was not working in your hands before submission. The only difference between your IAP tests before you submit and your IAP tests after you submit is, assuming you are validating receipts, how you point to the Apple servers (and your banking contracts must be signed). If not done correctly this can lead to the failure App Review is seeing and so App Review assumed this is what you were doing wrong.


But you are left with the problem - why doesn't your IAP work?


So back to your statement: "it sounds as if they are saying that the in-app purchase process is not being initiated when they tap the GET button, but I don't think that is exactly what they meant"


If they mean that, check your IAP again and be sure your contracts are signed. The only other thing they might mean - and this would bring them more naturally to their speculation regarding receipt validation - is what happens when the first subscription period of an autorenewable subscription expires - do you correctly update the subscription?

Hmmm, thanks for the reply PBK, all contracts have been signed, so it is not that, but I appreciate you bringing it to my attention.


I am not sure I understand your final question entirely - "what happens when the first subscription period of an autorenewable subscription expires - do you correctly update the subscription?"


It is an auto-renewable subscription, so technically it doesn't expire. I get RENEW and CANCEL events sent to my server, so I reply to Apple's servers that I recieved them with a 200 response and update my database. By expire, do you mean if a user cancels, and then re-signs up? If they cancel, the subscription cannot be considered "expired" until my server polls Apple's servers to verify that it has actually expired, but it only polls once a day, so no subscription can be turned off during their Review process.


Could you elaborate a little more on what you mean in your last question?


Thanks

> The only other thing they might mean - and this would bring them more naturally to their speculation regarding receipt validation - is what happens when the first subscription period of an autorenewable subscription expires - do you correctly update the subscription?


Your reliance on those notifications may have led you astray. At the end of each subscription period of an autorenewable subscription the subscription gets renewed. That causes three things to happen and your app needs to detect one of those three things:

1) a new call to updatedTransactions (but only if the user has an observer active)

2) a notification event sent to some server (but not with 100% certainty)

3) a revision to the latest_receipt_info field that would be returned if you sent an old receipt to the Apple servers.


You need to detect one of those three events at the end of each subscriptionperiod. I suspect that in App Review's hands you let their subscription expire at the end of the first renewal period (i.e. about 5 minutes) and you failed to register their renewal event. App Review thought you failed to do #3 correctly hence their comment about 21007.

So far, I can find nothing wrong with my code. My test device is an old iPad 3 running iOS 9. Everything worked fine on my iPad. The reviewer said that it failed the test on a newer iPad running iOS 12.1.4. I don't know if it could make any difference, but I packaged my app with iOS 11. I am going to try and repackage the app with iOS 12.1.4 and see if it makes any difference, but I have a hard time imagining that iOS 11 would have failed to "initiate the in-app purchase process" when my GET button was clicked. I guess I won't know until I try.

Do you have code in your app that responds to a renewal event sent to updatedTransactions?

Do you have code in your app that allows the user to restore their subscription in a second device afetr having purchased it in a different device?

When you install the app, there is definitely a "restore" process when a user is signing up that we use. The app does not respond to renewal events because I do not have an observer running constantly in the application.


This may not be the typical way that things are done, but ours is a multi-platform application on all mobile devices as well as computers. It started with just a simple Stripe interface, but Apple and Android both need in-app purchase to be compliant with subscriptions, so I had to incorporate IAP into the mobile app. To make things as simple as possible, we do not run an observer the entire time the app is open because we do not sell products, we only offer a subscription upon signing up. So an observer is opened every time a user goes to their account information, but it is closed all other times to save on resources. The idea is that IAP runs during signup to either make a purchase or restore a purchase, our database is updated to reflect that they are an active user, and then our servers take over.


They listen for notifications from Apple for renewals and cancellations, and do daily polling to determine if the subscription is active or expired, and a single field in our database is updated to active or inactive. When the app opens up, it tests our database for active or inactive status and unlocks or locks the content. If a user cancels and the account expires, the server listens for the notification and inactivates the users account. The next time the app opens, the account information pops up and an option to re-purchase a subscription is offered to a user.


Is there anything wrong with handing the account maintenance off to Status Update Notifications and servers instead of handling directly in the app? Is it necessary to handle a renew event in the app, and therefore to need an observer to be open all the time?

Apple requires a straightforward way to restore the subscription to new devices owned by the same user who purchased the subscription on a different device.


So let me ask you again...."Do you have code in your app that allows the user to restore their subscription"? Specifically, what would App Review have seen if they purchased the subscription on one device and then opened the app on a second device? Would they see only that giant "GET button"? If so, what would happen if they tapped that GET button? Would there be an option for "Restore"? If not, what would you expect App Review to do at that point?


Also...."The app does not respond to renewal events" - that sounds bad. What do you expect a user to do if they purchased the subscription on day 1 and then tried to use the app after the imitial subscription had expired?


I fear you are expecting the user to approach your app with some pre-knowledge of how it functions? Look at this from the perspective of the user who opens the app and gets a "you don't have a subscription" warning or a "your subscription has expired" warning. Are you presuming they know that they "just need to log onto their account"?

Regarding the Restore, there is a Restore button right underneath the GET button. But even if they don't use the Restore button, we automatically test for a Restore when a use clicks the GET button just to be on the safe side. So restore should be covered.


As far as renewal is concerned, a user shouldn't have to think about it a bit. A user signs up, and the App should work until my servers receive a cancel event from Apple's servers, followed by polling to test for expiration confirmation. Once polling confirms expiration, the database sets their "active" field to false. Once that happens, the app will let the user know that they need to purchase a new subscription to continue using the app and to login to their account to make the purchase.


To summarize, the app is not responsible at all for renewal and cancellation events at all, the server is. The app only does Purchase or Restore, and the server handles the status of the subscription after that by listening for Renewal and Cancellation notifications.


Is this not acceptable by Apple?

>Is this not acceptable by Apple?


Note that no one here can promise what is/isn't acceptable. You're receiving (good) opinions here, not guarantees, so...