Purchase passing receipt validor, never show as sale

Users are making what I believe to be fraud subscription purchases because they never show as sales, but according to the receipt validator log, Apple is saying all of the receipts. are valid.

Here's a response from the receipt validator:
Code Block language
{
"receipt": {
"receipt_type": "Production",
....
"receipt_creation_date": "2020-06-20 08:52:18 Etc/GMT",
"receipt_creation_date_ms": "1592643138000",
"receipt_creation_date_pst": "2020-06-20 01:52:18 America/Los_Angeles",
"request_date": "2020-06-20 13:41:02 Etc/GMT",
"request_date_ms": "1592660462195",
"request_date_pst": "2020-06-20 06:41:02 America/Los_Angeles",
"original_purchase_date": "2020-06-20 08:52:18 Etc/GMT",
"original_purchase_date_ms": "1592643138000",
"original_purchase_date_pst": "2020-06-20 01:52:18 America/Los_Angeles",
"original_application_version": "2.5",
"in_app": []
},
"status": 0,
"environment": "Production"
}


The "original purchase date" is 6/20, but it doesn't show as a sale in Apple Store Connect. Our analytics are also reporting a weird transaction id, so I'm pretty sure it's not a valid transaction.

Any idea how this is passing the validator if it's not real?

Thanks
Answered by in 613403022
What you've presented above is a standard appStoreReceipt which is written to all applications when an app is installed by the App Store in the production environment. When there are no in-app transactions recorded by the user for the app, the inapp array is always empty. With regards to your finding, this is a bug report issue to be investigated by the App Store Server team. The App Store Server is responsible for the contents of the appStoreReceipt. I’ve provided the instructions for you to submit a bug report below. First let me clarify the situation, then provide some suggestions as to how the app should behave when this situation is detected.

First to clarify the issue - The empty in-app array is detected after the user has attempted an in-app purchase and the app has received the .purchased transactionState and has sent the appStoreReceipt directly to the verifyReceipt server or to your server and on to the verifyReceipt server, then the validated appStoreReceipt is returned where the app detects the empty in
app array. My reason for asking is that if the user has two devices, both with your app, and the purchase is made from one device, but the 2nd device is used and the app launched, and the appStoreReceipt processed, for the second app, the validated appStoreReceipt will show an empty inapp array.

Now for some thoughts on how the app should handle this situation -
  1. If the in

app array is found to be empty, DO NOT call finishTransaction. Per the documentation <https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction>“Call finishTransaction(_:) only after the app has finished all work it performs to complete the transaction. “
It would have been clearer to say - don’t call finishTransaction unless the app is satisfied that the purchase is valid and the content associated with the purchase has been provided to the user. The main reason for this - when the app makes the finishTransaction call, it’s telling the App Store that the transaction is now considered complete. If the finishTransaction is not made, then the transaction remains on the transaction queue and will be processed the next time that the transactionObserver is activated. For an iOS app, there are three times that the transactionObserver is activated to check the App Store.
  1. When the addTransactionObserver is called

  2. When the addPayment call is made to make a purchase and

  3. When the application moves from the background to the foreground - for example if the app is active and you move the app launch screen to the foreground, then switch back to activating the app, the transactionObserver will query the App Store.

The reason it’s important - the App Store is responsible for writing the contents of the appStoreReceipt. At some point it may finally realize the the purchase was not included, and add it - then when the transactionObserver fires and detects the queued transaction it will process it much as if the user just pressed the “Buy” button.

Another strategy to consider - that of validating the appStoreReceipt when the app launches to determine what in-app purchases have already been made - only validate the receipt if it is present. If the app validates the appStoreReceipt then this makes it possible for the solution - whereby users can delete the app and re-install it from the App Store. Some developers have reported that in doing this, the App Store installs the app with the updated appStoreReceipt and now the in-app paid service is available. There is no requirement that the app do this, but it’s a means to stay current as to what services are available to the app.

Finally, another solution - if your product is an auto-renewing subscription - a suggestion is to implement server-to-server notifications. Whenever a new subscription purchase occurs, your server will be notified as to the new transactions. I've not heard of this issue reported by developers who've implemented server-to-server notifications. For more information about server-to-server notifications, I refer you to the Apple Developer documentation - <https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/enabling_server-to-server_notifications>

As for submitting the bug report - I’ve been asked that when developer submit such report, please include a copy of the base64 encoded appStoreReceipts captured after the .purchased event along with the iTunes user account - if possible.

To submit a bug report, please use the Apple Developer Feedback web page -

<https://feedbackassistant.apple.com/>.

Enter the “Feedback Assistant” page and login
Click on the Compose icon to start a new bug report

Start by clicking on the appropriate OS button - “iOS and iPadOS”
  • In the “Descriptive Title” field, enter an appropriate description.

  • In the “Problem Area” field select “StoreKit”

  • In the “Type of Feedback” select “Incorrect / Unexpected Behavior”

  • In the “Describe the Issue” section enter the following

  • application ID (and In-App Purchase identifiers if appropriate)

  • production environment

  • make sure to indicate the the issue has occurred moments after the user has made the in-app purchase and the app has been notified as to the successful transaction, then the appStoreReceipt in the app is validated.

  • the information above - the appStoreReceipt may be in a text file and uploaded as an attachment to the bug report.

rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI
Accepted Answer
What you've presented above is a standard appStoreReceipt which is written to all applications when an app is installed by the App Store in the production environment. When there are no in-app transactions recorded by the user for the app, the inapp array is always empty. With regards to your finding, this is a bug report issue to be investigated by the App Store Server team. The App Store Server is responsible for the contents of the appStoreReceipt. I’ve provided the instructions for you to submit a bug report below. First let me clarify the situation, then provide some suggestions as to how the app should behave when this situation is detected.

First to clarify the issue - The empty in-app array is detected after the user has attempted an in-app purchase and the app has received the .purchased transactionState and has sent the appStoreReceipt directly to the verifyReceipt server or to your server and on to the verifyReceipt server, then the validated appStoreReceipt is returned where the app detects the empty in
app array. My reason for asking is that if the user has two devices, both with your app, and the purchase is made from one device, but the 2nd device is used and the app launched, and the appStoreReceipt processed, for the second app, the validated appStoreReceipt will show an empty inapp array.

Now for some thoughts on how the app should handle this situation -
  1. If the in

app array is found to be empty, DO NOT call finishTransaction. Per the documentation <https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction>“Call finishTransaction(_:) only after the app has finished all work it performs to complete the transaction. “
It would have been clearer to say - don’t call finishTransaction unless the app is satisfied that the purchase is valid and the content associated with the purchase has been provided to the user. The main reason for this - when the app makes the finishTransaction call, it’s telling the App Store that the transaction is now considered complete. If the finishTransaction is not made, then the transaction remains on the transaction queue and will be processed the next time that the transactionObserver is activated. For an iOS app, there are three times that the transactionObserver is activated to check the App Store.
  1. When the addTransactionObserver is called

  2. When the addPayment call is made to make a purchase and

  3. When the application moves from the background to the foreground - for example if the app is active and you move the app launch screen to the foreground, then switch back to activating the app, the transactionObserver will query the App Store.

The reason it’s important - the App Store is responsible for writing the contents of the appStoreReceipt. At some point it may finally realize the the purchase was not included, and add it - then when the transactionObserver fires and detects the queued transaction it will process it much as if the user just pressed the “Buy” button.

Another strategy to consider - that of validating the appStoreReceipt when the app launches to determine what in-app purchases have already been made - only validate the receipt if it is present. If the app validates the appStoreReceipt then this makes it possible for the solution - whereby users can delete the app and re-install it from the App Store. Some developers have reported that in doing this, the App Store installs the app with the updated appStoreReceipt and now the in-app paid service is available. There is no requirement that the app do this, but it’s a means to stay current as to what services are available to the app.

Finally, another solution - if your product is an auto-renewing subscription - a suggestion is to implement server-to-server notifications. Whenever a new subscription purchase occurs, your server will be notified as to the new transactions. I've not heard of this issue reported by developers who've implemented server-to-server notifications. For more information about server-to-server notifications, I refer you to the Apple Developer documentation - <https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/enabling_server-to-server_notifications>

As for submitting the bug report - I’ve been asked that when developer submit such report, please include a copy of the base64 encoded appStoreReceipts captured after the .purchased event along with the iTunes user account - if possible.

To submit a bug report, please use the Apple Developer Feedback web page -

<https://feedbackassistant.apple.com/>.

Enter the “Feedback Assistant” page and login
Click on the Compose icon to start a new bug report

Start by clicking on the appropriate OS button - “iOS and iPadOS”
  • In the “Descriptive Title” field, enter an appropriate description.

  • In the “Problem Area” field select “StoreKit”

  • In the “Type of Feedback” select “Incorrect / Unexpected Behavior”

  • In the “Describe the Issue” section enter the following

  • application ID (and In-App Purchase identifiers if appropriate)

  • production environment

  • make sure to indicate the the issue has occurred moments after the user has made the in-app purchase and the app has been notified as to the successful transaction, then the appStoreReceipt in the app is validated.

  • the information above - the appStoreReceipt may be in a text file and uploaded as an attachment to the bug report.

rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI
Thank you for the reply! This helped a lot. I wasn't checking the "in_app" property in my validator. Now that I know the purchases need to be validated against that, all is good. Thanks again!
An update to this issue - if your app responds to a .purchased transactionState, validates the appStoreReceipt and finds an empty inapp array, the App Store team has implemented a fix such that if the app includes the "shared-secret" for the "password" key in the request JSON payload, the verifyReceipt server will return unfulfilled transactions. As all StoreKit apps which offer auto-renewing subscriptions do this, this suggestion is targeted for developers offering non-consumable, consumable and non-renewing subscription in-app purchase types. If the app does this and the inapp array is empty and there is evidence that an in_app purchase was successfully transacted via the app, this would be a bug report issue.

rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI
Purchase passing receipt validor, never show as sale
 
 
Q