[TUT] Simple InApp Billing / Payment V3

Google have released a new version of their In App Billing (Version 3). So I’ve followed suit and updated my tutorial!

Eclipse Project Source Code Download

GitHub Source Code Repo Link

There are a fair few changes, too many to fit into one tutorial but the overview is:

All purchases are now managed purchases
This means that once a user buys your item they own it, you have to ‘consume the purchase’ if you want to allow it to be purchased again. This is used in this tutorial but not explained or coded in a clean way so it is left for my next tut.
The benefits of this are that all purchases will be stored by Google Play and it will be easy to retrieve the status of whether a user has already purchased an item.

The Android team have released a ‘helper’ class
This is to make in app purchases that much simpler. It has a vague resemblance to my original ‘BillingHelper’ class only it is called ‘IabHelper’ .. very interesting. This helper class makes things a hell of a lot easier.

Let’s get straight into it.

To start with I’d like to say, this isn’t the only way to do this. I myself have followed the developer tutorials on developer.android and managed to create this test project which I can now use as a reference when making other projects. Feel free to comment on the style and ask any questions. I would recommend you download the test project I have attached rather than copy and paste, this will stop minor syntax errors.

Developer.Android Links:
Google Billing Overview
Google Billing Integration

Ok first the outline.

Were going to create and app that has one button, when you click this button it informs the android market you want to make a purchase. On confirmation of this purchase you have bought a picture of a passport and it is shown to you in the app.

Simple setup stuff will be skipped (it is all in the download source).

First you create your project with the IInAppBillingService.aidl file included. You then need to add the Billing Helper classes, look at the source code, I have added these in the android.vending.billing.util package. They are the same as the Android example except the IabHelper which I have modified when I found some slight bugs.

Create 3 Activities, the first is the start up activity. This checks if In App Purchase is available on the device. If it is we load the main activity if it isn’t we don’t let them go any further:

StartUpActivity.java

package com.blundell.tutorial.simpleinappbillingv3.ui;

import android.os.Bundle;

import com.android.vending.billing.util.IabHelper.OnIabSetupFinishedListener;
import com.android.vending.billing.util.IabResult;
import com.blundell.tutorial.simpleinappbillingv3.ui.base.PurchaseActivity;
import com.blundell.tutorial.simpleinappbillingv3.util.Log;

/**
 * Checks that In App Purchasing is available on this device
 * 
 * @author Blundell
 * 
 */
public class StartUpActivity extends PurchaseActivity implements OnIabSetupFinishedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("App started");
    }

    @Override
    public void onIabSetupFinished(IabResult result) {
        if (result.isSuccess()) {
            Log.d("In-app Billing set up" + result);
            dealWithIabSetupSuccess();
        } else {
            Log.d("Problem setting up In-app Billing: " + result);
            dealWithIabSetupFailure();
        }
    }

    @Override
    protected void dealWithIabSetupSuccess() {
        navigate().toMainActivity();
        finish();
    }

    @Override
    protected void dealWithIabSetupFailure() {
        popBurntToast("Sorry In App Billing isn't available on your device");
    }
}

This StartUpActivity extends the PurchaseActivity. The PurchaseActivity is what talks to the Android helper service. Whenever the activity is created it will check that in app billing is still supported at this time.

PurchaseActivity.java

package com.blundell.tutorial.simpleinappbillingv3.ui.base;

import android.content.Intent;
import android.os.Bundle;

import com.android.vending.billing.util.*;
import com.android.vending.billing.util.IabHelper.OnIabPurchaseFinishedListener;
import com.android.vending.billing.util.IabHelper.OnIabSetupFinishedListener;
import com.blundell.tutorial.simpleinappbillingv3.AppProperties;
import com.blundell.tutorial.simpleinappbillingv3.R;
import com.blundell.tutorial.simpleinappbillingv3.domain.items.Passport;
import com.blundell.tutorial.simpleinappbillingv3.util.Log;

/**
 * This class should be the parent of any Activity that wants to do in app purchases
 * it makes our life easier by wrapping up the talking to the IabHelper and just exposing what is needed.
 * 
 * When this activity starts it will bind to the Google Play IAB service and check for its availability
 * 
 * After that you can purchase items using purchaseItem(String sku) and listening for the result
 * by overriding dealWithPurchaseFailed(IabResult result) and dealWithPurchaseSuccess(IabResult result, Purchase info)
 * 
 * @author Blundell
 * 
 */
public abstract class PurchaseActivity extends BlundellActivity implements OnIabSetupFinishedListener, OnIabPurchaseFinishedListener {

