Search code examples
oauth-2.0google-playgoogle-play-servicesin-app-billing

Google Play In-app billing / in-app-purchases: purchase, verification, and consumption flow?


I would like to confirm that I have the correct concept of in-app billing purchase, verification, and consumption flow for single in-app purchases.

1) A user orders a product in my app, let's say gems_9. (9 gems) They query the Google Play server and enter in their password. Google Play records the order.

2) My server then uses openssl_verify to verify the transaction is valid and has the correct values. E.G.:

function validate_google_play_signature ($receipt, $signature, $public_key) {
 // Create an RSA key compatible with openssl_verify from our Google Play signature.
 $key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($public_key, 64,"\n") . '-----END PUBLIC KEY-----';
 $key = openssl_get_publickey ($key);

 // Signature should be in binary format, but it comes as BASE64.
 $signature = base64_decode($signature);

 // Verify the signature.
 $result = openssl_verify ($receipt, $signature, $key, OPENSSL_ALGO_SHA1);
 return ($result == 1);
}

3) My own server will now verify that the purchase is not yet consumed. For visitors:
https://developers.google.com/android-publisher/api-ref/purchases/products/get
Get android subscription status, failed with 403

3b) My server gives the users in-app currency.

4) Ok, great, the purchase was verified. Now I need to consume it. The app itself consumes the purchase.

Q: What stops the user from hacking my app, not consuming the purchase, and sending the same order to my server? Do I need to store and test against tokens for each user in this case, and is this the only way?

Is there a way I can consume the purchase via oauth?


Solution

  • I'm going to answer my own question as I thought of a way to prevent replays without storing signatures.

    Replay problems can be fixed by storing every signature and then checking for uniqueness.

    For non-consumable purchases, a developer payload can eliminate this need, since we can easily extract something like (for example) a user id from the token.

    For consumable purchases, it is trickier but we can still do this: simply store a total_purchased count in the token. We can verify this count from our servers. (don't forget to lock that entry from reads and writes while doing our verification!)