[TUT] Simple InApp Billing / Payment

By blundell  

*DEPRECATED Please see my new tutorial: Simple InApp Billing / Payment V3*


—> InAppPurchaseTUT Source Project Download <---

Hi guys,

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

Going to try and walk you through creating an inapp payment method with the new android market payment system. This isn’t live yet (you can’t publish the app for general use) but you can upload it to the market and do full testing.

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.

First things first.

Create a new project.

This project has a Main Activity XML and Class:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:text="Would you like to purchase this item?"
package com.blundell.test;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
public class AppMainTest extends Activity implements OnClickListener{
        private static final String TAG = "BillingService";
        private Context mContext;
        private ImageView purchaseableItem;
        private Button purchaseButton;
        /** Called when the activity is first created. */
    public void onCreate(Bundle savedInstanceState) {
        Log.i("BillingService", "Starting");
        mContext = this;
        purchaseButton = (Button) findViewById(R.id.main_purchase_yes);
        purchaseableItem = (ImageView) findViewById(R.id.main_purchase_item);
        public void onClick(View v) {
                switch (v.getId()) {
                case R.id.main_purchase_yes:
                        // nada
                        Log.i(TAG,"default. ID: "+v.getId());
        private void showItem() {
        protected void onPause() {

You could now run this project up and it won’t do anything. Fine.

There is an AIDL file that you have to include your project, this gives you access to the Remote Methods of the Android Market service. Don’t worry about this, just know that this is what is under the com.android.vending.billing package in the test project.

InApp Billing in android basically means:
Tell the Android Market what the user is buying In the test project it is “android.test.purchase”
The Android Market compares this against a list you create (on your publisher page) [url=Testing]http://developer.android.com/guide/market/billing/billing_testing.html[/url]
User fills in their details You dont have to worry about this
You receive a message saying purchase complete or failed.

In order to tell the Android Market what you are buying you need to be running the billing service. ( A service that talks between your app and the android market) This looks like:

package com.blundell.test;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import com.android.vending.billing.IMarketBillingService;
public class BillingService extends Service implements ServiceConnection{
        private static final String TAG = "BillingService";
        /** The service connection to the remote MarketBillingService. */
        private IMarketBillingService mService;
        public void onCreate() {
                Log.i(TAG, "Service starting with onCreate");
                try {
                        boolean bindResult = bindService(new Intent("com.android.vending.billing.MarketBillingService.BIND"), this, Context.BIND_AUTO_CREATE);
                                Log.i(TAG,"Market Billing Service Successfully Bound");
                        } else {
                                Log.e(TAG,"Market Billing Service could not be bound.");
                                //TODO stop user continuing
                } catch (SecurityException e){
                        Log.e(TAG,"Market Billing Service could not be bound. SecurityException: "+e);
                        //TODO stop user continuing
        public void setContext(Context context) {
        public IBinder onBind(Intent intent) {
                return null;
        public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "Market Billing Service Connected.");
                mService = IMarketBillingService.Stub.asInterface(service);
                BillingHelper.instantiateHelper(getBaseContext(), mService);
        public void onServiceDisconnected(ComponentName name) {

Now I have wrapped this BillingService in a BillingHelper, the helper will do all your work for you (call the market with purchases):

package com.blundell.test;
import java.util.ArrayList;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import com.android.vending.billing.IMarketBillingService;
import com.blundell.test.BillingSecurity.VerifiedPurchase;
import com.blundell.test.C.ResponseCode;
public class BillingHelper {
        private static final String TAG = "BillingService";
        private static IMarketBillingService mService;
        private static Context mContext;
        private static Handler mCompletedHandler;
        protected static VerifiedPurchase latestPurchase;
        protected static void instantiateHelper(Context context, IMarketBillingService service) {
                mService = service;
                mContext = context;
        protected static void setCompletedHandler(Handler handler){
                mCompletedHandler = handler;
        protected static boolean isBillingSupported() {
                if (amIDead()) {
                        return false;
                Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
                if (mService != null) {
                        try {
                                Bundle response = mService.sendBillingRequest(request);
                                ResponseCode code = ResponseCode.valueOf((Integer) response.get("RESPONSE_CODE"));
                                Log.i(TAG, "isBillingSupported response was: " + code.toString());
                                if (ResponseCode.RESULT_OK.equals(code)) {
                                        return true;
                                } else {
                                        return false;
                        } catch (RemoteException e) {
                                Log.e(TAG, "isBillingSupported response was: RemoteException", e);
                                return false;
                } else {
                        Log.i(TAG, "isBillingSupported response was: BillingService.mService = null");
                        return false;
         * A REQUEST_PURCHASE request also triggers two asynchronous responses (broadcast intents).
         * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides error information about the request. (which I ignore)
         * Next, if the request was successful, the Android Market application sends an IN_APP_NOTIFY broadcast intent.
         * This message contains a notification ID, which you can use to retrieve the transaction details for the REQUEST_PURCHASE
         * @param activityContext
         * @param itemId
        protected static void requestPurchase(Context activityContext, String itemId){
                if (amIDead()) {
                Log.i(TAG, "requestPurchase()");
                Bundle request = makeRequestBundle("REQUEST_PURCHASE");
                request.putString("ITEM_ID", itemId);
                try {
                        Bundle response = mService.sendBillingRequest(request);
                        //The RESPONSE_CODE key provides you with the status of the request
                        Integer responseCodeIndex       = (Integer) response.get("RESPONSE_CODE");
                        //The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI
                        PendingIntent pendingIntent = (PendingIntent) response.get("PURCHASE_INTENT");
                        //The REQUEST_ID key provides you with a unique request identifier for the request
                        Long requestIndentifier         = (Long) response.get("REQUEST_ID");
                        Log.i(TAG, "current request is:" + requestIndentifier);
                        C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
                        Log.i(TAG, "REQUEST_PURCHASE Sync Response code: "+responseCode.toString());
                        startBuyPageActivity(pendingIntent, new Intent(), activityContext);
                } catch (RemoteException e) {
                        Log.e(TAG, "Failed, internet error maybe", e);
                        Log.e(TAG, "Billing supported: "+isBillingSupported());
         * A GET_PURCHASE_INFORMATION request also triggers two asynchronous responses (broadcast intents).
         * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request.  (which I ignore)
         * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent.
         * This message contains detailed transaction information.
         * The transaction information is contained in a signed JSON string (unencrypted).
         * The message includes the signature so you can verify the integrity of the signed string
         * @param notifyIds
        protected static void getPurchaseInformation(String[] notifyIds){
                if (amIDead()) {
                Log.i(TAG, "getPurchaseInformation()");
                Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
                // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate.
                // The Android Market application returns this nonce with the PURCHASE_STATE_CHANGED broadcast intent so you can verify the integrity of the transaction information.
                request.putLong("NONCE", BillingSecurity.generateNonce());
                // The NOTIFY_IDS key contains an array of notification IDs, which you received in the IN_APP_NOTIFY broadcast intent.
                request.putStringArray("NOTIFY_IDS", notifyIds);
                try {
                        Bundle response = mService.sendBillingRequest(request);
                        //The REQUEST_ID key provides you with a unique request identifier for the request
                        Long requestIndentifier         = (Long) response.get("REQUEST_ID");
                        Log.i(TAG, "current request is:" + requestIndentifier);
                        //The RESPONSE_CODE key provides you with the status of the request
                        Integer responseCodeIndex       = (Integer) response.get("RESPONSE_CODE");
                        C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
                        Log.i(TAG, "GET_PURCHASE_INFORMATION Sync Response code: "+responseCode.toString());
                } catch (RemoteException e) {
                        Log.e(TAG, "Failed, internet error maybe", e);
                        Log.e(TAG, "Billing supported: "+isBillingSupported());
         * To acknowledge that you received transaction information you send a
         * CONFIRM_NOTIFICATIONS request.
         * A CONFIRM_NOTIFICATIONS request triggers a single asynchronous response—a RESPONSE_CODE broadcast intent.
         * This broadcast intent provides status and error information about the request.
         * Note: As a best practice, you should not send a CONFIRM_NOTIFICATIONS request for a purchased item until you have delivered the item to the user.
         * This way, if your application crashes or something else prevents your application from delivering the product,
         * your application will still receive an IN_APP_NOTIFY broadcast intent from Android Market indicating that you need to deliver the product
         * @param notifyIds
        protected static void confirmTransaction(String[] notifyIds) {
                if (amIDead()) {
                Log.i(TAG, "confirmTransaction()");
                Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
                request.putStringArray("NOTIFY_IDS", notifyIds);
                try {
                        Bundle response = mService.sendBillingRequest(request);
                        //The REQUEST_ID key provides you with a unique request identifier for the request
                        Long requestIndentifier         = (Long) response.get("REQUEST_ID");
                        Log.i(TAG, "current request is:" + requestIndentifier);
                        //The RESPONSE_CODE key provides you with the status of the request
                        Integer responseCodeIndex       = (Integer) response.get("RESPONSE_CODE");
                        C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
                        Log.i(TAG, "CONFIRM_NOTIFICATIONS Sync Response code: "+responseCode.toString());
                } catch (RemoteException e) {
                        Log.e(TAG, "Failed, internet error maybe", e);
                        Log.e(TAG, "Billing supported: " + isBillingSupported());
         * Can be used for when a user has reinstalled the app to give back prior purchases.
         * if an item for sale's purchase type is "managed per user account" this means google will have a record ofthis transaction
         * A RESTORE_TRANSACTIONS request also triggers two asynchronous responses (broadcast intents).
         * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request.
         * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent.
         * This message contains the detailed transaction information. The transaction information is contained in a signed JSON string (unencrypted).
         * The message includes the signature so you can verify the integrity of the signed string
         * @param nonce
        protected static void restoreTransactionInformation(Long nonce) {
                if (amIDead()) {
                Log.i(TAG, "confirmTransaction()");
                Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
                // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate
                request.putLong("NONCE", nonce);
                try {
                        Bundle response = mService.sendBillingRequest(request);
                        //The REQUEST_ID key provides you with a unique request identifier for the request
                        Long requestIndentifier         = (Long) response.get("REQUEST_ID");
                        Log.i(TAG, "current request is:" + requestIndentifier);
                        //The RESPONSE_CODE key provides you with the status of the request
                        Integer responseCodeIndex       = (Integer) response.get("RESPONSE_CODE");
                        C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
                        Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());
                } catch (RemoteException e) {
                        Log.e(TAG, "Failed, internet error maybe", e);
                        Log.e(TAG, "Billing supported: " + isBillingSupported());
        private static boolean amIDead() {
                if (mService == null || mContext == null) {
                        Log.e(TAG, "BillingHelper not fully instantiated");
                        return true;
                } else {
                        return false;
        private static Bundle makeRequestBundle(String method) {
                Bundle request = new Bundle();
                request.putString("BILLING_REQUEST", method);
                request.putInt("API_VERSION", 1);
                request.putString("PACKAGE_NAME", mContext.getPackageName());
                return request;
         * You must launch the pending intent from an activity context and not an application context
         * You cannot use the singleTop launch mode to launch the pending intent
         * @param pendingIntent
         * @param intent
         * @param context
        private static void startBuyPageActivity(PendingIntent pendingIntent, Intent intent, Context context){
                //TODO add above 2.0 implementation with reflection, for now just using 1.6 implem
                // This is on Android 1.6. The in-app checkout page activity will be on its
            // own separate activity stack instead of on the activity stack of
            // the application.
                try {
                        pendingIntent.send(context, 0, intent);                
                } catch (CanceledException e){
                        Log.e(TAG, "startBuyPageActivity CanceledException");
         protected static void verifyPurchase(String signedData, String signature) {
                ArrayList<VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(signedData, signature);
                if(purchases != null && !purchases.isEmpty()){
                        latestPurchase = purchases.get(0);
                        confirmTransaction(new String[]{latestPurchase.notificationId});
                } else {
                       Log.d(TAG, "BillingHelper.verifyPurchase error. purchases was null");
                if(mCompletedHandler != null){
                } else {
                        Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
        public static void stopService(){
                mContext.stopService(new Intent(mContext, BillingService.class));
                mService = null;
                mContext = null;
                mCompletedHandler = null;
                Log.i(TAG, "Stopping Service");

When a request to the android market from your BillingHelper is instantiated, the market sends back certain intent’s these need to be caught in a receiver and dealt with appropriately (so the market will say “yes I confirm this user has just bought…” ):

package com.blundell.test;
import static com.blundell.test.C.ACTION_NOTIFY;
import static com.blundell.test.C.ACTION_PURCHASE_STATE_CHANGED;
import static com.blundell.test.C.ACTION_RESPONSE_CODE;
import static com.blundell.test.C.INAPP_REQUEST_ID;
import static com.blundell.test.C.INAPP_RESPONSE_CODE;
import static com.blundell.test.C.INAPP_SIGNATURE;
import static com.blundell.test.C.INAPP_SIGNED_DATA;
import static com.blundell.test.C.NOTIFICATION_ID;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BillingReceiver extends BroadcastReceiver {
        private static final String TAG = "BillingService";
        public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                Log.i(TAG, "Received action: " + action);
        if (ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
            String signedData = intent.getStringExtra(INAPP_SIGNED_DATA);
            String signature = intent.getStringExtra(INAPP_SIGNATURE);
            purchaseStateChanged(context, signedData, signature);
        } else if (ACTION_NOTIFY.equals(action)) {
            String notifyId = intent.getStringExtra(NOTIFICATION_ID);
            notify(context, notifyId);
        } else if (ACTION_RESPONSE_CODE.equals(action)) {
            long requestId = intent.getLongExtra(INAPP_REQUEST_ID, -1);
            int responseCodeIndex = intent.getIntExtra(INAPP_RESPONSE_CODE, C.ResponseCode.RESULT_ERROR.ordinal());
            checkResponseCode(context, requestId, responseCodeIndex);
        } else {
           Log.e(TAG, "unexpected action: " + action);
        private void purchaseStateChanged(Context context, String signedData, String signature) {
                Log.i(TAG, "purchaseStateChanged got signedData: " + signedData);
                Log.i(TAG, "purchaseStateChanged got signature: " + signature);
                BillingHelper.verifyPurchase(signedData, signature);
        private void notify(Context context, String notifyId) {
                Log.i(TAG, "notify got id: " + notifyId);
                String[] notifyIds = {notifyId};
        private void checkResponseCode(Context context, long requestId, int responseCodeIndex) {
                Log.i(TAG, "checkResponseCode got requestId: " + requestId);
                Log.i(TAG, "checkResponseCode got responseCode: " + C.ResponseCode.valueOf(responseCodeIndex));

To keep the code a bit tidier all constants are stored in a class called C:

package com.blundell.test;
public class C {
        // The response codes for a request, defined by Android Market.
        public enum ResponseCode {
                // Converts from an ordinal value to the ResponseCode
                public static ResponseCode valueOf(int index) {
                        ResponseCode[] values = ResponseCode.values();
                        if (index < 0 || index >= values.length) {
                                return RESULT_ERROR;
                        return values[index];
        // The possible states of an in-app purchase, as defined by Android Market.
    public enum PurchaseState {
        // Responses to requestPurchase or restoreTransactions.
        PURCHASED,   // User was charged for the order.
        CANCELED,    // The charge failed on the server.
        REFUNDED;    // User received a refund for the order.
        // Converts from an ordinal value to the PurchaseState
        public static PurchaseState valueOf(int index) {
            PurchaseState[] values = PurchaseState.values();
            if (index < 0 || index >= values.length) {
                return CANCELED;
            return values[index];
        // These are the names of the extras that are passed in an intent from
    // Market to this application and cannot be changed.
    public static final String NOTIFICATION_ID = "notification_id";
    public static final String INAPP_SIGNED_DATA = "inapp_signed_data";
    public static final String INAPP_SIGNATURE = "inapp_signature";
    public static final String INAPP_REQUEST_ID = "request_id";
    public static final String INAPP_RESPONSE_CODE = "response_code";
        // Intent actions that we send from the BillingReceiver to the
        // BillingService. Defined by this application.
        public static final String ACTION_CONFIRM_NOTIFICATION = "com.example.dungeons.CONFIRM_NOTIFICATION";
        public static final String ACTION_GET_PURCHASE_INFORMATION = "com.example.dungeons.GET_PURCHASE_INFORMATION";
        public static final String ACTION_RESTORE_TRANSACTIONS = "com.example.dungeons.RESTORE_TRANSACTIONS";
        // Intent actions that we receive in the BillingReceiver from Market.
        // These are defined by Market and cannot be changed.
        public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
        public static final String ACTION_RESPONSE_CODE = "com.android.vending.billing.RESPONSE_CODE";
        public static final String ACTION_PURCHASE_STATE_CHANGED = "com.android.vending.billing.PURCHASE_STATE_CHANGED";

And finally, this code I took from the Android sample project. What it does is decrypt the messages that the Android Market is sending back to you and package them into a purchased item object. YOU NEED TO EDIT ONE LINE, to add your public key from your market account (edit profile):

// Copyright 2010 Google Inc. All Rights Reserved.
package com.blundell.test;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashSet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.text.TextUtils;
import android.util.Log;
import com.blundell.test.C.PurchaseState;
import com.blundell.test.util.Base64;
import com.blundell.test.util.Base64DecoderException;
 * Security-related methods. For a secure implementation, all of this code
 * should be implemented on a server that communicates with the application on
 * the device. For the sake of simplicity and clarity of this example, this code
 * is included here and is executed on the device. If you must verify the
 * purchases on the phone, you should obfuscate this code to make it harder for
 * an attacker to replace the code with stubs that treat all purchases as
 * verified.
public class BillingSecurity {
        private static final String TAG = "BillingService";
        private static final String KEY_FACTORY_ALGORITHM = "RSA";
        private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
        private static final SecureRandom RANDOM = new SecureRandom();
         * This keeps track of the nonces that we generated and sent to the server.
         * We need to keep track of these until we get back the purchase state and
         * send a confirmation message back to Android Market. If we are killed and
         * lose this list of nonces, it is not fatal. Android Market will send us a
         * new "notify" message and we will re-generate a new nonce. This has to be
         * "static" so that the {@link BillingReceiver} can check if a nonce exists.
        private static HashSet<Long> sKnownNonces = new HashSet<Long>();
         * A class to hold the verified purchase information.
        public static class VerifiedPurchase {
                public PurchaseState purchaseState;
                public String notificationId;
                public String productId;
                public String orderId;
                public long purchaseTime;
                public String developerPayload;
                public VerifiedPurchase(PurchaseState purchaseState, String notificationId, String productId, String orderId, long purchaseTime,
                                String developerPayload) {
                        this.purchaseState = purchaseState;
                        this.notificationId = notificationId;
                        this.productId = productId;
                        this.orderId = orderId;
                        this.purchaseTime = purchaseTime;
                        this.developerPayload = developerPayload;
                public boolean isPurchased(){
                        return purchaseState.equals(PurchaseState.PURCHASED);
        /** Generates a nonce (a random number used once). */
        public static long generateNonce() {
                long nonce = RANDOM.nextLong();
                Log.i(TAG, "Nonce generateD: "+nonce);
                return nonce;
        public static void removeNonce(long nonce) {
        public static boolean isNonceKnown(long nonce) {
                return sKnownNonces.contains(nonce);
         * Verifies that the data was signed with the given signature, and returns
         * the list of verified purchases. The data is in JSON format and contains a
         * nonce (number used once) that we generated and that was signed (as part
         * of the whole data string) with a private key. The data also contains the
         * {@link PurchaseState} and product ID of the purchase. In the general
         * case, there can be an array of purchase transactions because there may be
         * delays in processing the purchase on the backend and then several
         * purchases can be batched together.
         * @param signedData
         *            the signed JSON string (signed, not encrypted)
         * @param signature
         *            the signature for the data, signed with the private key
        public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature) {
                if (signedData == null) {
                        Log.e(TAG, "data is null");
                        return null;
                Log.i(TAG, "signedData: " + signedData);
                boolean verified = false;
                if (!TextUtils.isEmpty(signature)) {
                         * Compute your public key (that you got from the Android Market
                         * publisher site).
                         * Instead of just storing the entire literal string here embedded
                         * in the program, construct the key at runtime from pieces or use
                         * bit manipulation (for example, XOR with some other string) to
                         * hide the actual key. The key itself is not secret information,
                         * but we don't want to make it easy for an adversary to replace the
                         * public key with one of their own and then fake messages from the
                         * server.
                         * Generally, encryption keys / passwords should only be kept in
                         * memory long enough to perform the operation they need to perform.
                        String base64EncodedPublicKey = "PUT YOUR PUBLIC KEY HERE";
                        PublicKey key = BillingSecurity.generatePublicKey(base64EncodedPublicKey);
                        verified = BillingSecurity.verify(key, signedData, signature);
                        if (!verified) {
                                Log.w(TAG, "signature does not match data.");
                                return null;
                JSONObject jObject;
                JSONArray jTransactionsArray = null;
                int numTransactions = 0;
                long nonce = 0L;
                try {
                        jObject = new JSONObject(signedData);
                        // The nonce might be null if the user backed out of the buy page.
                        nonce = jObject.optLong("nonce");
                        jTransactionsArray = jObject.optJSONArray("orders");
                        if (jTransactionsArray != null) {
                                numTransactions = jTransactionsArray.length();
                } catch (JSONException e) {
                        return null;
                if (!BillingSecurity.isNonceKnown(nonce)) {
                        Log.w(TAG, "Nonce not found: " + nonce);
                        return null;
                ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>();
                try {
                        for (int i = 0; i < numTransactions; i++) {
                                JSONObject jElement = jTransactionsArray.getJSONObject(i);
                                int response = jElement.getInt("purchaseState");
                                PurchaseState purchaseState = PurchaseState.valueOf(response);
                                String productId = jElement.getString("productId");
                                String packageName = jElement.getString("packageName");
                                long purchaseTime = jElement.getLong("purchaseTime");
                                String orderId = jElement.optString("orderId", "");
                                String notifyId = null;
                                if (jElement.has("notificationId")) {
                                        notifyId = jElement.getString("notificationId");
                                String developerPayload = jElement.optString("developerPayload", null);
                                // If the purchase state is PURCHASED, then we require a
                                // verified nonce.
                                if (purchaseState == PurchaseState.PURCHASED && !verified) {
                                purchases.add(new VerifiedPurchase(purchaseState, notifyId, productId, orderId, purchaseTime, developerPayload));
                } catch (JSONException e) {
                        Log.e(TAG, "JSON exception: ", e);
                        return null;
                return purchases;
         * Generates a PublicKey instance from a string containing the
         * Base64-encoded public key.
         * @param encodedPublicKey
         *            Base64-encoded public key
         * @throws IllegalArgumentException
         *             if encodedPublicKey is invalid
        public static PublicKey generatePublicKey(String encodedPublicKey) {
                try {
                        byte[] decodedKey = Base64.decode(encodedPublicKey);
                        KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
                        return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
                } catch (NoSuchAlgorithmException e) {
                        throw new RuntimeException(e);
                } catch (InvalidKeySpecException e) {
                        Log.e(TAG, "Invalid key specification.");
                        throw new IllegalArgumentException(e);
                } catch (Base64DecoderException e) {
                        Log.e(TAG, "Base64DecoderException.", e);
                        return null;
         * Verifies that the signature from the server matches the computed
         * signature on the data. Returns true if the data is correctly signed.
         * @param publicKey
         *            public key associated with the developer account
         * @param signedData
         *            signed data from server
         * @param signature
         *            server signature
         * @return true if the data and signature match
        public static boolean verify(PublicKey publicKey, String signedData, String signature) {
                Log.i(TAG, "signature: " + signature);
                Signature sig;
                try {
                        sig = Signature.getInstance(SIGNATURE_ALGORITHM);
                        if (!sig.verify(Base64.decode(signature))) {
                                Log.e(TAG, "Signature verification failed.");
                                return false;
                        return true;
                } catch (NoSuchAlgorithmException e) {
                        Log.e(TAG, "NoSuchAlgorithmException.");
                } catch (InvalidKeyException e) {
                        Log.e(TAG, "Invalid key specification.");
                } catch (SignatureException e) {
                        Log.e(TAG, "Signature exception.");
                }  catch (Base64DecoderException e) {
                        Log.e(TAG, "Base64DecoderException.", e);
                return false;

That is it. Now to get this to work you have to add to your activity class.

In your onCreate you need to start the market service.

startService(new Intent(mContext, BillingService.class));

In the onClick is where we are going to request to make a purchase:

        public void onClick(View v) {
                switch (v.getId()) {
                case R.id.main_purchase_yes:
                                BillingHelper.requestPurchase(mContext, "android.test.purchased");
                                // android.test.purchased or android.test.canceled or android.test.refunded
                } else {
                        Log.i(TAG,"Can't purchase on this device");
                        // nada
                        Log.i(TAG,"default. ID: "+v.getId());

Now the BillingService won’t do anything on it’s own, so you need to load up the BillingHelper. The billing helper uses a handler to send callbacks on completed purchases:


This handler needs to be declared in your activity:

 public Handler mTransactionHandler = new Handler(){
                public void handleMessage(android.os.Message msg) {
                        Log.i(TAG, "Transaction complete");
                        Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
                        Log.i(TAG, "Item attempted purchase is: "+BillingHelper.latestPurchase.productId);
                        } else {
                               // Failure

Once you have all this don’t forget to update your manifest! You need to tell it your using a service, a broadcast reciever and those intents you wish to receive (from the android market):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    <uses-sdk android:minSdkVersion="4" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".AppMainTest"
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <service android:name=".BillingService" />
                <receiver android:name=".BillingReceiver">
                                <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
                                <action android:name="com.android.vending.billing.RESPONSE_CODE" />
                                <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />                   
    <uses-permission android:name="com.android.vending.BILLING" />

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.

May have issues with multiple purchases and network delays
I am not responsible if you use this code in a production envrionment.
Testing with real applications http://developer.android.com/guide/market/billing/billing_testing.html
Please obfuscate your code to ensure people can’t get your purchases for free!

—> InAppPurchaseTUT Source Project Download <---

Remember to say thanks and enjoy!


*DEPRECATED Please see my new tutorial: Simple InApp Billing / Payment V3*


  1. Ajith
    Posted June 10, 2014 at 9:20 pm | Permalink | Reply


    I’ve created a sample project with this code.
    For android.test.purchased- it is working fine.
    But when i used myown product Id and public key(i’ve uploaded my app in drafts)
    then im getting an error message in the pop up as “The item you were attempting to purchase could not found” Y i’m getting this error??My App is not published and the product id is in activated state.

  2. Ranadheer
    Posted April 3, 2014 at 9:18 am | Permalink | Reply

    Is there any paymentId or purchaseId which will send by play store?

  3. iwanttoknow
    Posted January 14, 2014 at 7:03 pm | Permalink | Reply

    I love the tutorial. Thank you for posting it. Im making a flash game using “adobe flash pro cc ” and would like to implement in-app purchase that is consumable such as “speed boost” that would last for only one game when you use it. My question is would this tutorial work for games that are made in flash pro? What program do others use to make android games? What program would work with this tutorial? I dont know much about this topic. I need your help.

  4. cyrus
    Posted January 13, 2014 at 3:48 am | Permalink | Reply

    hello, i try to import your project file and compile, however the ddms display words – error open tracing file: No such files or directories.

    How to fix it? Thanks

  5. pranoti
    Posted January 7, 2014 at 10:01 am | Permalink | Reply

    i got the following line in logcat
    please suggest….
    failed to bind to service with name com.android.vending.billing.inappbillingservice.bind

  6. divya
    Posted July 17, 2013 at 10:25 am | Permalink | Reply

    Hii, i wanted to implement inapp purchase for my cocos2d-android, can i use this code for it?

    • blundell
      Posted August 3, 2013 at 9:39 pm | Permalink | Reply

      yeah sure

  7. Dibyendu
    Posted May 1, 2013 at 12:28 pm | Permalink | Reply

    I am new in android field. I want to attach the inapp api with restore transaction for only managed product. Can anyone help me to send the sample complete code. Thanks in advance. My email id is- mail2dibyendu@rediffmail.com

  8. Charlie
    Posted April 30, 2013 at 11:34 am | Permalink | Reply

    Hi there, love the tutorial. Im new to android and have struggled to grasp the concept of in app billing however have found this one alot more user friendly. So i have a silly question. Can i use this code for my app? not sure the legal issues are if i do. Regards Charlie =)

    • blundell
      Posted May 4, 2013 at 11:41 am | Permalink | Reply

      Yes it’s all free

      • Charles Leota
        Posted May 5, 2013 at 3:12 am | Permalink | Reply

        thanks Blundell… if my app pays off will donate some to ya cause =)

  9. Margeret
    Posted April 21, 2013 at 12:33 pm | Permalink | Reply

    You need to be a part of a contest for one of the greatest websites online.
    I am going to recommend this blog!

  10. hire
    Posted March 25, 2013 at 5:28 am | Permalink | Reply

    Hi everybody!
    Great and it is a good sample! But I got errors :(
    item is not shown after BillingHelper.requestPurchase(Main.this, “My Product item”) but the billing works perfectly
    plz help me regarding this issue.

  11. MacDroid
    Posted December 30, 2012 at 6:52 pm | Permalink | Reply

    Your tutorial is really good for beginners. You have mentioned that it can be uploaded to market as test app. How to get the public key? 1) By what name i need to upload this test app to market(In in app product id field what should i mention in the account while uploading)? 2) How to test the app without really paying something like using dummy card details?

    • blundell
      Posted December 30, 2012 at 9:53 pm | Permalink | Reply

      If you just want to test dummy purchases you don’t need to upload to the market. Just use the item id “android.test.purchase”. Your public key is part of your Google Play account, if you want to test with real products it is explained Here – Testing with Product IDs

      • MacDroid
        Posted December 31, 2012 at 8:24 am | Permalink | Reply

        THANKS. When i launch the app in device it is connecting to market and showing app title as “Sample Title” and “Sample Merchant” as the publisher when i click accept and buy. I receive a toast message that “Thank you your item will appear shortly”. But it is not showing that image after successful responds. I am trying to upload this sample app and to learn the purchasing process in real time. So as you mentioned added the base64 public key in code after that what modification i need to do, so that i can upload this sample app in market and test in real time, So can you please help me in sorting this out Thanks in advance.

        • blundell
          Posted December 31, 2012 at 11:53 am | Permalink | Reply

          Not really sure where you are up to. If you read the LogCat as you are doing it it might give you a clue what is going on and what code is not being called, sounds like it is missing a handler callback somewhere.

          • MacDroid
            Posted December 31, 2012 at 3:15 pm | Permalink

            im trying this sample app by uploading to the account as draft and to understand by having 1 in app and purchasing. I have changed the package name and also included the public key and generated the apk in release mode i have uploaded it in market but whan i run the app in logcat it under application it is showing “com.blundell.test” but i have changed the packages name and app name in the project and if i run the app it should show the app name in which i have uploaded but instead it is showing “Sample Title” thats what i asked. Thanks once again

  12. Cloud
    Posted December 27, 2012 at 8:45 am | Permalink | Reply

    THX. But I found a bug that if I exit the app, it will crash, and said billingservice that was originally bound here. Logcat print Stopping Service , but but I don’t know how to unbind can u help me ?

    • blundell
      Posted December 28, 2012 at 5:33 pm | Permalink | Reply

      You should be unbinding from the service in onPause() of the activity.

      • Cloudd
        Posted January 6, 2013 at 11:27 am | Permalink | Reply

        The app i run in android 2.3.5 device it’ok, but run in 4.0.3.it’s some errors. it didn’t crash, but can not complete the transaction. Is it some diffirent between those verson?

        • blundell
          Posted January 9, 2013 at 9:48 am | Permalink | Reply

          Not sure, what does your LogCat say?

  13. John Kane
    Posted December 21, 2012 at 10:51 pm | Permalink | Reply

    Hi, I’m getting the same problem as sachin.

    Apparently BillingHelper.latestPurchase is null in the handler.

    Help please

  14. Posted December 14, 2012 at 12:14 pm | Permalink | Reply

    Hello sir … i am using your In app purchase code . I did not change any thing except (internet permission )in this code , when i have clicked on YES button then Google play store opened , when i am clicking on (Accept & bye ) then it shows message “thank you ! your item will appear shortly” and than force close…… shall i make any change in the code please suggest me

  15. Ethan
    Posted December 11, 2012 at 12:04 am | Permalink | Reply

    Using the code you’ve shared with us here, is it possible to restore managed transactions? For example, someone makes a purchase, deletes the app, re-downloads it, and wants to use the same feature again.

    How do I restore that in-app purchase for that person? Any ideas?


    • blundell
      Posted December 11, 2012 at 12:12 pm | Permalink | Reply

      You could use Cloud SharedPreferences to do that. See Here

  16. John Kane
    Posted December 1, 2012 at 8:06 pm | Permalink | Reply

    Great tutorial!

    Question though, where does the line

    • blundell
      Posted December 4, 2012 at 10:25 am | Permalink | Reply

      This is in the Activity, download the source and you can see it right there!

  17. Matt
    Posted November 26, 2012 at 3:23 pm | Permalink | Reply

    Thank you very much for this tutorial – it was extremely helpful in getting help with InAppBilling setup in my application.

    So your simplified code performs the exact same operations as the Dungeons example just in less code and easier to understand? Your BillingService and BillingReceiver are much smaller so just want to confirm that it will perform the same operations.

    Also can you confirm this works with the new “Play Store” as well since it was converted from the Google Market?

    Thank you again!

    • blundell
      Posted November 27, 2012 at 8:53 am | Permalink | Reply

      Yes it works with the playstore.
      It doesn’t do everything the dungeons examples does. For example it doesn’t handle refunds, but it does the basics of making purchases yes.

  18. lisa
    Posted November 26, 2012 at 9:28 am | Permalink | Reply

    Hello, I downloaded your sample project. I changed the public key to my publisher key. Then uploaded it. And I added a test account to my publisher key.I log in the device with the test account and test your project with this test account. When I click “Accept and Buy” then it will show “Your item will appear shortly” and then “the application has stopped unexpectedly.Please try again”. I really don’t know why it is so. Please help. Thank you.

    • blundell
      Posted November 27, 2012 at 8:52 am | Permalink | Reply

      Hi Lisa, please check the code on the blog post itself, I think there is a null check missing on the array in the download, but it is in the sourcecode on the site.

  19. Zizo S
    Posted November 24, 2012 at 10:23 am | Permalink | Reply

    Thank you so much for this tutorial!!! it’s very helpful and easy to understand :)
    i am new in this domain! i have implemented your code in my app, did everything you explained here and
    uploaded it as a signed test app, but when i run it on my device and click on an item to purchase, it gives me this error: “Application error This version of the application is not configured for billing through google play. check the help center for more information” (the version of the activated test app and the one used on my device are the same). if you can help me with this i will be so thankful!

    • blundell
      Posted November 25, 2012 at 8:20 pm | Permalink | Reply

      The version you run on your device has to be a signed release apk that you have created? Not a debug build that you run through eclipse

  20. Imran
    Posted November 15, 2012 at 11:31 am | Permalink | Reply

    hello thanks for ur great feedback….

    i’m having problem on calling unbindService(). In ur code stopService() is called but no unbinding service. I am confused where is to unbind the service as I found that onServiceDisconnected() is not being called…..otherwise service leak error is hampering the approach.

    • Imran
      Posted November 22, 2012 at 8:24 am | Permalink | Reply

      okay solved this service leaking issue….
      just have to unbind the service at onDestroy() of BillingService –>

      public void onDestroy() {

  21. Srinath
    Posted November 9, 2012 at 4:58 am | Permalink | Reply

    Hi…when i am testing sample app in that after pressing Accept and buy it put a toast telling “thank you,Your item will appear shortly”…

    • blundell
      Posted November 9, 2012 at 9:40 am | Permalink | Reply

      You’d just put this in the onClick?

  22. Imran
    Posted November 7, 2012 at 10:55 am | Permalink | Reply

    Really great tutorial… lot better than the fu**ed up sample of android play_billing….

    though I had a question to be clear.
    I am testing through sending “android.test.purchased” and I’m using my PUBLIC KEY but didn’t publish the apk on the market. But on mTransactionHandler, I’m receiving BillingHelper.latestPurchase == null.

    My question is “is this for not publishing the apk on market or else?”

    • blundell
      Posted November 7, 2012 at 11:25 am | Permalink | Reply

      Possibly, you should upload a draft to test purchasing

  23. Kattie
    Posted November 6, 2012 at 11:15 am | Permalink | Reply

    Thank you for another informative blog. The place else may I am getting that kind of info written in such an ideal manner?

    I’ve a mission that I’m just now operating on,
    and I have been at the glance out for such info.

  24. James
    Posted November 2, 2012 at 2:04 pm | Permalink | Reply

    Thanks for your tut, I have tiny issue with it,
    1. I have downloaded your example (Untouched)
    2. Update the Key to my key

    but i rcv this error I would be really appreciated if you could tell me what is the problem.
    Here is the screenshot of the error i get:



  25. Posted October 30, 2012 at 10:46 am | Permalink | Reply

    Awesome tutorial… Great work…..Keep going….Looking forward for more tutorials on android on this blog .

  26. khabbab
    Posted October 23, 2012 at 7:35 am | Permalink | Reply

    I would appreciate some help integrating it with my application.
    I do not want the checkout screen to appear, instead, i want to display a message regarding one time purchase like “Do you want to buy this product for 2 Dollars, Yes or No”, If user clicks on Yes button then request purchase is called and i should get a boolean response that purchase was successful or not.

    Right not, a blank screen appear and when i press the back button i get the purchase response Successful in my Log.

    Thanks in advance,

    • blundell
      Posted November 1, 2012 at 9:32 am | Permalink | Reply

      Sorry you’ll have to follow the approved steps, the actual money taking is done by the Google Play application and is out of your hands

  27. Jordi
    Posted October 16, 2012 at 8:37 am | Permalink | Reply

    Is there any way to make a product validation purchase without having to deal with the market purchase window? I need to make a verification once a week or so, in case the storeddata is wiped or something and i want to avoid the market popup saying me that the item is already bought.


  28. yogesh
    Posted October 10, 2012 at 10:32 am | Permalink | Reply

    i am working on in-app billing with your project but i am getting error
    10-10 15:47:11.457: E/AndroidRuntime(20360): java.lang.RuntimeException: Unable to start receiver com.blundell.test.BillingReceiver: java.lang.NullPointerException

    i add public key and use android.test.purchased (id) ….can u tell me what is my mistake …?
    and i am not upload any project on google i just install and run on my device …
    so its must be need to upload sign apk on market as draft…

  29. Posted October 8, 2012 at 2:20 pm | Permalink | Reply

    Billing should be as simple as a single function call:
    On GitHub
    Git Page – project.soom.la

    • blundell
      Posted October 8, 2012 at 2:33 pm | Permalink | Reply

      Yeah good point. Even nicer would be:

      Store.buyCoinPack(TEN); or even Store.buyCoins(TEN);

      • Posted October 9, 2012 at 8:28 pm | Permalink | Reply

        I agree but Google (and Apple) requires product ID to request a purchase.

        • blundell
          Posted October 10, 2012 at 9:24 am | Permalink | Reply

          Yes but you can still hide the product ID internally.

  30. crazydev
    Posted October 6, 2012 at 3:25 am | Permalink | Reply

    Hi, i’m getting an error for

    Unable to start receiver. Index is 0;

    Any help?

    • blundell
      Posted October 6, 2012 at 1:50 pm | Permalink | Reply

      You’ll have to step through the code and check your LogCat for the actual error and line number

  31. Sam
    Posted September 16, 2012 at 3:39 pm | Permalink | Reply


  32. NiMS
    Posted September 12, 2012 at 11:03 am | Permalink | Reply

    Fake purchase works okay.
    But when I try to buy real item in my game, billing window shows me item name correctly
    and the message “there is not such item to puchase”. I ‘ve created and tryed another item. The same problem.
    Does anybody has this code workable?

    • Jordi
      Posted September 14, 2012 at 8:44 am | Permalink | Reply

      I think you must wait a couple or .. hours before the item is available XD

  33. Posted September 6, 2012 at 11:54 am | Permalink | Reply

    Nice sample, however ‘Null pointer exception’ on ‘handleMessage’ or the mTransactionHandler found in the main activity, both in your full sample application and my own test project, is the code no longer supported?

    • blundell
      Posted September 13, 2012 at 7:21 am | Permalink | Reply

      I believe it still is supported, I’ve seen some other people mention this on specific OS, have a quick scan through the comments.

  34. Kermagod
    Posted August 31, 2012 at 1:11 pm | Permalink | Reply

    Simple question hopefully.

    Using code – BillingHelper.requestPurchase(mContext, “android.test.purchased”); – The passport picture comes up perfectly fine, but with no purchase amount.

    What should the code be, so that I can see the list of items on my dev account. Or am I missing something.

    • Kermagod
      Posted August 31, 2012 at 4:33 pm | Permalink | Reply

      Found the answer…


      BillingHelper.requestPurchase(mContext, “android.test.purchased”);

      BillingHelper.requestPurchase(mContext, “Your In-app Product ID”); (including “”)

  35. Deer
    Posted August 31, 2012 at 6:51 am | Permalink | Reply

    Really Really Really (upto n times) thanks…superb post…!?!

  36. Jordi
    Posted August 30, 2012 at 2:13 pm | Permalink | Reply

    Thanks for all this work!!

    I’m also having a crash in ‘confirmTransaction’ and it’s caused because “latestPurchase.notificationId” is NULL.

    I do a ‘restoreTransactionInformation()’ to check if my item is already bought, and i got that error. Finally y remade a bit the code to make it more suitable for me..
    Now my ‘BillingHelper.verifyPurchase’ returns a List of {productId,purchaseState} and i check if my productId is there and what’s it’s purchase state :)

    No way to find about a notificationId!=null when i’ve already bought the product (real one, not the example one) :(

    • Jordi
      Posted August 30, 2012 at 3:15 pm | Permalink | Reply

      Got it. ‘latestPurchase.notificationId’ works only at buying time. If you check it later, it breaks so in BillingHelper() add the ‘if’ statement
      confirmTransaction(new String[]{latestPurchase.notificationId});

      in BillingSecutiry i’ve added a ‘private Static String productId=null’ and in ‘restoreTransactionInformation’ i’ve added a public Static setCheckId(String productId) that saves my productId. So i can check if the requested item is checked.
      Then in ‘verifyPurchase’ i only add the line if productId==null (default, when i’m not checking) or it matches.

  37. NiMS
    Posted August 30, 2012 at 1:28 pm | Permalink | Reply

    I’ve installed the app on Market and added published content for sale.
    I replace this line:
    BillingHelper.requestPurchase(mContext, “myitem_001″);
    If I hit “YES!!” I got “File not found” pop-up window.
    Maybe I have to wait after publishing the app?

  38. NiMS
    Posted August 30, 2012 at 12:14 pm | Permalink | Reply

    It seems I understand that “android.test.purchased” is item ID (fake in this case.
    The app crashes after a fake purchase. Logcat said that “FATAL… Unable to start receiver … ArrayList.java:257″

  39. NiMS
    Posted August 30, 2012 at 11:20 am | Permalink | Reply

    Looks much easier than Google example.
    We can save a lot of time using this.
    But I can’t understand where is the main line with itemID which user buys.

  40. Peter
    Posted August 29, 2012 at 8:00 am | Permalink | Reply

    Hi everybody!
    Now the billing works perfectly but the restoreTransactionInformation not. I received the signedData, signature, and after the verifications was good, the next method “confirmTransaction(new String[]{latestPurchase.notificationId});” crashes my app at this line: mService.sendBillingRequest(request);

    And it doesnt run into the catch branch. Can somebody help me to find the problem?
    Thank you!

  41. Ramesh M Nair
    Posted August 24, 2012 at 11:33 am | Permalink | Reply

    Can i puchase more tha one item by modifying this code.And also i have a doubt when i use android.test.purchase after purchase the app is get crash why?

  42. Ruzanna
    Posted August 24, 2012 at 9:43 am | Permalink | Reply

    Hi. thanks for the tutorial. however I’m getting an error saying “The item you requested is not available for purchase” while trying to buy a real item with a preserved id. I’ve signed the application and uploaded it as a draft version, added products and published them. I have the same version(signed) on my device as the uploaded one. any ideas ?
    THanks in advance.

    • blundell
      Posted August 24, 2012 at 1:30 pm | Permalink | Reply

      It may be because you can’t purchase items from the same developer account that you upload your apk with? Otherwise I’d wait about an hour for Google Play to catch up.

  43. Peter
    Posted August 22, 2012 at 8:46 am | Permalink | Reply

    Great and it is a good sample! But I got errors :(
    I signed the app with eclipse android tools then I uploaded it.
    I didn’t create in-app product item because in the main activity the item id is android.test.purchased so I would like to test something wrong.
    A could buy the item, and after this (“please wait until the item will be listed”… or something like that) the log says: Signature verification failed.

    Thanks for help!
    Sorry for english.

    • blundell
      Posted August 22, 2012 at 11:50 am | Permalink | Reply

      Signature verification failed, you’ll have to check you have set your public key correctly and debug through the Security class.

      • Peter
        Posted August 23, 2012 at 10:05 am | Permalink | Reply

        The key is correct. Another case: when I try to purchase with Id android.test.purchse then I can test buying, but after this event when I receive the sign data etc. then it says Signature failed.
        Maybe I have to create a new project.

        • Peter
          Posted August 29, 2012 at 7:33 am | Permalink | Reply

          Now already works!

          • George
            Posted August 29, 2012 at 4:25 pm | Permalink

            Hey Peter, I have the same issue. How did you fix it? Was in through new project?

          • Saumya
            Posted January 31, 2013 at 7:55 am | Permalink

            Hi peter, i am facing that same problem can u plz share how its resolved. If without any of your changes then after how many days?

            Any ideas r welcome.


  44. Posted August 20, 2012 at 7:57 pm | Permalink | Reply

    Fantastic tutorial. Thank you. It was taking my hours to sort through the google docs and your app really made things more clear. Is there a release date for a follow up that included directions on reflection? I see that is TODO. Thanks again.

    • blundell
      Posted August 20, 2012 at 9:10 pm | Permalink | Reply

      Glad you like, no sorry its just TODO :)

      • Posted August 21, 2012 at 2:25 am | Permalink | Reply

        Fair enough.. Tell you what, If I figure it out, I’ll send you a patch ;)

  45. sachin
    Posted August 15, 2012 at 12:10 pm | Permalink | Reply

    helllo ,i have a a/c on publisher site , in my code ,what it needed to add the a/c information in the code ?.

  46. sachin
    Posted August 13, 2012 at 10:26 am | Permalink | Reply

    sir , i download your project and when i am running this , it give force close when i click on (accep.t & buy) . plz help me regarding this issue.

    • blundell
      Posted August 13, 2012 at 4:56 pm | Permalink | Reply

      Do you have Google Play installed, have you added the permissions to your AndroidManifest? What does your LogCat say

      • sachin
        Posted August 14, 2012 at 5:47 am | Permalink | Reply

        i did not install google play because of this package does not shows in my Sdk manager (in eclipse) .

        • sachin
          Posted August 14, 2012 at 10:23 am | Permalink | Reply

          Hello , i have install google play and all the permission is added to menifest file …. after the it is not working and ……….

          • blundell
            Posted August 14, 2012 at 10:56 am | Permalink

            YOu have installed google play? Are you on an emulator? You really need to read the LogCat

  47. Ramesh M nair
    Posted August 12, 2012 at 6:38 pm | Permalink | Reply

    which function will execute after the in app billing in this code (is it getitem() function)and can i make multiple in app purchase by modifying this code please do replay sir and thank you for all previous replays :)

    • blundell
      Posted August 13, 2012 at 7:52 am | Permalink | Reply

      it is the showItem() function in the Activity if I understand you correctly.

  48. Ramesh M Nair
    Posted August 3, 2012 at 8:37 am | Permalink | Reply

    item is not shown in the android.test.purchase key but when i made it live after purchase the item has been show why is it not showing the pic during testing

    • blundell
      Posted August 6, 2012 at 12:35 pm | Permalink | Reply

      You can’t purcahase items from your own account, you need to be a test account user, you need to have the latest APK uploaded as a draft to Google Play.

  49. Ruchi
    Posted July 18, 2012 at 6:56 am | Permalink | Reply

    I am getting “unable to start receiver” exception. Please help.
    Thanks and regards

    • blundell
      Posted July 18, 2012 at 7:12 am | Permalink | Reply

      Hi please ensure you’ve added it correctly to your manifest

  50. bharadwaj.c
    Posted July 16, 2012 at 10:54 am | Permalink | Reply

    hi blundell,

    my application force closes if i click back button or login button in Appmaintest activity.where it calls Ondestroy() method.please give me the solution.

    • blundell
      Posted July 16, 2012 at 11:04 am | Permalink | Reply

      did you call instantiateHelper() on the BillingHelper with a valid context?

  51. hello
    Posted July 6, 2012 at 8:17 am | Permalink | Reply

    Another error:

    java.lang.RuntimeException: Unable to destroy activity {com.blundell.test.AppMainTest}: java.lang.NullPointerException

    plz help me to sort these errors out….
    thnx in advance

  52. hello
    Posted July 6, 2012 at 7:31 am | Permalink | Reply

    how to resolve this error:
    java.lang.RuntimeException: Unable to start receiver com.blundell.test.BillingReceiver: java.lang.NullPointerException

    plz help….

  53. RAMESH
    Posted July 5, 2012 at 10:09 pm | Permalink | Reply

    hi sir am using your test app it works fine but when i use my in app id .which i have published(not the app) it show item not found i have also set my public key sir please replay what wrong to my email address i need your help my email address rameshmnairmlm@gmail.com

    • blundell
      Posted July 6, 2012 at 7:02 am | Permalink | Reply

      Have you published the InApp product on the market

  54. Antonov
    Posted July 5, 2012 at 3:52 am | Permalink | Reply

    im still working on an app that has an in-app purchase. i used your sample app and it’s working fine. i added some codes to work with restorePurchase… it’s working well but the problem is when restorePurchase is called this time, i havent made a purchase yet. the app crashes. can you help me on this?

    my code:
    public void restorePurchases(){
    BillingHelper.isRestore = true;

    } else {
    Log.i(TAG,”Can’t purchase on this device”);

    public Handler mTransactionHandler = new Handler(){
    public void handleMessage(android.os.Message msg) {
    Log.i(TAG, “Transaction complete”);


  55. Antonov
    Posted June 27, 2012 at 6:39 am | Permalink | Reply

    Great tutorial…

    Just wondering how to make restoreTransactionInformation work if app is uninstalled/reinstall?

    • blundell
      Posted June 27, 2012 at 7:03 am | Permalink | Reply

      You’ll have to have a look at the IN_APP_NOTIFY receiver, then the GET_TRANSACTION_INFO (i think) then in the Json you parse it tells you if this transaction was cancelled/refunded

      • Antonov
        Posted June 27, 2012 at 8:43 am | Permalink | Reply

        Im still confused. Can you send me an email step by step? please… thanks

  56. Marcel
    Posted June 14, 2012 at 5:06 pm | Permalink | Reply

    Hi, great tutorial!
    It works great for testing.

    But where can I switch from testing to real purchase?
    I configured three items in Google Play. How do I tell Google which item I want to buy?

    • blundell
      Posted June 14, 2012 at 7:43 pm | Permalink | Reply

      It’s where you call this method. ( BillingHelper.requestPurchase ) i.e. the onClick in the Activity

  57. Pero Ericsson
    Posted June 7, 2012 at 10:14 pm | Permalink | Reply

    Thanks a lot! I was So lost after reading Android developers tutorial. Thank you!

  58. Taha
    Posted June 7, 2012 at 6:45 am | Permalink | Reply

    This is a great tutorial, but i am really confused while implementing in app purchase. Can u plz clear those?
    1st: I copied the public key from publisher account, but when i am running app, it is giving null pointer exception.
    so should i upload this app in my publisher account?
    I am confused with the sequence of steps… please help

    • blundell
      Posted June 7, 2012 at 7:11 am | Permalink | Reply

      What does the LogCat say about the null pointer? Yes you need to upload the application and save it as a ‘draft’ apk. This will allow you to test purchases using ‘android.test.purchased’ if you want to test your own in app products (that you create on Google Play) you will need to create a signed APK and push it onto your device using ADB.

      • Taha
        Posted June 7, 2012 at 12:14 pm | Permalink | Reply

        Now i have uploaded ur app in my publisher account, but still there is an exception of ArrayIndexOutOfBound.

        at java.util.ArrayList.throwIndexOutOfBoundsException

        at com.blundell.abc.BillingHelper.verifyPurchase
        at com.blundell.abc.BillingReceiver.purchaseStateChanged(BillingReceiver.java:44)

        at com.blundell.abc.BillingReceiver.onReceive(BillingReceiver.java:27)

        This is what logcat is saying, at this line:
        latestPurchase = purchases.get(0); in verifyPurchase method of BillingHelper class.
        Actually the ArrayList of VerifiedPurchase does have anything after purchasing from google play site, which causes this exception.

        I just uploaded ur application (didnt publish it) and copied public key from account and run the app. M i missing something or doing something wrong uptil now?

  59. tamasu
    Posted June 3, 2012 at 11:33 am | Permalink | Reply


    works great. However I had to spend some time researching how I can make restoretransaction part work.

    I added:

    if (!isRestore) { confirmTransaction(new String[]{latestPurchase.notificationId});

    to BillingHelper’s verifyPurchase function to prevent nullpointer.

    And the restoretransaction must be called like:


    Could be useful to someone. Cheers. :)

    • Taha
      Posted June 13, 2012 at 9:50 am | Permalink | Reply

      i am also getting null pointer exception, so can u please elaborate ur point?


      where should it be called?
      if (!isRestore) { confirmTransaction(new String[]{latestPurchase.notificationId});
      what is isRestore variable here? i mean where has this boolean used?

    • Anthony
      Posted June 27, 2012 at 6:36 am | Permalink | Reply

      Can you make a step by step to solve the restoretransaction error?

    • Anshuman
      Posted December 3, 2012 at 9:53 am | Permalink | Reply

      Can u please show me the code for restore Transaction whenever user clear data, I tried calling BillingService.restoreTransactionInformation(BillingSecurity.generateNounce); but no action occur.

      • blundell
        Posted December 4, 2012 at 10:23 am | Permalink | Reply

        Hi sorry this function isn’t implemented, left as a task for you.

  60. hello
    Posted June 2, 2012 at 5:46 am | Permalink | Reply

    thnx i sorted that thing out….
    n this post helped me alot please continue this work…..

  61. hello
    Posted May 31, 2012 at 7:33 am | Permalink | Reply

    hello sir,
    I used your code in bt in the logcta it is showing error
    “BillingHelper not fully Instantiated”
    please tell me how to resolve it plz….
    thnx in advance…

    • blundell
      Posted June 1, 2012 at 11:48 am | Permalink | Reply

      You need to set all the fields on the BillingHelper. Take a look at the log line and see what variables it is checking that need to be non-null (instantiated)

  62. digiant
    Posted May 30, 2012 at 2:11 am | Permalink | Reply

    I found a solulation of nullpointer exception after successfully purchased “android.test.purchased” item, just rename the app package from “com.blundell.test” to an unique package, something like “com.blundell.test3242342″, and everything will work fine.

  63. RawCoder
    Posted May 21, 2012 at 10:46 am | Permalink | Reply


    I want to unlock some feature if the payment is successful but my problem is hoew do i know(depending on which parameter in code) that the payment is successful and i unlock the data. Please help.


    • blundell
      Posted May 22, 2012 at 6:57 am | Permalink | Reply

      Hi, You would use the productId like in the example:


  64. SIlentDoc
    Posted May 15, 2012 at 11:21 pm | Permalink | Reply

    Hi blundell,

    First of all, thanks a million for this post, it helped me a lot understanding google’s inapp billing with your simple, straight to the point explanation.

    However, I came across a problem that may be looked up in your sample and may get people into problem. The billing service is started and stopped, but it is never unbound. Since the StartService call also binds it to the ServiceConnection, the binding is already done, but the service should be unbound after stopping it.

    • Sam
      Posted October 21, 2012 at 5:20 pm | Permalink | Reply

      I have the same issue – did you ever work this out or just accept that the service connection is leaked on stopping the service as it is not unbound.

      • blundell
        Posted December 31, 2012 at 11:54 am | Permalink | Reply

        It should be unbound in the onPause, this is done in the source code.

  65. Nemo
    Posted May 2, 2012 at 11:21 am | Permalink | Reply

    We have a problem. If user uninstall app, then install again. The database is lost, and user must buy in app again. It is too bad. Your code does not contain this feature. Could you explain this for me?

  66. Nemo
    Posted May 2, 2012 at 1:45 am | Permalink | Reply

    Thank you in advance . I have done my app with “buy in app” feature.

  67. Siddharth Iyer
    Posted April 3, 2012 at 6:22 pm | Permalink | Reply


    First of all, thank you for this tutorial and the sample project; Very useful!

    I have a question. I’m on the last leg of the In App Billing implementation. I am able to bind successfully, and initiate the purchase for the reserved id “android.test.purchased” using my developer email address as the purchasing account.

    However, when the code begins verification, the signature coming back is blank and hence nothing gets added to the purchase array. This leads to a nullpointer exception on “latestPurchase = purchases.get(0);” in BillingHelper.java.

    I have already uploaded a draft (unpublished) app with the same version number to Google Play. I’m limited in my debugging since I’m testing on a signed release version. Any idea what could be happening here?


    • blundell
      Posted April 3, 2012 at 9:08 pm | Permalink | Reply

      Hmm I haven’t seen that yet, have you got your developer key correct? Stepping through is all that I can recommend atm.

      • Siddharth Iyer
        Posted April 12, 2012 at 12:32 am | Permalink | Reply

        Totally my bad on this one. My key was skewed just barely enough for me to not notice. Thanks for your response!

      • Siddharth Iyer
        Posted April 12, 2012 at 3:58 am | Permalink | Reply

        Hey blundell,

        Just wanted to let you know something I just found out. You may have received messages from loads of users saying they get a NullPointerException when using restoreTransaction. In case you haven’t noticed this, you don’t need to send a CONFIRM_NOTIFICATIONS for the PURCHASE_STATE_CHANGED that you get back when you send a RESTORE_TRANSACTIONS. The reason is that when you send a RESTORE_TRANSACTIONS request, the PURCHASE_STATE_CHANGED that comes back does not need a CONFIRM_NOTIFICATIONS, like the rest of the requests. In the signed data that you get back on calling RESTORE_NOTIFICATIONS, there are no notification_id’s that come back; In your code, you go ahead and send the CONFIRM_NOTIFICATIONS for this situation also, which you don’t need to (Google Docs confirm this). The NullPointerException takes place because of the null notification_id’s that you add to the confirmation bundle for a RESTORE_TRANSACTION.

        Sorry if the above paragraph seems long winded haha. I’m very hopped up on coffee right now.

  68. billybill
    Posted March 30, 2012 at 11:58 am | Permalink | Reply

    In the attached source code your main activity uses an object mHandler, this is not mentioned in the blog post itself. What is this good for, can you spend a few words on it? Thanks, b.

    • blundell
      Posted April 1, 2012 at 3:06 pm | Permalink | Reply

      Hi, Are you talking about the mCompletedHandler? Handlers are used a callback mechanism, allowing you to notify your activity after a certain event has happened on another thread ( or in a service ). This is what the completed handler does, when you create your BillingHelper you inform it that the handler is the activity. Therefore when the helper completes a purchase it tells the Activity ‘transaction complete’.

      • billybill
        Posted April 2, 2012 at 7:14 pm | Permalink | Reply

        yes and no. You create mTransactionHandler in your AppMainTest.java. If the handler is used for callback to inform an activity it must have some kind of callback method which someone else, e.g. a service, calls. Where is that callback mehtod defined and where can I see in the server that it is called? Thanks.

  69. billybill
    Posted March 30, 2012 at 11:33 am | Permalink | Reply


    thanks for the blog. I have a question about

    boolean bindResult = bindService(new Intent(“com.android.vending.billing…

    What shall I tell the user to do if it returns false? Is there anything I can check why it returned false? Just telling the user “sorry cannot bind, no purchase possible” is probably not what you want to show a user. What are examples when this can fail?


    • blundell
      Posted April 1, 2012 at 2:51 pm | Permalink | Reply

      Binding to the Android marketing service can fail when the Android Market (Google Play) application is out of date and needs updating. i.e. You are attempting you bind communication between your app and Google Play, so it’s upto Google Play to ensure this works. If you cannot bind to the service i.e. ‘bindResult’ == false, you could write a message something along the lines of “Sorry can’t make purchases until you update to the latest Android Market / Google Play. You could also show them how to do it or link to this FAQ here: https://support.google.com/googleplay/bin/answer.py?hl=en&answer=190860 . Hope that helps.

  70. Posted March 29, 2012 at 2:57 pm | Permalink | Reply

    Greate , thank you very much this is the best tutorial I’ve seen sice I started research In-app billing , thank you very much

  71. Steve A
    Posted March 28, 2012 at 6:19 pm | Permalink | Reply

    Where do I specify which elements are purchased?

    Just like “Purchase the `monster sword`” or “purchase level unlock 5″.

    • blundell
      Posted March 29, 2012 at 7:04 am | Permalink | Reply

      Hi Steve, not quite sure what you mean. You can put this wherever you want within your app, in a list, on a dialog?

  72. Hippy
    Posted March 12, 2012 at 12:30 am | Permalink | Reply

    OK, I’ve narrowed my problem down a little – I need to check for purchases before the app starts.

    When I try to do so in onCreate, BillingHelper.isBillingSupported() always returns false. How do I get it to start?

    • blundell
      Posted March 12, 2012 at 10:02 am | Permalink | Reply

      If your doing this in onCreate the Activity may not have been bound to the service yet. you could create a callback for when the service is bound, or add it to a queue and do it when the service is bound. Add some Log into ‘onServiceConnected()’ to help you understand the timings.

      • Hippy
        Posted March 19, 2012 at 12:53 am | Permalink | Reply

        Great tips, thanks. To avoid any problems I might have with making a callback in the wrong place, I simply threw in a dialog with a button I can hit to trigger the check. I added a Toast to show when billing is connected, so I know that I’m hitting the button after it’s connected ok. Of course, when I try to run this on USB debug, all I get is a “RESULT_DEVELOPER_ERROR” response from the market, which is because I’m using the debugger.

        When I sign the package and install & run on a real device, I see the message telling me billing has connected, but then when I trigger the check it just errors and tells me to force close it.

        Any ideas what else could be wrong? Any suggestions how to even diagnose it without a debugger?

        • Hippy
          Posted March 23, 2012 at 12:47 am | Permalink | Reply

          I’ve worked out (or rather been told) how to use adb to view logs on my real device. I’ve posted the logcat at this StackOverflow question. It looks like the response is received fine, but then it attempts to call the rever again, and fails to do so – after the request is received, shouldn’t that simply trigger the payment handler? Do you have any other suggestions?


          • blundell
            Posted March 29, 2012 at 7:10 am | Permalink

            I’ve looked at that SO question. Is the item you have purchased a managed item? You know you can build your app live but not obfuscated to rule that out. You know you can just plug your ‘real device’ into eclipse and it’ll bring up your LogCat? Your getting a NPE so I’d Log out all the variables you send to Market to make sure you’ve got everything.

          • Hippyjim
            Posted April 20, 2012 at 11:51 pm | Permalink

            Just to confirm, Siddharth Iyer above has it exactly right. I simply wrapped the confirmTransaction cal in a check to make sure latestPurchases.notificationId isn’t null, and no more errors. Perhaps you could update the tutorial to include those changes?

  73. Hippy
    Posted March 11, 2012 at 9:52 pm | Permalink | Reply

    Thanks for posting this. The code is much easier to follow than the spaghetti that is the Google sample app.

    I’m also looking to restore previous purchases after a reinstall – calling restoreTransactionInformation doesn’t seem to do anything though. I mean literally no responses – do you have any tips on how to use it?

  74. Rawcoder
    Posted March 8, 2012 at 8:31 pm | Permalink | Reply

    Hi I have downloaded the project and trying to run in the emulator. Billing service can not bind with the android market service.

    Following code returns false….

    bindService(new Intent(“com.android.vending.billing.MarketBillingService.BIND”), this, Context.BIND_AUTO_CREATE);

    What should be done?

    • blundell
      Posted March 8, 2012 at 10:07 pm | Permalink | Reply

      The Android Market may not be installed on your emulator?

  75. getLaid
    Posted March 4, 2012 at 4:12 am | Permalink | Reply

    Hi there! Do you use Twitter? I’d like to follow you if that would be ok. I’m absolutely enjoying your blog and look forward to new updates.

  76. Kopeck
    Posted March 4, 2012 at 3:48 am | Permalink | Reply

    I found you’re blog via Yahoo and I have to say. An enormous Thank you, I thought your post was extremely enlightening I will revisit to see what extra great information I can recieve here.

  77. vivi
    Posted February 17, 2012 at 5:58 pm | Permalink | Reply

    Thanks for this awesome tut :)

    I used these classes in my project, in-app billing is working great!

    Simple question though : how do you restore transactions? If I buy my product, then uninstall and install the app back again, I’m not able to purchase the product anymore (error text is something like “transaction in progress). Noticed about the restoreTransactionInformation() method but when I launch it when installing the app, the handler is not triggered at all and my logs show a RESULT_DEVELOPER_ERROR.

    • blundell
      Posted February 18, 2012 at 12:41 pm | Permalink | Reply

      Hmm I haven’t covered restore in this tutorial, make sure your product is set as a ‘managed’ product on the android market.

      • vivi
        Posted February 19, 2012 at 11:48 am | Permalink | Reply

        My product is set as a “managed product” but I first though that the buying process would automatically check and restore a transaction already processed for this product! But no :/

        I guess I need to investigate about this restoreTransactionInformation() method then! If you have any hint, let me know ;)


  78. chia
    Posted February 17, 2012 at 9:50 am | Permalink | Reply

    Hi, i m new to android, i have download ur source code n run it, but after i press ‘yes’ button, it did not continue to other page, anythings i have to do or miss?

    • blundell
      Posted February 17, 2012 at 3:41 pm | Permalink | Reply

      Yes, please read over the blog post there are a few things you have to change :-)

  79. Posted February 16, 2012 at 3:18 am | Permalink | Reply

    I downloaded your sample code but the following code does not run:

    public Handler mTransactionHandler = new Handler(){
    public void handleMessage(android.os.Message msg) {
    Log.i(TAG, “Transaction complete”);
    Log.i(TAG, “Transaction status: “+BillingHelper.latestPurchase.purchaseState);
    Log.i(TAG, “Item purchased is: “+BillingHelper.latestPurchase.productId);


    showItem() does not run after send requestPurchase. Purchase fail?? Please check it and help me

    • blundell
      Posted February 16, 2012 at 8:21 am | Permalink | Reply

      If your purchase failed, then it won’t show the item.

    • Posted February 17, 2012 at 3:54 am | Permalink | Reply

      I have read your topic on anddev.org. I added PUBLIC KEY, using device 1.6,update Manifest to run the app. But it purchase failed? Nothing is display as i said. Logcat:


      Purchase fail? Code or Market?
      Please check it help me.

      • blundell
        Posted February 18, 2012 at 12:38 pm | Permalink | Reply

        I’d have to see more code, make a question on http://stackoverflow.com , all I can say make sure your passing in the transaction handler correctly and that it you stepp through it with breakpoints. Adding extra log output is always good as it shows you what is exactly going on

        • Nemo
          Posted April 26, 2012 at 9:17 am | Permalink | Reply

          Come back with my problem. The BillingReceiver does not run. I do not why.

          • blundell
            Posted April 26, 2012 at 7:55 pm | Permalink

            Have you got it declared in your AndroidManifest correctly? Try the example project I’ve attached

  80. Steve C
    Posted February 16, 2012 at 12:39 am | Permalink | Reply

    Very nice, but how does it store on the phone that the purchase has been made?

    • blundell
      Posted February 16, 2012 at 8:20 am | Permalink | Reply

      It doesn’t, this depends on what type of product your selling, you may not want to save it. If you did you would save it when you get the response saying bought you. It’s up to you to use SharedPreferences or a database or whatever.

      • Subs
        Posted September 1, 2012 at 4:43 am | Permalink | Reply

        blundell thanks for u :)

        Where we can catch the response to store it in db or SharedPreferences such that purchase has been made or not??

        • blundell
          Posted September 13, 2012 at 7:25 am | Permalink | Reply

          Hi Subs, there is a callback in the original Activity

7 Trackbacks

  1. [...] is also this site that has an actual example of simple in app billing which would give you a great starting [...]

  2. By InAppBilling - Android-Hilfe.de on December 10, 2012 at 11:46 am

    [...] die GoogleDoku gelesen, das Dungeons-Beispiel umgesetzt und mir folgendes Example angeschaut: Android Simple InApp Billing / Payment | Blundell Die Umsetzung mit der Helper-Klasse fand ich im ersten Schritt ganz gut. Was an diesem Beispiel [...]

  3. [...] did coding from this tutorial Simple InApp Billing / Payment, updated my apk file in google play but not published, is it compulsory to publish app in google [...]

  4. By in-app billing on android « w writes her story on November 1, 2012 at 8:05 am

    [...] you are having the same problem as me, I highly recommend you to take a look at this site. Simply download the source, make a copy to your project folder and do some modifications. Trust [...]

  5. By Android: in-app: check if an item has been purchased on September 3, 2012 at 3:50 am

    [...] I folloed this tutorial to implement in-app in my application: http://blog.blundell-apps.com/simple-inapp-billing-payment/ [...]

Post a Comment

Your email is never shared. Required fields are marked *