77

I'm using version 3 of the in-app billing API. I have a single, managed, non-consumable item. I have not released this feature in my app yet, so I want to decide on the purchase payload contents before there are any purchases.

From "Security Best Practices":

Set the developer payload string when making purchase requests

With the In-app Billing Version 3 API, you can include a 'developer payload' string token when sending your purchase request to Google Play. 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.

You should pass in a string token 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.

When you get back the response from Google Play, make sure to verify that the developer payload string matches the token that you sent previously with the purchase request. As a further security precaution, you should perform the verification on your own secure server.

Rightly or wrongly, I have decided not to take the "further security precaution" of setting up a server to perform purchase verification. And I do not store my own record of the purchase -- I always call the billing API. So is there really any reason for me to do this payload verification? The verification API itself certainly verifies the identity of a user before reporting an item as purchased, and if an attacker has compromised a device (either the app or the google play API), I don't see any benefit of doing an additional check on the user's identify on the device where it can easily be circumvented. Or is there a reason to do this that I'm not thinking of?

Travis
  • 2,961
  • 4
  • 22
  • 29

8 Answers8

70

If you don't keep a record there is no way to verify that what you get is what you sent. So if you add something to the developer payload, you can either trust that it is legitimate (which is a reasonable assumption if the signature verifies), or not trust it completely and only use it a reference, but not for validating license status, etc. If you store the user email, for example, you can use the value instead of asking them to enter it again, which is slightly more user friendly, but your app won't break if it is not there.

Personally, I think that this whole 'best practices' part is confusing and is trying to make you do work that the API should really be doing. Since the purchase is tied to a Google account, and the Play Store obviously saves this information, they should just give you this in the purchase details. Getting a proper user ID requires additional permissions that you shouldn't need to add just to cover for the deficiencies of the IAB API.

So, in short, unless you have your own server and special add-on logic, just don't use the developer payload. You should be OK, as long as the IAB v3 API works (which is, unfortunately, quite a big 'if' at this point).

Nikolay Elenkov
  • 52,576
  • 10
  • 84
  • 84
  • 4
    One and a half years after your initial answer, do you still feel the same way? I'm not sure if there have been any behind the scene changes made to the API since... and I can't say I trust Google's documentation right about now. – Alex Lockwood Jul 07 '14 at 23:26
  • 6
    I still think that IAP should provide security transparently and using the developer payload should not be required. Maybe it does now, but I haven't looked at it (or even used it) for quite a while. My apps still use original IAP mostly because it provides purchase notifications that I can track on my own server, which the 'new' IAP does not. – Nikolay Elenkov Jul 08 '14 at 01:07
  • 2
    I think there is need to validate on the server of course, which V3 makes quite hard if you don't have user "logged in" with some sort of account identifier (your own or 3-dparty) Was going maybe try API V2 **BUT**... We plan to turn off the In-app Billing Version 2 service on January 27, 2015, after which time users will no longer be able to purchase in-app items and subscriptions through the Version 2 API. We strongly encourage and recommend you migrate your apps to use Version 3 API by November 2014, to provide ample time for users to update their apps to the new version. – inteist Oct 05 '14 at 02:57
  • @Nikolay Elenkov, what is your suggestion as for now? I agree with you that this whole best practice is should be on Google's side. – stuckedunderflow Jan 16 '16 at 15:27
30

You should pass in a string token that helps your application to identify the user who made the purchase...

If your application provides its own user login and identity, which is different from what Google accounts the phone is connected to, then you would need to use the developer payload to attach the purchase to one of your accounts that made the purchase. Otherwise someone could switch accounts in your app, and get the benefit of purchased stuff.

e.g.

Suppose our app has login for userA and userB. And the phone's Android google account is X.

  1. userA, logs into our app and purchases life membership. The purchase details are stored under google account X.
  2. userA logs out, and userB logs into our app. Now, userB also gets the benefit of life membership, as android google account is still X.

To avoid such misuse, we will tie a purchase to an account. In the above example, we will set developer payload as "userA" when userA is making the purchase. So when userB signs in, the payload won't match to signed in user (userB), and we will ignore the purchase. Thus userB can't get benefits of a purchase done by userA.

