The training class for Selling In-app Products in Android suggests to use a payload when making a purchase request:
The fifth argument contains a ‘developer payload’ string that you can use to send supplemental information about an order (it can be an empty string). Typically, this is used to pass in a string token that uniquely identifies this purchase request. If you specify a string value, Google Play returns this string along with the purchase response. Subsequently, when you make queries about this purchase, Google Play returns this string together with the purchase details.
Security Recommendation: It’s good practice to pass in a string that helps your application to identify the user who made the purchase, so that you can later verify that this is a legitimate purchase by that user. For consumable items, you can use a randomly generated string, but for non-consumable items you should use a string that uniquely identifies the user.
The Implementing IAB Purchase page has a similar recommendation, with the additional suggestion that the payload value should be checked on your own secure server:
Security Recommendation: When you send a purchase request, create a String token that uniquely identifies this purchase request and include this token in the developerPayload.You can use a randomly generated string as the token. When you receive the purchase response from Google Play, make sure to check the returned data signature, the orderId, and the developerPayload String. For added security, you should perform the checking on your own secure server. Make sure to verify that the orderId is a unique value that you have not previously processed, and the developerPayload String matches the token that you sent previously with the purchase request.
Looking at the source code for the Trivial Drive app that Google is using to demonstrate the API, I find this warning:
* WARNING: Locally generating a random string when starting a purchase and
* verifying it here might seem like a good approach, but this will fail in the
* case where the user purchases an item on one device and then uses your app on
* a different device, because on the other device you will not have access to the
* random string you originally generated.
*
* So a good developer payload has these characteristics:
*
* 1. If two different users purchase an item, the payload is different between them,
* so that one user's purchase can't be replayed to another user.
*
* 2. The payload must be such that you can verify it even when the app wasn't the
* one who initiated the purchase flow (so that items purchased by the user on
* one device work on other devices owned by the user).
*
* Using your own server to store and verify developer payloads across app
* installations is recommended.
So from all these messages, it sounds like a bad idea to use a random number/string for the payload. Also, after reading the last warning, it sounds like a bad idea to pass the device ID as the payload too since it will different for the same user on different devices. So what should be used for the developer payload?
My app provides local functionality that can be accessed by the user without having to sign-in to any service. So there is no concept of a 'user' and there is no server side component either. The in-app purchase request is for an upgrade that removes ads from the app. Does it make sense for an app like this to make use of the payload feature, or am I better off just using an empty string for it and leaving it prone to replay attacks?
My knowledge with InApp purchasing is from the older v2 library. I haven't worked with the newest v3. However, hopefully I can still be helpful.
Yes using the developer payload as an added security feature will definitely help, however whether you should or not is probably more subjective then an objective dilema. In my case, my client already had a server in the mix since customers have to download 200mb of files following an inapp purchase. We used the developer payload to store information about the file authorized to be downloaded. This info was critical in telling the App how to process the downloaded files.
We still provided added security by overriding the local verifyPurchase()
method with a server call. IE, supplying our own nonce to check. Doing that locally is not very secure.
As for your situation, I say it's a matter of risk vs cost. What's the risk of your App being hacked and customers bypassing purchases vs the cost of implementing the added security. If your App is being downloaded over 100,000 times then you have a fair amount of risk. If over 1 million times, then there's a high risk. If only a few thousand, then piracy will probably overlook your App. If you choose the added security, you need to get a server up and running, then add all the code and handshaking required for App and server. All of which will require time and money. Especially in paying for a server during the life of your App.