2
\$\begingroup\$

I want to add in-app purchases to my application. In-app in test mode works great, but there is no check on their authenticity. While googling some info about making server-side of in-app, I found out that server-side won't work correctly with test purchases.

Will this code work fine with real purchases or is there anything that won't work in production?

Client-side:

IabHelper mHelper;
public boolean billingworks=false;
String itemsku="Item name";
String AccLogin="Account name";

public void initBilling(){ //Is called inside onCreate
    String base64EncodedPublicKey = 
                                   "Public key from Google Play";

        mHelper = new IabHelper(this, base64EncodedPublicKey);

        mHelper.startSetup(new 
        IabHelper.OnIabSetupFinishedListener() {
             public void onIabSetupFinished(IabResult result) 
         {
            if (result.isSuccess()){
            List additionalSkuList = new ArrayList();
            additionalSkuList.add(itemsku);
            mHelper.queryInventoryAsync(true, additionalSkuList, mQueryFinishedListener);
             billingworks=true;} else
             Toast.makeText(getApplicationContext(),"An error occurred with billing system.",1024).show();
         }
        });
}

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

    }
}

public void buyItem(){
    if (billingworks){
mHelper.flagEndAsync();
        mHelper.launchPurchaseFlow(this, itemsku, 10001,   
               mPurchaseFinishedListener, "Ejfjejf");
               toast("Trying to purchase...");
    } else
    Toast.makeText(this,"An error occurred. Try to restart",1024).show();
}

public void toast(String s){
    Toast.makeText(this,s,1024).show();
}

public String urlencode(String s){
    try{
    return URLEncoder.encode(s,"UTF-8");
    }catch(Exception e){return "none";}
}

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {

        if (result.isFailure()) {
            toast("Error purchasing: "+result);
            return;}
        if (!verifyDeveloperPayload(purchase)) {
            toast("Error purchasing. Authenticity verification failed.");
            return;
        }

        if (purchase.getSku().equals(itemsku)) {

            mHelper.consumeAsync(purchase, mConsumeFinishedListener);

        }}};

IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() 
{
public void onConsumeFinished(Purchase purchase, IabResult result) 
{
    if (mHelper == null) return;
    if (result.isSuccess()) 
    {
        try{
        JSONObject checker=new JSONObject(purchase.getOriginalJson());
            toast("Activating account...");
            String url="http://mypage/check.php?login="+AccLogin+"&responsedata="+urlencode(purchase.getOriginalJson())+"&signature="+urlencode(purchase.getSignature());
            String[] verify=getPageS(url);
            if (verify.length>0) toast(verify[0]);
        }catch(Exception e){}
    } 
    else 
    {
        // handle error
    }
}
};

IabHelper.QueryInventoryFinishedListener 
   mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
   public void onQueryInventoryFinished(IabResult result, Inventory inventory)   
   {
  if (result.isFailure()) {
     return;
   }

   Purchase p =
      inventory.getPurchase(itemsku);
      if (p!=null)
          mHelper.consumeAsync(p, mConsumeFinishedListener);

   }
};

Server-side:

$responseCode = 0;
$user = $_GET['login'];
$data = json_decode(urldecode($_GET['responsedata'],true)); 
$signature = urldecode($_GET['signature']); 
$publicKey = "Public key from GP"; 
$key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($publicKey, 64, "\n") . "-----END PUBLIC KEY-----"; 
$key = openssl_get_publickey($key); 
if (false === $key) { 
    exit("error openssl_get_publickey"); 
}

$result = openssl_verify($data, base64_decode($signature), $key); 
if ($result == 1) { 
//Activating account 
} elseif ($result == 0) { 
    echo "bad"; 
} else { 
    echo "error"; 
} 
\$\endgroup\$
3
  • \$\begingroup\$ Do you know if this code works? Because submitting broken code is off topic here. \$\endgroup\$
    – Yann
    Commented Jun 30, 2015 at 14:51
  • \$\begingroup\$ sounds to me like it works, but there is no way to test a specific aspect of the app in the test environment. I would say it is on-topic. \$\endgroup\$
    – Malachi
    Commented Jun 30, 2015 at 15:02
  • \$\begingroup\$ Client-side part of this code works perfectly while server-side one tells me that I give wrong data. Is it true that server-side part doesn't work with test mode purchases? \$\endgroup\$ Commented Jun 30, 2015 at 15:42

1 Answer 1

1
\$\begingroup\$

This is a big, big issue with your code: indentation.

It is excruciatingly difficult to read code that is poorly indented. Here is how indentation works:

  • Everything starts without indentation
  • Every time you create a code block ({ [code] }), another indentation is added.

Typically a single indentation is either 1 tab or 4 spaces. I use 1 tab.

For example, let's take this snippet from your code:

if (billingworks){
mHelper.flagEndAsync();
        mHelper.launchPurchaseFlow(this, itemsku, 10001,   
               mPurchaseFinishedListener, "Ejfjejf");
               toast("Trying to purchase...");
    } else
    Toast.makeText(this,"An error occurred. Try to restart",1024).show();

Here is what it would look like with correct indentation:

if (billingworks) {
    mHelper.flagEndAsync();
    mHelper.launchPurchaseFlow(this, itemsku, 10001,   
    mPurchaseFinishedListener, "Ejfjejf");
    toast("Trying to purchase...");
} else
    Toast.makeText(this,"An error occurred. Try to restart",1024).show();

I actually can see what this code is doing now.


Honestly, this code is so poorly indented that I'm not sure I can even continue the review; I don't want to give any false information based on my interpretation of what I can read of the code.

However, I will try to at least point a few more things out.


if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
    super.onActivityResult(requestCode, resultCode, data);
} else {

}

Why is there an else statement? You have nothing in it, so there is no point in putting this here at all.


IabHelper mHelper;
public boolean billingworks=false;
String itemsku="Item name";
String AccLogin="Account name";

I don't this code is it; I am assuming that it is in a class.

If so, then these variables should all have scope identifiers, like the one that billingworks has.

However, going back to billingworks, I think you should make this a private field. Keeping this public could be a potential security issue. Also, from I am shown, you are only accessing billingworks from inside the class, so there is no point in letting it be access globally.

For the same reason as I just stated, I think the other fields should be private too.

The reason why I say this could be a security issue is because this is going to be run on a client's side and it is handling billing and payment information. Someone might be able to manipulate the information if the field is public.


Please, please go through your code and fix the indentation. I'm sure there is plenty more to review in this post.

Once you fix your code, post a follow up question and leave a comment to this answer; I will review the new one also.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.