    private IabHelper billingHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_purchase);
        setResult(RESULT_CANCELED);

        billingHelper = new IabHelper(this, AppProperties.BASE_64_KEY);
        billingHelper.startSetup(this);
    }

    @Override
    public void onIabSetupFinished(IabResult result) {
        if (result.isSuccess()) {
            Log.d("In-app Billing set up" + result);
            dealWithIabSetupSuccess();
        } else {
            Log.d("Problem setting up In-app Billing: " + result);
            dealWithIabSetupFailure();
        }
    }

    protected abstract void dealWithIabSetupSuccess();

    protected abstract void dealWithIabSetupFailure();

    protected void purchaseItem(String sku) {
        billingHelper.launchPurchaseFlow(this, sku, 123, this);
    }

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

    /**
     * Security Recommendation: When you receive the purchase response from Google Play, make sure to check the returned data
     * signature, the orderId, and the developerPayload string in the Purchase object to make sure that you are getting the
     * expected values. You should verify that the orderId is a unique value that you have not previously processed, and the
     * developerPayload 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.
     */
    @Override
    public void onIabPurchaseFinished(IabResult result, Purchase info) {
        if (result.isFailure()) {
            dealWithPurchaseFailed(result);
        } else if (Passport.SKU.equals(info.getSku())) {
            dealWithPurchaseSuccess(result, info);
        }
        finish();
    }

    protected void dealWithPurchaseFailed(IabResult result) {
        Log.d("Error purchasing: " + result);
    }

    protected void dealWithPurchaseSuccess(IabResult result, Purchase info) {
        Log.d("Item purchased: " + result);
        // DEBUG XXX
        // We consume the item straight away so we can test multiple purchases
        billingHelper.consumeAsync(info, null);
        // END DEBUG
    }

    @Override
    protected void onDestroy() {
        disposeBillingHelper();
        super.onDestroy();
    }

    private void disposeBillingHelper() {
        if (billingHelper != null) {
            billingHelper.dispose();
        }
        billingHelper = null;
    }
}

Then our MainActivity, this just has the button for purchasing a passport. It will also receive a callback saying if the passport was bought or not.

The MainActivity starts the PurchasePassportActivity using startActivityForResult, this is how we inform it of a purchase. As with the StartUpActivity the PurchasePassportActivity extends our PurchaseActivity allowing it to talk to the Google Play Billing Service (and it’s helper class).

When the PurchasePassportActivity is started, it will get a callback saying billing is available and go onto to purchase the passport. The only thing special in this Activity is that is passed the SKU (the items ID from Google Play) to the Helper service. In this tutorial we use the “android.test.purchased” SKU meaning purchases will always be successful.

PurchasePassportActivity

package com.blundell.tutorial.simpleinappbillingv3.ui;

import android.os.Bundle;

import com.android.vending.billing.util.IabResult;
import com.android.vending.billing.util.Purchase;
import com.blundell.tutorial.simpleinappbillingv3.domain.items.Passport;
import com.blundell.tutorial.simpleinappbillingv3.ui.base.PurchaseActivity;

/**
 * This activity will purchase a Passport from Google Play.
 * 
 * If you wanted to change to purchase something else all you have to change is the SKU (item id) that is used
 * you could even pass this in as an Intent EXTRA to avoid duplication for multiple items to purchase
 * 
 * N.B that we extend PurchaseActivity if you don't understand something look up to this class
 * 
 * @author Blundell
 * 
 */
public class PurchasePassportActivity extends PurchaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set the result as cancelled in case anything fails before we purchase the item
        setResult(RESULT_CANCELED);
        // Then wait for the callback if we have successfully setup in app billing or not (because we extend PurchaseActivity)
    }

    @Override
    protected void dealWithIabSetupFailure() {
        popBurntToast("Sorry buying a passport is not available at this current time");
        finish();
    }

    @Override
    protected void dealWithIabSetupSuccess() {
        purchaseItem(Passport.SKU);
    }

    @Override
    protected void dealWithPurchaseSuccess(IabResult result, Purchase info) {
        super.dealWithPurchaseSuccess(result, info);
        setResult(RESULT_OK);
        finish();
    }

    @Override
    protected void dealWithPurchaseFailed(IabResult result) {
        super.dealWithPurchaseFailed(result);
        setResult(RESULT_CANCELED);
        finish();
    }

}

Note- The code below in the PurchaseActivity.java would be removed if you wanted to use this. It is there because the new version of in app billing treats all purchases as “managed items” i.e. when you buy them once you own them until you use them or they are refunded. So this code automatically ‘refunds’ the purchase so the demo doesn’t just work first time!

// DEBUG XXX
// We consume the item straight away so we can test multiple purchases
billingHelper.consumeAsync(info, null);
// END DEBUG

That’s it, the rest of the code are just helpers and comments to try and explain what is going on and making it easier to extend.

As I always say This code tutorial isn’t foolproof and I feel I may of swept over a few things. But I really want to just give an alternative to the tutorial that is on the developer.android site. You could read this and understand the smaller concepts then go on to make a better implementation.

If you want to productionise this code you will want to go away and look at some of the security tutorials, obfuscation etc.

Remember to say thanks and enjoy!

Here are the code links again:

Eclipse Project Source Code Download

GitHub Source Code Repo Link

93 thoughts on “[TUT] Simple InApp Billing / Payment V3

  1. I used your sample code. It works fine till purchasing. But after purchasing I have to get the purchase credentials through my app. I call a service api from method “onSuccessfulPurchased” and pass the purchase class variable as the parameter, But my service is not working. I guess it is not called from that class.
    Please help me to resolve this issue.
    You can contact me through my mail too.

    Thanks.

  2. Cool, very helpful. Super.
    I just added some messages in Strings to have nothing inside.

Comments are closed.