Search code examples
androidnode.jsgoogle-playgoogle-console-developer

How to valid purchase between an app and node.js server using google-play-purchase-validator


I have an app that I am testing that is free to use but has in app purchases. I have it running in alpha on the google play store, I got past using the android.test SKU and then installed my own that work. Now, when someone makes a purchase I want the app to communicate to my server the information that would be necessary for my server to ask google if the app purchase was legit, to then turn around and credit their account appropriately since control of services is done by the server. So far I have this:

In my index.js file of my node server I have a segment like this:

var Verifier = require('google-play-purchase-validator');

function validUserPurchases (receipt) {
    var options = {
        email: "#################@developer.gserviceaccount.com",
        key: "-----BEGIN PRIVATE madness -----END PRIVATE KEY-----\n",
        keyFile: "./Google Play Android Developer-##########.json"
    };

    var verifier = new Verifier(options);

    verifier.verify(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");
            //do crap on my server to grant that account new privileges
            console.log(response);
        }
    });
    
}

where receipt is a string is the purchase.getOriginalJson() object from the following lines of code in my mainactivity.java in the app:

@Override
public void StartPayment(String payment) {
    pricing = payment; // this is a SKU set by the user in a fragment
    my_socket.getDeveloperPayload(); //this gets a public key for the app from my server

}

@Override
public void yourDeveloperPayLoad(String s) {
    your_pay_load = s; //something I am doing to uniquely stamp each transaction on my end
    if (your_pay_load.isEmpty()) {
        toastShort("Your account was not billed, something went wrong, try again.");
    } else {
        mHelper.launchPurchaseFlow(this, pricing, 100001,
                mPurchaseFinishedListener, your_pay_load);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode,
                                Intent data)
{
    if (!mHelper.handleActivityResult(requestCode,
            resultCode, data)) {
        super.onActivityResult(requestCode, resultCode, data);
    }
}


IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =
        new IabHelper.OnIabPurchaseFinishedListener() {

            @Override
            public void onIabPurchaseFinished(IabResult result, Purchase info) {
                if (result.isFailure()) {
                    //handle errors
                    return;
                } else if (info.getSku().equals(pricing)) {
                    consumeItem();
                }
            }
        };

private void consumeItem() {
    mHelper.queryInventoryAsync(mReceivedInventoryListener);

}

IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
        = new IabHelper.QueryInventoryFinishedListener() {

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        if (result.isFailure()) {
            //handle failure
        } else {
            mHelper.consumeAsync(inv.getPurchase(pricing),
                    mConsumeFinishedListener);
        }
    }
};

IabHelper.OnConsumeFinishedListener mConsumeFinishedListener
        = new IabHelper.OnConsumeFinishedListener() {

        @Override
        public void onConsumeFinished(Purchase purchase, IabResult result) {
            if (result.isSuccess()) {
                System.out.println("Checkout was successful");
                //System.out.println("Purchase has: " + purchase);
                //System.out.println("Purchase SKU: " + purchase.getSku());
                System.out.println("Purchase getDeveloperPayload: " + purchase.getDeveloperPayload());
                System.out.println("Purchase getItemType: " + purchase.getItemType());
                System.out.println("Purchase getOriginalJson(): " + purchase.getOriginalJson());
                System.out.println("Purchase getOrderId(): " + purchase.getOrderId());
                System.out.println("Purchase getPackageName(): " + purchase.getPackageName());
                System.out.println("Purchase getSignature(): " + purchase.getSignature());
                System.out.println("Purchase getToken(): " + purchase.getToken());
                System.out.println("Purchase getPurchaseState(): " + purchase.getPurchaseState());
                System.out.println("Purchase getPurchaseTime(): " + purchase.getPurchaseTime());
                System.out.println("Purchase hashCode(): " + purchase.hashCode());
                my_socket.sendPaymentVerification(purchase.getOriginalJson());

            } else {
                System.out.println("Checkout was not successful");
            }
        }
};

I thought that purchase.getOriginalJson() would constitute everything the server would need to send to google through verifier.verify(receipt, function cb(err, response) however when I try to use a test purchase I get:

there was an error validating the receipt

[Error: No application was found for the given package name.]

Not sure what I am doing wrong, does purchase.getOriginalJson() not constitute what I need to put into the receipt for google to verify that this is a real purchase that took place? Am I unable to test this feature in alpha or beta testing channels? Ty for your time.


Solution

  • I changed this function:

    IabHelper.OnConsumeFinishedListener mConsumeFinishedListener
                = new IabHelper.OnConsumeFinishedListener() {
    
            @Override
            public void onConsumeFinished(Purchase purchase, IabResult result) {
                if (result.isSuccess()) {
                    System.out.println("Checkout was successful");
                    System.out.println("Purchase getDeveloperPayload: " + purchase.getDeveloperPayload());
                    System.out.println("Purchase getItemType: " + purchase.getItemType());
                    System.out.println("Purchase getOriginalJson(): " + purchase.getOriginalJson());
                    System.out.println("Purchase getOrderId(): " + purchase.getOrderId());
                    System.out.println("Purchase getPackageName(): " + purchase.getPackageName());
                    System.out.println("Purchase getSignature(): " + purchase.getSignature());
                    System.out.println("Purchase getToken(): " + purchase.getToken());
                    System.out.println("Purchase getPurchaseState(): " + purchase.getPurchaseState());
                    System.out.println("Purchase getPurchaseTime(): " + purchase.getPurchaseTime());
                    System.out.println("Purchase hashCode(): " + purchase.hashCode());
                    try {
                        JSONObject json = new JSONObject(purchase.getOriginalJson());
                        JSONObject obj = new JSONObject();
                        obj.put("data",json);
                        obj.put("signature", purchase.getSignature());
                        my_socket.sendPaymentVerification(obj);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
    
                } else {
                    System.out.println("Checkout was not successful");
                }
            }
        };
    

    I am using node.js where I settled on the 'iab_verifier' module:

    var IABVerifier = require('iab_verifier');
    
    function validUserPurchases(purchase) {
        var receiptData = JSON.stringify(purchase.data);
        var receiptSignature = JSON.stringify(purchase.signature);
        console.log("data: " + receiptData);
        console.log("signature: " + receiptSignature);
    
        var googleplayVerifier = new IABVerifier(googleplay_public_key);
        Receipts.findOne({ data: receiptData, signature: receiptSignature }, function (err, result) {
            if (err) {
                console.log("In receipt error");
            } else if (result == null) {
                console.log("No result for receipt found");
                var isValid = googleplayVerifier.verifyReceipt(receiptData, receiptSignature);
                if (isValid) {
                    console.log("The receipt is valid");
                    Clients.findOne({ socket_id: socket.id }, function (err, client) {
                        if (client) {
                            var receipt = new Receipts({
                                client_ID: client._id,
                                data: receiptData, unique: true,
                                signature: receiptSignature, unique: true
                            });
                            receipt.save(function (err, result) {
                                if (err) {
                                    console.log("Error saving the receipt");
                                } else {
                                    console.log("Update the clients account time");
                                    var productID = JSON.parse(receiptData).productId;
                                    updateSubscription(productID);
                                }
                            });
                        }
                    });
                } else {
                    console.log("The receipt is not valid");
                }
            } else {
                console.log("This receipt is already in use");
            }
        });  
    }
    

    Hope this save someone else some time, gl.