1. Introduction
This article and the associated source code is an example of Google Play In-App Billing Version 3. The attached TestInAppBilling
source code is a complete demo application. In the case of In-App Billing it is not possible to provide ready to run application (APK file). Each In-App Billing must have a unique package code and unique Google Play provided key. To obtain a license key you must have access to Google Play Developer Console. If you do not have access to the developer console you must register as a developer. Section 2. Installation goes through all the required steps to get the demo application up and running.
The TestInAppBilling
source code is part of Android Color Selector for Programmers application available on Google Play. The application is free. However, the user can purchase the source code of the application by using In-App Billing. If you download Android Color Selector go to the About screen to see the In-App buttons. A brief description of the Android Color Selector is given below.
The Android Color Selector for Programmers application allows you to select a color from a color chart. The result of the selection is a 24 bit RGB color value. The application was designed to be called from another application and return the result to the calling application. If you develop an application that requires a user color selection, Android Color Selector is for you. You can also start the Android Color Selector manually by clicking the icon on your tablet. The result will be displayed on the screen.
2. TestInAppBilling Source Code Installation
If you are an experience Android programmer you can skip this section. The only things you must do is change the package name from "com.granotech.testinginappbilling" to your own name, define on Google Play one in-app product with product0001 id, and copy the application key from Google Play to TestInAppBilling.java.
2.1. Prerequisites
Testing the TestInAppBilling application requires the following prerequisites:
- You are registered as a developer and have access to Google Play Developer Console.
- You have installed Android Developer Tools (ADT) on your computer.
- You have an Android tablet with Google Play Store software installed.
- You have enabled developer mode and USB access on your tablet.
- You have enabled installation from unknown sources on your tablet.
- Your tablet has file explorer type software. If not, get one.
2.2. Importing the source code to Android Developer Tools (ADT)
If you know how to import existing Android code you can skip this section except for the package rename step. After the import you must change the package name from "com.granotech.testinappbilling" to a globally unique name such as "com.yourcompany.yourappname".
- Download the source code.
- Extract the files from the Zip file to a temporary folder. Please note: the ADT import function will not work directly with the downloaded Zip file.
- Start ADT program.
- If you want to load the project into empty workspace, you must add a new empty project to it. You cannot import a project to an empty workspace. This empty project can be deleted after you import the source code.
- Select Import from the File menu.
- Select Android --> Existing Android Code into Workspace. Click Next.
- Click Browse Root Directory button. Navigate to the location you extracted the project files. Click OK.
- You will see TestInAppBilling project to import.
- Make sure to CHECK the "Copy projects into workspace" checkbox.
- Click Finish.
- TestInAppBillingActivity package was added to your Package Explorer.
- Rename (Alt-Shift-R) this name to a name of your choice.
- In the Package Explorer pane under src rename the com.granotech.testinappbilling to "com.yourcompany.yourappname". You must change the package name to a globally unique name in order for Google Play to accept it.
- If Empty project was created, it can be deleted now.
- You can perform very limited testing in debug mode. You must go through the next two steps before being able to test the in-app billing. When you run it the first time make sure it is set as Android Application.
2.3. Adding TestInAppBilling application to Google Play Developer Console
- Log into your Google Play Developer Console Account.
- Add a new Application.
- Give it a title of your choice. For the purpose of this article we will use Test In-App Billing.
- Define one in-app product. For the purpose of this test, it must be a Managed Product with "product0001" product ID. All other information is not relevant to the test.
2.4. Uploading TestInAppBilling APK to Alpha
- Log into your Google Play Developer Console Account.
- Select Test In-App Billing application.
- Select Services & APIs.
- Go to "Your license key for this application" and copy the base64 encoded RSA public key to the clipboard.
- Go to ADT and Edit TestInAppBillingActivity.java source module.
- Paste the base64 encoded RSA public key from the clipboard into applicationPublicKey string replacing the existing dummy key.
- Click File-->Export
- Select Android-->Export Android Application. Click Next.
- Select TestInAppBilling project. Click next.
- If this is the first time, create keystore, otherwise enter the password.
- Create a new key or enter the password for existing key.
- Save your APK file in a production directory on your development computer.
- Go back to the Developer Console and select TestInAppBilling application.
- Select APK Alpha Testing.
- Click Upload new APK to Alpha.
- Drop your production APK file for upload to Google Play. When upload is done click save.
2.5. Testing TestInAppBilling on your tablet
- Connect your tablet to your development computer via USB cable.
- Copy the production APK file that was created in step 2.4 above from your production directory on your development computer to a download directory of the tablet.
- Go to your tablet and look for the APK file. Click on it and install it on your tablet.
- Open the application.
- You have two choices: buy a product or consume a product. First press the Buy button. The program will initiate buying process for the product that was defined in the Developer Console. Once you buy it you cannot buy it again unless it is consumed. To consume the product press the second button.
- If there is an error, you will get a message and error location. Error location is source module name, line number and method.
3. Application Source Code Overview
The attached source code has three java modules:
InAppBilling.java
The InAppBilling class does all the communication work with Google Play. This is the class that you will incorporate into your application to perform In-App Billing.
TestInAppBillingActivity.java
The TestInAppBillingActivity is simulation of your own activity class for In-App Billing.
IInAppBillingService.aidl
The IInAppBillingService.aidl is Android Interface Definition Language (AIDL) file defines the interface to the Google Play service. The source code is provided by Google. Please note: you cannot change the name or package name assoicaited with this module. The package name must be "com.android.vending.billing". Go to the following link to get all the information about it. Preparing Your In-app Billing Application.
4. InAppBilling Class Code Overview
The InAppBilling class handles the in-app billing flow for purchasing, or consuming one item. To purchase an item you instantiate the class and call the startServiceConnection
method. The purchase process is asynchronous. The startServiceConnection
method initiates the process and returns immediately. When the process terminates, sometime later, one of the callback methods of the InAppBillingListener
class will be called. The InAppBilling
class is active from the moment startServiceConnection
is called until any of the callback methods of InAppBillingListener
is called. You can call startServiceConnection
multiple times except when the class is busy. A call to startServiceConnection
while busy will be ignored.
4.1. InAppBilling Constructor
public InAppBilling
(
Activity parentActivity,
final InAppBillingListener inAppBillingListener,
String appPublicKeyStr,
int purchaseRequestCode
)
Calling arguments are:
parentActivity
: the parent activity context for InAppBilling. In this demo project it isTestInAppBillingActivity
.inAppBillingListener
: a class implementing the callback methods to deliver the final result. The InAppBillingListener
interface
is described in the next section. In this demo project it is TestInAppBillingActivity
.appPublicKeyStr
: the public key assigned by Google Play Store tothis application in base64 format.purchaseRequestCode
: request code for onActivityResult
.
4.2. InAppBillingListener Interface
public interface InAppBillingListener
{
public void inAppBillingBuySuccsess();
public void inAppBillingItemAlreadyOwned();
public void inAppBillingCanceled();
public void inAppBillingConsumeSuccsess();
public void inAppBillingItemNotOwned();
public void inAppBillingFailure(String errorMessage);
}
The listener class must implement 6 methods.
inAppBillingBuySuccsess
(): The purchase process was successful. Add code to provide the customer with the product purchased.inAppBillingItemAlreadyOwned
(): The customer already owns this product. No purchase request was made to Google Play. Do whatever is appropriate in this case.inAppBillingCanceled
(): The purchase process was canceled. You can leave this method empty or provide some feedback to the user.inAppBillingConsumeSuccess
(): The product was consumed successfully. In other words, the user can purchase it one more time.inAppBillingItemNotOwned
(): The item is not owned. Therefore it cannot be consumed.inAppBillingFailure(String errorMessage)
: Unexpected error occurred. The error message has an appropriate text message plus the source code file name and line number of the error. In addition the method name is given.
Please note: when any of the listening methods is called the InAppBilling class is no longer active. In other words, you can initiate another purchase or consume using the same object.
4.3. Calling startServiceConnection method
public void startServiceConnection
(
String itemType,
String itemSku,
boolean consumeItem
)
itemType
: The item can be ITEM_TYPE_ONE_TIME_PURCHASE
= "inapp" for one-time purchases or ITEM_TYPE_SUBSCRIPTION
= "subs" for subscription.itemSku
: Item product ID exactly as defined in Google Play Developer Console. In this example it is product0001.consumeItem
: For purchase it should be ACTIVITY_TYPE_PURCHASE
= false. For consume it should be ACTIVITY_TYPE_CONSUME
= true.
4.4. InAppBilling Logic Flow
- Instantiate InAppBilling object by calling the constructor. You can reuse the object as long as it is not active.
- Call
startServiceConnection
method. This method will create a serviceConnection
and bind it to Google Play Store service on the device. The binding process is asynchronous. The startServiceConnection
will return immediately before the connection is activated. - When the binding process is done the system will call the
serviceConnected
method. - The
serviceConnected
method will test if in-app billing service is available on this device. Note: IInAppBillingService.isBillingSupported
. - If in-app billing is supported, the method will check if the item is available for sale. Note:
IInAppBillingService.getSkuDetails
. - If the item is available for sale, the method will check if it is already owned by the customer. Note:
IInAppBillingService.getPurchases
. - If the
startServiceConnection
was called for consume and the item is owned, the item will be consumed. Note: inAppBillingService.consumePurchase
. If the item is not owned, the InAppBilling will terminate with a call to inAppBillingItemNotOwned
(). - If the
startServiceConnection
was called for purchase and the item is owned by the customer, the InAppBilling will terminate with a call to inAppBillingItemAlreadyOwned
(). - If the
startServiceConnection
was called for purchase and the item is not owned by the customer the program will send an asynchronous request to purchase the item. Note: inAppBillingService.getBuyIntent
and startIntentSenderForResult
. - Please note: this is the time the customer will see the Google dialog asking the customer to approve the purchase.
- When the request was processed by Google Play Store The system calls
onActivityResult
method in that parent Activity
class This call is transferred to onActivityResult
method in this class. - The
onActivityResult
method checks the result. There are four possible outcomes: (1) User canceled (2) Error (3) Result is ok but returned data is invalid (4) Purchase is successful. The appropriate callback method will be called. - Whenever a callback methods is called, the
InAppBilling
class un-binds itself from the service, resets the active flag and then calls the callback method.
4.5. InAppBilling Programming Notes
The calls to IInAppBillingService.getSkuDetails()
, IInAppBillingService.getBuyIntent()
and IINAppBillingService.getPurchases()
return more information than used in this demo. The information is in JSON key-value pairs. The InAppBilling.java source code identifies the extra information available to you if you need it. For more information got to In-app Billing Reference (IAB Version 3).
The InAppBilling class contains two methods related to signature verification: verifySignature
and decodeBase64
. These methods are based on the sample project given by Google. However, both are much simpler.
At the end of InAppBilling source there are a number of methods related to creation of error message. The getErrorLocation
is of particular interest to C and c++ programmers because the method shows how to get the source module name __FILE__ and the line number __LINE__ of the error.
5. TestInAppBillingActivity Programming Notes
The TestInAppBillingActivity
class is a simulation of your own activity class. This is the activity that In-App Billing will be called from. Below you will find all methods defined in this class. For other members of the class please look at the source module.
5.1. onCreate
This activity creates the layout programmatically. We use RelativeLayout
. Four views are added to the layout: title, buy button, consume button, and text message area.
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
mmToPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1.0F, displayMetrics);
float textSize = 7.0F;
float msgTextSize = 5.0F;
int margin = (int) (4.0F * mmToPixels);
layout = new RelativeLayout(this);
layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
layout.setBackgroundColor(Color.rgb(163, 176, 255));
layout.setGravity(Gravity.CENTER);
createTextView(TITLE1_ID, "Google Play", textSize, 0);
createTextView(TITLE2_ID, "In-App Billing Demo", textSize, 0);
createTextView(TITLE3_ID, "Granotech Limited", msgTextSize, margin);
createButton(BUY_BUTTON_ID, "Buy Product", textSize, margin).setOnClickListener(new OnClickListener()
{
public void onClick(View view)
{
buyProduct();
return;
}});
createButton(CONSUME_BUTTON_ID, "Consume Product", textSize, margin).setOnClickListener(new OnClickListener()
{
public void onClick(View view)
{
consumeProduct();
return;
}});
msgBox = createTextView(MESSAGE_BOX_ID, "Click either\nBuy Product button or\nConsume Product button", msgTextSize, margin);
setContentView(layout);
return;
}
5.2. createTextView
Create TextView
during onCreate
.
private TextView createTextView(int id, String text, float textSize, int margin)
{
TextView textView = new TextView(this);
textView.setId(id);
textView.setText(text);
textView.setTextSize(TypedValue.COMPLEX_UNIT_MM, textSize);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
if(id > 0) lp.addRule(RelativeLayout.BELOW, id - 1);
lp.setMargins(0, 0, 0, margin);
layout.addView(textView, lp);
return(textView);
}
5.3. createButton
Create Button
during onCreate
.
private Button createButton(int id, String text, float textSize, int margin)
{
Button button = new Button(this);
button.setId(id);
button.setText(text);
button.setTextSize(TypedValue.COMPLEX_UNIT_MM, textSize);
int padding = (int) (4.0F * mmToPixels);
button.setPadding(padding, 0, padding, 0);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
if(id > 0) lp.addRule(RelativeLayout.BELOW, id - 1);
lp.setMargins(0, 0, 0, margin);
layout.addView(button, lp);
return(button);
}
5.4. buyProduct
It is the Buy Product button on-click listener. It is the method that will initiate
the purchase process.
private void buyProduct()
{
if(inAppBilling == null)
{
inAppBilling = new InAppBilling(this, this, applicationPublicKey, PURCHASE_REQUEST_CODE);
}
inAppBilling.startServiceConnection(InAppBilling.ITEM_TYPE_ONE_TIME_PURCHASE,
PRODUCT_SKU, InAppBilling.ACTIVITY_TYPE_PURCHASE);
return;
}
5.5. consumeProduct
It is the Consume Product button on-click listener. It is the method that will initiate the consume product process.
private void consumeProduct()
{
if(inAppBilling == null)
{
inAppBilling = new InAppBilling(this, this, applicationPublicKey, PURCHASE_REQUEST_CODE);
}
inAppBilling.startServiceConnection(InAppBilling.ITEM_TYPE_ONE_TIME_PURCHASE,
PRODUCT_SKU, InAppBilling.ACTIVITY_TYPE_CONSUME);
return;
}
5.6. inAppBillingBuySuccsess
Listener callback method. Buy process was successful.
@Override
public void inAppBillingBuySuccsess()
{
msgBox.setText("In App purchase successful");
return;
}
5.7. inAppBillingItemAlreadyOwned
Listener callback method. Buy process did not go through. The customer
already owns this product.
@Override
public void inAppBillingItemAlreadyOwned()
{
msgBox.setText("Product is already owned.\nPurchase was not initiated.");
return;
}
5.8. inAppBillingCanceled
Listener callback method. Buy process was canceled.
@Override
public void inAppBillingCanceled()
{
msgBox.setText("Purchase was canceled by user");
return;
}
5.9. inAppBillingConsumeSuccess
Listener callback method. Customer owned this product and now it was
consumed. He/she can buy it again.
@Override
public void inAppBillingConsumeSuccsess()
{
msgBox.setText("In App consume product successful");
return;
}
5.10. inAppBillingItemNotOwned
Listener callback method. Consume process did not go through. The item was
not owned.
@Override
public void inAppBillingItemNotOwned()
{
msgBox.setText("Product is not owned.\nConsume failed.");
return;
}
5.11. inAppBillingBillingFailure
Listener callback method. Unexpected error occurred during the purchase or
consume process.
@Override
public void inAppBillingFailure(String errorMessage)
{
msgBox.setText("Purchase or consume process failed.\n" + errorMessage);
return;
}
5.12. onActivityResult
This callback will be activated when Google Play received customer authorization to go ahead with the purchase.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(inAppBilling != null && requestCode == PURCHASE_REQUEST_CODE)
{
inAppBilling.onActivityResult(resultCode, data);
}
else
{
super.onActivityResult(requestCode, resultCode, data);
}
return;
}
5.13. onBackPressed
User pressed on back button. Terminate the application
@Override
public void onBackPressed()
{
if(inAppBilling != null) inAppBilling.dispose();
finish();
return;
}
5.14. onDestroy
Application is about to be destroyed. Make sure InAppBilling is disposed of.
@Override
public void onDestroy()
{
if(inAppBilling != null) inAppBilling.dispose();
super.onDestroy();
return;
}
6. References
Google has extensive website for software developers. The following link is specific for in-app billing: Preparing Your In-app Billing Application.
7. Other open source software published by the author:
8. History
2014/01/16: Version 1.0 Original revision