We launched the Android In-app Billing API to make it easy for users to buy digital products and subscriptions. Since then we have continuously improved the API by introducing subscription upgrades/downgrades, promotion codes, and much more. Google Play Billing supports credit cards, gift cards, PayPal and carrier billing in over 130 countries around the world. It's a great way for you to monetize your app without having to worry about managing your users' payment information and setting up relationships with payment providers.

The new Play Billing Library provides a simple API for integration with Google Play In-app Billing. The new Java library makes it easy to create and manage transactions without dealing with a complex service interface.

Play Billing Library Features

This codelab will walk you through integration with the Play Billing Library.

What you will build

We start with a simple app called Trivial Drive that lets users drive and buy fuel. You will download this skeleton app, and then you will add in-app billing to it.

You will learn:

  • How to integrate purchases and subscriptions
  • The best practices of developing reliable apps that handle purchases

What you'll learn

What you'll need

Download the Code

The code is available via git or a ZIP file.

Clone the sources via git:

Open up your Terminal and run:

git clone https://github.com/googlecodelabs/play-billing-codelab

Or download via a single zip-file:

Click the following link to download all the code for this codelab:

Download Source Code

Unpack the downloaded zip file. You will get the same folders structure as if you cloned it via a github command above.

Setup the environment

This codelab uses Android Studio, an IDE for developing Android apps.

If you don't have it installed yet, please download and install it.

Open Android Studio and click File > Open from the menu bar or Open an Existing Android Studio Project from the splash-screen.

Then select work from the recently cloned folder (play-billing-codelab):

You will need to wait some time until Android Studio synchronizes the gradle build.

Meanwhile, spend some time looking into the existing source code.

All the Java sources are distributed among the following packages:

We will use the billing APIs to make the app look like this:

Launch the skeleton app

Once, Android Studio finished the synchronization, you will see ‘Run' button on the toolbar turned green:

Click it, select the Connected device and skip ‘Instant Run' if asked.

The skeleton app will be automatically built and installed into the Android device, connected to your machine.

It's main functionality:

We're going to extend this app's functionality to:

Information about signing your app

Google Play Billing APIs only works with apps that are published on Google Play. This codelab allows you to skip the publishing step by re-using an app that has already gone through the publishing steps.

In order to re-use our package name, you must have the same release keystore and publishing information that we used to publish to the Play Store.

We have included our keystore in the /work/app directory and your gradle file is configured to use our release key during debugging.

Signed APK

You don't need to do anything special to have your app signed with exact the same certificate as the one, uploaded into Google Play Developer console. Every time you click "Run" button inside your Android Studio, it signs the app with that certificate.

1. Include Billing library into build.gradle file

Add a new dependency inside the app module's build.gradle file:

compile 'com.android.billingclient:billing:1.0'

Press ‘Sync Now' button in the top right corner of Android Studio.

2. Create a BillingClient

The primary interface for using Google Play Billing is the BillingClient interface. The library provides a complete implementation, BillingClientImpl.

To get started, initialize a BillingClient inside BillingManager.java and start a connection to the Google Play Store service:

public class BillingManager implements PurchasesUpdatedListener {
...
private final BillingClient mBillingClient;
private final Activity mActivity;

public BillingManager(Activity activity) {
        mActivity = activity;
        mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
        mBillingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(@BillingResponse int billingResponse) {
                if (billingResponse == BillingResponse.OK) {
                    Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
                } else {
                    Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
                }
            }
            @Override
            public void onBillingServiceDisconnected() {
                Log.w(TAG, "onBillingServiceDisconnected()");
            }
        });
}
}

Resolve all the imports (you can do this by "Alt+Enter" hotkey in front of each unresolved import).

And implement PurchasesUpdatedListener interface inside your BillingManager class, which will receive callbacks for all the updates on future purchases:

@Override
public void onPurchasesUpdated(@BillingResponse int responseCode,
                List<Purchase> purchases) {
        Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
}

Test it out

Now try to build and run your application on the device.

Once GamePlayActivity starts, it initializes BillingManager. The logcat should contain something like the following: "onBillingSetupFinished() response: 0". It indicates the successful connection to the Billing client (0 equals to BillingResponse.OK code).

Please see BillingClient.BillingResponse for full set of response codes.

Initiate a query

Once onBillingSetupFinished is called with BillingResponse.OK, the BillingClient is ready to be used.

First, let's try to query for SKU details. The codelab app already has two in-app products ("gas", "premium") and two subscriptions SKUs ("gold_monthly", "gold_yearly").

Let's query to get their details.

First, let's define a data structure to retrieve the lists of all the SKU IDs for a particular SKU type from Google Play Developer Console. We'll do it inside BillingManager to have all billing integration related code there:

private static final HashMap<String, List<String>> SKUS;
static
{
   SKUS = new HashMap<>();
   SKUS.put(SkuType.INAPP, Arrays.asList("gas", "premium"));
   SKUS.put(SkuType.SUBS, Arrays.asList("gold_monthly", "gold_yearly"));
}

public List<String> getSkus(@SkuType String type) {
   return SKUS.get(type);
}

Then let's implement a new method inside BillingManager that allows to get all the details about products (SKUs) defined at Google Play Developer Console.

This method will accept SkuDetailsParams instance which allows to set SkuType and list of SKUs to get info about. The second parameter is a listener to the actual response from BillingClient library:

public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType,
        final List<String> skuList, final SkuDetailsResponseListener listener) {
    SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
            .setSkusList(skuList).setType(itemType).build();
    mBillingClient.querySkuDetailsAsync(skuDetailsParams,
            new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(int responseCode,
                        List<SkuDetails> skuDetailsList) {
                    listener.onSkuDetailsResponse(responseCode, skuDetailsList);
                }
            });
}

Now let's show this data in the UI. Most of this is ready in the AcquireFragment.

We already pre-defined handleManagerAndUiReady method inside AcquireFragment which will be called once the fragment is shown and BillingManager is accessible. So to get in-app SKU details, just extend this method with the query:

    private void handleManagerAndUiReady() {
        // Start querying for SKUs
        List<String> inAppSkus = mBillingProvider.getBillingManager()
                .getSkus(SkuType.INAPP);
        mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.INAPP,
                inAppSkus,
                new SkuDetailsResponseListener() {
                    @Override
                    public void onSkuDetailsResponse(int responseCode,
                            List<SkuDetails> skuDetailsList) {
                        if (responseCode == BillingResponse.OK
                                && skuDetailsList != null) {
                            for (SkuDetails details : skuDetailsList) {
                                Log.w(TAG, "Got a SKU: " + details);
                            }
                        }
                    }
                });
        
        // Show the UI
        displayAnErrorIfNeeded();
    }

Test it out

Launch the app. Once you click on the "Purchase" button, AcquireFragment will be shown and handleManagerAndUiReady() will execute your query. Logcat should output something like this:

SkuDetails: {"productId":"gas","type":"inapp","price":"$0.99","price_amount_micros":990000,"price_currency_code":"USD","title":"Gas (Play Billing Codelab)","description":"Buy gasoline to ride!"}
SkuDetails: {"productId":"premium","type":"inapp","price":"$1.49","price_amount_micros":1490000,"price_currency_code":"USD","title":"Upgrade your car (Play Billing Codelab)","description":"Buy a premium outfit for your car!"}

Display the SKUs

We created all the UI elements to render the list of products details. You just need to connect the result of the query to the adapter.

First, inside AcquireFragment.handleManagerAndUiReady, repack the result of your query into a local list of SkuRowData, so onSkuDetailsResponse will now look like:

if (responseCode == BillingResponse.OK && skuDetailsList != null) {
    List<SkuRowData> inList = new ArrayList<>();
    for (SkuDetails details : skuDetailsList) {
       Log.i(TAG, "Found sku: " + details);
       inList.add(new SkuRowData(details.getSku(), details.getTitle(), details.getPrice(), 
               details.getDescription(), details.getType()));
    }
}

Finally, use this newly generated list to update the adapter:

if (inList.size() == 0) {
   displayAnErrorIfNeeded();
} else {
   mAdapter.updateData(inList);
   setWaitScreen(false);
}

Generate and upload a signed APK as described above.

Verify that your AcquireFragment dialog now looks something like:

Add subscriptions

As we mentioned before, we also added subscription products inside Google Play Developer console. So let's try to display them as well.

This task is very similar to displaying in-app items from similar steps, so to avoid copy/pasting the code, let's define a reusable listener to getSkuDetails method inside AcquireFragment.handleManagerAndUiReady():

final List<SkuRowData> inList = new ArrayList<>();
SkuDetailsResponseListener responseListener = new SkuDetailsResponseListener() {
   @Override
   public void onSkuDetailsResponse(int responseCode,
               List<SkuDetails> skuDetailsList) {
       if (responseCode == BillingResponse.OK && skuDetailsList != null) {
           // Repacking the result for an adapter
           for (SkuDetails details : skuDetailsList) {
               Log.i(TAG, "Found sku: " + details);
               inList.add(new SkuRowData(details.getSku(), details.getTitle(),
                       details.getPrice(), details.getDescription(),
                       details.getType()));
           }
           if (inList.size() == 0) {
               displayAnErrorIfNeeded();
           } else {
               mAdapter.updateData(inList);
               setWaitScreen(false);
           }
       }
   }
};

Once we have this reusable listener and an extendable list, it's easy to fill our adapter with in-app and subscription products. We can just query with the same SkuDetailsResponseListener for both types of SKUs:

// Start querying for in-app SKUs
List<String> skus = mBillingProvider.getBillingManager().getSkus(SkuType.INAPP);
mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.INAPP, skus, responseListener);
// Start querying for subscriptions SKUs
skus = mBillingProvider.getBillingManager().getSkus(SkuType.SUBS);
mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.SUBS, skus, responseListener);

After those changes, handleManageAndUiReady() will look like this:

    private void handleManagerAndUiReady() {
        final List<SkuRowData> inList = new ArrayList<>();
        SkuDetailsResponseListener responseListener = new SkuDetailsResponseListener() {
            @Override
            public void onSkuDetailsResponse(int responseCode,
               List<SkuDetails> skuDetailsList) {
                if (responseCode == BillingResponse.OK && skuDetailsList != null) {
                    // Repacking the result for an adapter
                    for (SkuDetails details : skuDetailsList) {
                        Log.i(TAG, "Found sku: " + details);
                        inList.add(new SkuRowData(details.getSku(), details.getTitle(),
                                details.getPrice(), details.getDescription(),
                                details.getType()));
                    }
                    if (inList.size() == 0) {
                        displayAnErrorIfNeeded();
                    } else {
                        mAdapter.updateData(inList);
                        setWaitScreen(false);
                    }
                }
            }
        };

        // Start querying for in-app SKUs
        List<String> skus = mBillingProvider.getBillingManager().getSkus(SkuType.INAPP);
        mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.INAPP, skus, responseListener);
        // Start querying for subscriptions SKUs
        skus = mBillingProvider.getBillingManager().getSkus(SkuType.SUBS);
        mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.SUBS, skus, responseListener);
    }

Now you should see subscription details as well:

Display Google Play Billing dialog

Implement the code snippet below inside BillingManager.startPurchaseFlow(). The method startPurchaseFlow is triggered when any of the buttons inside AcquireFragment (i.e. SkusAdapter) is clicked. It also knows the SKU of the product and billing type of the product that had the button triggered.

So you just need to construct BillingFlowParams and add a call to launchBillingFlow there:

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
        .setType(billingType).setSku(skuId).build();
mBillingClient.launchBillingFlow(mActivity, billingFlowParams);

Install it on the device and click button on top of the card with gas. You should see something like the following:

Implement a basic retry policy

If Play Store service was disconnected for some reason, it makes sense to try to connect back to it. For example, it could happen if Play Store app was updated in the background while your application was still running.

So let's try to implement a basic retry mechanism which calls BillingClient.startConnection() method only one time after it was disconnected.

To accomplish this, we will use isReady() method of Billing client.

And let's move start connection of Billing client implemented before from BillingManager's constructor into a separate method to make it reusable.

Also we will add a Runnable argument that will be executed once the connection was successfully established or immediately if client was not disconnected:

private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
   if (mBillingClient.isReady()) {
       if (executeOnSuccess != null) {
           executeOnSuccess.run();
       }
   } else {
       mBillingClient.startConnection(new BillingClientStateListener() {
           @Override
           public void onBillingSetupFinished(@BillingResponse int billingResponse) {
               if (billingResponse == BillingResponse.OK) {
                   Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
                   if (executeOnSuccess != null) {
                       executeOnSuccess.run();
                   }
               } else {
                   Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
               }
           }
           @Override
           public void onBillingServiceDisconnected() {
               Log.w(TAG, "onBillingServiceDisconnected()");
           }
       });
   }
}

Now this method will try to connect one time if the client was disconnected for any reason.

E.g. here is how we will implement a query for SKU details with a basic retry-policy described above:

public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType,
            final List<String> skuList, final SkuDetailsResponseListener listener) {
        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                        .setSkusList(skuList).setType(itemType).build();
                mBillingClient.querySkuDetailsAsync(skuDetailsParams,
                        new SkuDetailsResponseListener() {
                            @Override
                            public void onSkuDetailsResponse(int responseCode,
                                    List<SkuDetails> skuDetailsList) {
                                listener.onSkuDetailsResponse(responseCode, skuDetailsList);
                            }
                        });
            }
        };

        // If Billing client was disconnected, we retry 1 time
        // and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
}

And here is our updated startPurchaseFlow() method:

public void startPurchaseFlow(final String skuId, final String billingType) {
        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                        .setType(billingType)
                        .setSku(skuId)
                        .build();
                mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
            }
        };

        // If Billing client was disconnected, we retry 1 time
        // and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
}

Clean up the resources

It's important to clean the resources and avoid memory leaks in Java, so let's cover this part as well.

To clean all the resources and unregister the observer, you just need to call BillingClient.endConnection. So define a method with this call inside BillingManager and then call it from GamePlayActivity.onDestroy:

public void destroy() {
   mBillingClient.endConnection();
}