therealsachin
  • 1,684
  • 1
  • 14
  • 9
  • Good answer. But what if we do not have any use login mechanism? Then we just not use the payload? – tasomaniac May 14 '13 at 22:29
  • 7
    After scratching my head for a while about this user ID issue, I think what you point to might be the actual intent of the payload string. It is simply very poorly explained in the Google documentation. – Pooks Nov 11 '13 at 08:34
  • So, user gmail will do the trick and can be used as `developerPaylod` – Sami Eltamawy Aug 10 '14 at 11:40
  • I tried this use case, but actually this cannot be solved. As you mentioned, purchase is bound to the google account, that paid for the item. If userA purchases life membership and userB tries to purchase the same membership on the same device but with a different payload, then in-app billing response is: "You already have this item" - so you cannot buy it again. And your verifyDevPayload() will also fail, so the user is actually deadlocked... I think the developer payload is only good to check if google play returned the good response for the request... I don't see any benefit using it. – keybee Aug 26 '15 at 14:15
  • 3
    @keybee That is exactly the point. In the case you mentioned, if we don't use dev payload, we will have to give userB premium features when in-app billing returns "You already have this item" (as we have no way to know whether it was userB who made the purchase in the first place). If userB has to buy on the same device where userA has bought, userB has to switch to own google account and make the purchase. – therealsachin Aug 28 '15 at 04:33
  • 4
    @therealsachin I get this, but If I check on my server and block userB, he will not have the chance to purchase membership with another account, because google play is checking first with the primary account, so 2 users on the same device will not work. However this can only be a problem when userA needs one more account for some reason. He will never be able to make another purchase with the same google account, not even with different payload. - The ideal case would be for me that in case of new payload, I should be able to buy subscription again. – keybee Aug 28 '15 at 12:26
  • These days you would just use the Google sign in sub field from the sign in token on your server which uniquely identifies the user so there would be no question of who is purchasing what... –  May 27 '18 at 09:35
21

There is also another approach to the developer payload handling. As Nikolay Elenkov said it is too much overhead to require user ID and setting additional permissions for user profile to your app, so this is not a good approach. So let's see what Google says in the latest version of TrivialDrive sample app in In-App Billing v3 samples:

  • 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 the random string is not a good idea if you are going to verify the purchased item on another device, but still they don't say this is not a good idea for verifying the purchase response. I would say - use developer payload only for verifying the purchase by sending a random unique string, save it in preferences/database and on the purchase response check this developer payload. As for querying the inventory (in-app purchases) on Activity start - don't bother checking developer payload since that might happen on another device where you don't have that random unique string stored. That's how I see it.

Dokker
  • 219
  • 1
  • 2
  • 4
    Thanks, I'm implementing this approach (and I've upvoted :) because it sounds like a good compromise, but I think there's a problem with the logic as described. If payload verification fails on purchase then Google Play still thinks you own the item, so you just have to restart the app, it will read the inventory, and without payload verification give you the item. One fix is to consume the item if payload verification fails on purchase, but then you've taken the user's money which may lead to nasty emails if this ever happens innocently... – Georgie Apr 21 '13 at 07:48
  • If you provide device id as a payload you can implement "per device licence" sales. So, if the user has made the purchase on one device he will not be able to use it on another. This could be useful on selling full version of an application, f.ex. antivirus. – bancer Sep 13 '13 at 16:41
  • Read @Georgie comment. Otherwise you'll spend a lot of time figuring out that this is a bad idea. – Halvor Holsten Strand Aug 14 '14 at 02:02
16

It depends how you verify the developerPayload. There are two scenarios: remote verification (using server) and local (on device).

Server

If you're using a server for developerPayload verification it can be arbitrary string that can be easily computed on both the device and server. You should be able to identify the user who has performed the request. Assuming every user has the corresponding accountId, the developerPayload may be computed as combination with purchaseId (SKU name) like this:

MD5(purchaseId + accountId)


Device

developerPayload shouldn't be user email. A good example why you shouldn't use email as payload is Google for Work service. Users are able to change their email associated with the account. The only constant thing is accountId. In most cases email will be OK (e.g. Gmail addresses are immutable at the moment), but remember to design for future.

Multiple users may use the same device, so you must be able to distinguish who's the owner of the item. For device verification developerPayload is a string that uniquely identifies the user e.g.:

MD5(purchaseId + accountId)


Conclusion

Generally the developerPayload in both cases may be just the accountId. For me it looks like security through obscurity. The MD5 (or other hashing algorithm) and purchaseId is just a way to make the payload more random without explicitly showing that we're using id of the account. The attacker would have to decompile the app to check how it is computed. If the app is obfuscated even better for you.

The payload doesn't provide any security. It can be easily spoofed with 'device' approach and without any effort seized in 'server' checking. Remember to implement signature checking using your public key available in the Google Publisher account console.

*A must-read blog post about using account id instead of email.

tomrozb
  • 25,773
  • 31
  • 101
  • 122
  • 1
    what is accountId? & How to get the accountId? is it possible to maintain same accountId for the multiple google play account (while switching account) for more details https://github.com/googlesamples/android-play-billing/issues/2 – Sarath Kumar Nov 25 '16 at 14:20
