How to validate android billing receipt server side
Asked Answered
A

2

7

I'd like to make server side validation of a purchase receipt made in an android application. I'm using a node.js backend.

I'm currently trying to do it using google-play-purchase-validator node module (https://www.npmjs.org/package/google-play-purchase-validator) which seems the most up-to-date module to do it (performing a real-time request to google purchase api).

In the Google developper console, i created a Google Service Accounts, and then obtained an email and key to be used in the module, I also connect this Services Accounts with my application as explained in this post Use service account to verify Google InAppPurchase). Unfortunatly it doesn't work. It seems that the jwt token generated by the node module isn't correctly signed (I get the following error : failed to sign JWT, the key is probably invalid).

Here's some code :

var Verifier = require('google-play-purchase-validator');
var options = {
  email:'[email protected]',
  key: 'myprivatekey',
};
var verifier = new Verifier(options);
verifier.verify(item.Receipt, function cb(err, response) {
if (err) {
    console.log("there was an error validating the receipt");
    console.log(err);
}
else{
    console.log("sucessfully validated the receipt");
    console.log(response);
}

The private key comes from a .pem file i was able to generate from a .p12 file provided by google using the following command :

openssl pkcs12 -in downloaded-key-file.p12 -out your-key-file.pem -nodes

The google-play-purchase-validator module is based on some others modules (google-oauth-jwt, request, crypto). I tried to debug a little, everything seems to be done correctly.

Any idea where i may be wrong ?

Paul

Abbess answered 15/11, 2014 at 0:43 Comment(1)
This app works only with subscriptions, and using obsolote V2 api, does anyone got other ideas to verify google play receipt?Room
T
10

This answer will be only useful once you have followed steps of Above accepted answer. i am using googleapis npm package to validate consumable purchase.

    import { google } from 'googleapis';
    
    const auth = new google.auth.GoogleAuth({
                credentials: {
                    client_email: 'email from json file',
                    private_key:
                        'private key from json file',
                },
                scopes: ['https://www.googleapis.com/auth/androidpublisher'],
            });
            const authClient = await auth.getClient();
            google.options({ auth: authClient });
            try {
// packageName,productId,token you can get from request sent from android
                const purchaseResponse: AndroidPurchaseResponse = await google
                    .androidpublisher({
                        version: 'v3',
                    }).purchases.products.get({
                        packageName: 'packageName',
                        productId: 'productId',
                        token: 'purchaseToken',
                    });
    
                if (purchaseResponse.data.purchaseState !== 0) {
                    throw new BadRequestException('Purchase is either Pending or Cancelled!');
                }
                if (purchaseResponse.data.consumptionState !== 0) {
                    throw new BadRequestException('Purchase is already consumed!');
                }
                if (purchaseResponse.data.orderId !== requestDto.orderId) {
                    throw new BadRequestException('Invalid orderId');
                }
                return purchaseResponse;
            } catch (e) {
                throw new BadRequestException(e);
            }
Toadeater answered 9/10, 2020 at 7:2 Comment(2)
Such a good answer - there's so much noise out there on this subject. These few lines of code are all you really need. One small improvement suggestion - Google has deprecated developerPayload so there's no need to check this field in the response any longer (May 2021). developer.android.com/google/play/billing/developer-payloadRebus
Thanks for the suggestion, will update the answer.Toadeater
B
12

Just added instructions on how to get the correct credentials to the module's Readme.md.

https://www.npmjs.org/package/google-play-purchase-validator

Or find them here:

  1. Start off by going into the google play developer console as the main Administrator of the account (this role is the only one who is allowed to perform the following steps).
  2. Go to "Settings->API Access" to link a Google Developer project to this account.
  3. If you are new here choose "create new project".
  4. You will now have more options. Choose "Create Service Account".
  5. Follow the link to the Google Developer Console and your project
  6. Click "Create new client id" to create a new client ID
  7. Ignore the .p12 file that will be downloaded to your machine and instead click "generate new JSON key".
  8. This will download a JSON file to your machine. We will get back to that file in a second.
  9. Now go back to the play store publisher account and click "done". Your new generate user will appear here.
  10. Click "grant access" and grant the user rights to read your project.
  11. Now setup your Node.JS project with this module and provide the email-address and the private key found in the json file as options to this module.
Bonaparte answered 17/11, 2014 at 9:47 Comment(2)
thanks a lot for your answer, it saved us at least a few hours !Baines
We've done exactly the same, but we still get the 'failed to sign JWT, the key is probably invalid'. The private key we enter looks like: -----BEGIN RSA PRIVATE KEY-----....-----END RSA PRIVATE KEY-----. Anything else we can do? Could be an issue with new lines?Ulloa
T
10

This answer will be only useful once you have followed steps of Above accepted answer. i am using googleapis npm package to validate consumable purchase.

    import { google } from 'googleapis';
    
    const auth = new google.auth.GoogleAuth({
                credentials: {
                    client_email: 'email from json file',
                    private_key:
                        'private key from json file',
                },
                scopes: ['https://www.googleapis.com/auth/androidpublisher'],
            });
            const authClient = await auth.getClient();
            google.options({ auth: authClient });
            try {
// packageName,productId,token you can get from request sent from android
                const purchaseResponse: AndroidPurchaseResponse = await google
                    .androidpublisher({
                        version: 'v3',
                    }).purchases.products.get({
                        packageName: 'packageName',
                        productId: 'productId',
                        token: 'purchaseToken',
                    });
    
                if (purchaseResponse.data.purchaseState !== 0) {
                    throw new BadRequestException('Purchase is either Pending or Cancelled!');
                }
                if (purchaseResponse.data.consumptionState !== 0) {
                    throw new BadRequestException('Purchase is already consumed!');
                }
                if (purchaseResponse.data.orderId !== requestDto.orderId) {
                    throw new BadRequestException('Invalid orderId');
                }
                return purchaseResponse;
            } catch (e) {
                throw new BadRequestException(e);
            }
Toadeater answered 9/10, 2020 at 7:2 Comment(2)
Such a good answer - there's so much noise out there on this subject. These few lines of code are all you really need. One small improvement suggestion - Google has deprecated developerPayload so there's no need to check this field in the response any longer (May 2021). developer.android.com/google/play/billing/developer-payloadRebus
Thanks for the suggestion, will update the answer.Toadeater

© 2022 - 2024 — McMap. All rights reserved.