5

In the Google IO video about IAB v3 given by the author of the trivial drive sample himself, this was briefly addressed towards the end of the video. It's to prevent replay attacks, e.g. attacker sniffs the traffic, steals the packet containing a successful purchase, then tries to replay the packet on his own device. If your app doesn't check the identity of the buyer via the dev payload (ideally on your server) before releasing the premium content (also ideally from your server), the attacker will succeed. Signature verification can't detect this since the packet is intact.

In my opinion, this protection seems ideal for apps with online account connectivity like clash of clans (payload comes in naturally since you have to identify users anyway), especially where hacking compromises multiplayer gameplay with far reaching effects other than a simple localized case of piracy. In contrast, if client side hacks on the apk can already unlock the premium content then this protection is not very useful.

(If the attacker attempts to spoof the payload, the signature verification should fail).

Kevin Lee
  • 2,307
  • 26
  • 31
  • can i use firebase with google sign in and realtime database with fields of account_id, developer_payload storing in realtime db checking them while user opens app (with additional shared preference for offline facility ) ? – Dhaval Jotaniya Feb 13 '18 at 05:19
5

Late 2018 update: The official Google Play Billing Library intentionally does not expose the developerPayload. From here:

The field developerPayload is a legacy field, kept to maintain the compatibility with old implementations, but as mentioned on Purchasing In-app Billing Products page (https://developer.android.com/training/in-app-billing/purchase-iab-products.html), this field isn't always available when completing tasks related to In-app Billing. And since the library was designed to represent the most updated development model, we decided to don't support developerPayload in our implementation and we have no plans to include this field into the library.

If you rely any important implementation of your in-app billing logic on the developerPayload, we recommend you change this approach, because this field will be deprecated at some point (or soon). The recommended approaches is to use your own backend to validate and track important details about your orders. For more details, check the Security and Design page (https://developer.android.com/google/play/billing/billing_best_practices.html).

Chris Lacy
  • 4,222
  • 3
  • 35
  • 33
3

I struggled with this one. Since a Google Play account can only own one of any "managed" item, but could have several devices (I have three), the above comment from somebody that you sell a "per device" won't work... they'd be able to put it on their first device, and no others ever... If you buy a premium upgrade, it should work on all your phones/tablets.

I despise the notion of getting the user's email address, but I really found no other reliable method. So I grab the 1st account that matches "google.com" in the accounts list (yep, a permission to add to your manifest), and then immediately hash it so it's no longer usable as an email address but does provide a "unique enough" token. That's what I send as the Developer Payload. Since most people activate their device with their Google Play id, there's a good shot all three devices will get the same token (using the same hash algorithm on each device).

It even works on KitKat with multiple "users". (My developer id is on one user, my test id on another, and each user in their own sandbox).

I've tested it across six devices with a total of 3 users and each users devices have returned the same hash, and the different users all have distinct hashes, satisfying the guidelines.

At no point am I storing the user's email address, it's passed straight from the code to get the account names to the hash function and only the hash is saved in the heap.

There's probably still a better solution out there that respects users privacy even more, but so far I haven't found it. I'll be putting a very clear description of how I use the users Email address in my privacy policy once the app is published.

elixenide
  • 44,308
  • 16
  • 74
  • 100
  • 6
    That's all great but requires ACCOUNTS permissions which is unnecessary overkill here. Why on earth google could not make a hash of the account used to make a purchase accessible... That would be the cleanest solution for that. Now it's just plain "broken". – inteist Oct 05 '14 at 03:46
  • I did something similar to this but now, with Android 6, the app has to ask the user to grant the Contacts permission, which is a terrible experience for the user when they start the app for the first time. Maybe I'll implement a Restore Purchases option instead. Also, if you grab the first google.com account you can run into problems if the user has more than one account on their phone and rearranges accounts (e.g., delete account A then add it again later, but now it's not the first account). It does happen, although admittedly rarely. So then you need to use the Account Picker too! Yuch! – snark Aug 07 '16 at 18:17
1

This often responds to a product definition (Your application). For example for the case of subscriptions. Will the same user be able to use the subscription on all the devices he / she has? If the answer is yes. We did not check the payload.

For consumables. Suppose a purchase in your application gives you 10 virtual coins. Will the user be able to use these coins on different devices? 4 on one device and 6 on another? If we want to work only on the device that made the purchase we have to check the payload for example with a self-generated string and locallly stored.

Based on these questions we have to decide how to implement payload check.

Regards

Santiago

Santiago SR
  • 171
  • 1
  • 4