Google Play Billing Library 4 to 5 Migration Guide

This topic describes how to migrate from Google Play Billing Library 4 to Google Play Billing Library 5 and how to use new subscription capabilities.

Overview

Google Play Billing Library 5 introduced subscription base plans and subscription offers. These features expand the ways by which you can sell subscriptions, and reduce the integration complexity compared to previous versions.

Using the Play Developer Console or Play Developer API, you can configure a single subscription with multiple base plans, each with multiple offers. Subscription offers have flexible pricing models and eligibility options. You can create offers across the subscription lifecycle using a variety of auto-renewing and prepaid plans. For more information, see the integration guide.

Migration Steps

Update Google Play Billing Library

Replace the existing Play Billing Library dependency with the updated version to your app’s build.gradle file.

dependencies {
    def billingVersion = "5.0.0"

    implementation "com.android.billingclient:billing:$billingVersion"
}

Your project should build right away, even if you haven’t modified any calls to methods, as we have built backwards compatibility on the Play Billing Library 5. However, the concept of SKU is considered deprecated.

Initializing the Billing Client and establishing a connection to Google Play

The first steps to launch purchases from an Android app remain the same:

Showing products available to buy

To obtain all offers a user is eligible to purchase:

  • Replace SkuDetailsParams with QueryProductDetailsParams
  • Switch the BillingClient.querySkuDetailsAsync() call to use BillingClient.queryProductDetailsAsync()

Note that query results are now ProductDetails instead of SkuDetails. Each ProductDetails item contains the information about the product (ID, title, type, and so on). For subscription products, ProductDetails contains a List<ProductDetails.SubscriptionOfferDetails>, which is the list of the subscription offer details. For one-time purchase products, ProductDetails contains a ProductDetails.OneTimePurchaseOfferDetails. These can be used to decide which offers to show to the users.

The following example shows how your app might look before and after making these changes:

Before

Kotlin

val skuList = ArrayList<String>()

skuList.add("up_basic_sub")

val params = SkuDetailsParams.newBuilder()

params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)

billingClient.querySkuDetailsAsync(params.build()) {
    billingResult,
    skuDetailsList ->
    // Process the result
}

Java

List<String> skuList = new ArrayList<>();

skuList.add("up_basic_sub");

SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();

params.setSkusList(skuList).setType(SkuType.SUBS);

billingClient.querySkuDetailsAsync(params.build(),
    new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(BillingResult billingResult,
                List<SkuDetails> skuDetailsList) {
            // Process the result.
        }
    }
);

After

Kotlin

val productList =
    listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("up_basic_sub")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )

val params = QueryProductDetailsParams.newBuilder().setProductList(productList)

billingClient.queryProductDetailsAsync(params.build()) {
    billingResult,
    productDetailsList ->
    // Process the result
}

Java

ImmutableList<Product> productList = ImmutableList.of(Product.newBuilder()
                                            .setProductId("up_basic_sub")
                                            .setProductType(ProductType.SUBS)
                                            .build());

QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
    .setProductList(productList)
    .build();

billingClient.queryProductDetailsAsync(
        params,
        new ProductDetailsResponseListener() {
                public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) {
                    // Process the result
                }
        }
);

The callback for queryProductDetailsAsync returns a List<ProductDetails>. Each ProductDetails item contains the information about the product (ID, title, type, and so on). The main difference is that subscription products now also contain a List<ProductDetails.SubscriptionOfferDetails> that contains all offers available to the user.

Since previous versions of the Play Billing Library do not support the new objects (subscriptions, base plans, offers, and so on), the new system translates each subscription SKU into a single backwards compatible base plan and offer. Available one-time purchase products are also ported to a ProductDetails object. The offer details of a one-time purchase product can be accessed with the getOneTimePurchaseOfferDetails() method.

Launching the offer purchase flow

Launching a purchase flow for an offer is very similar to launching a flow for a SKU. To start a purchase request using version 5, do the following:

  • Instead of using SkuDetails for BillingFlowParams, use ProductDetailsParams.
  • The offer(s) details can be obtained using the SubscriptionOfferDetails object.

To purchase a product with the user's selected offer, get the offerToken of the selected offer and pass it into the ProductDetailsParams object.

Once you've created a BillingFlowParams object, launching the billing flow with the BillingClient remains the same.

The following example show how your app might look before and after making these changes:

Before

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...;
// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
val billingFlowParams = BillingFlowParams.newBuilder()
                            .setSkuDetails(skuDetails)
                            .build()

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Java

// An activity reference from which the billing flow will be launched.
Activity activity = ...;
// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
        .setSkuDetails(skuDetails)
        .build();

BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

After

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...;
// Retrieve a value for "productDetails" by calling queryProductDetailsAsync()
// Get the offerToken of the selected offer
val offerToken = productDetails.subscriptionOfferDetails?.get(selectedOfferIndex)?.offerToken

val productDetailsParamsList =
    listOf(
        BillingFlowParams.ProductDetailsParams.newBuilder()
            .setProductDetails(productDetails)
            .setOfferToken(offerToken)
            .build()
    )
val billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)

// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Java

// Retrieve a value for "productDetails" by calling queryProductDetailsAsync()
// Get the offerToken of the selected offer
String offerToken = productDetails
                     .getSubscriptionOfferDetails(selectedOfferIndex)
                     .getOfferToken();
// Set the parameters for the offer that will be presented
// in the billing flow creating separate productDetailsParamsList variable
ImmutableList<ProductDetailsParams> productDetailsParamsList =
        ImmutableList.of(
                 ProductDetailsParams.newBuilder()
                     .setProductDetails(productDetails)
                     .setOfferToken(offerToken)
                     .build()
        );

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(productDetailsParamsList)
            .build();

// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

Processing the purchases

Processing purchases with Google Play Billing Library 5 remains similar to previous versions.

To pull all active purchases owned by the user and query for new purchases, do the following:

  • Instead of passing a BillingClient.SkuType value to queryPurchasesAsync(), pass a QueryPurchasesParams object that contains a BillingClient.ProductType value.

The following example show how your app might look before and after making these changes:

Before

Kotlin

billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS) {
    billingResult,
    purchaseList -> {
        // Process the result
    }
}

Java


billingClient.queryPurchasesAsync(
    BillingClient.SkuType.SUBS,
    new PurchasesResponseListener() {
        public void onQueryPurchasesResponse(
                BillingResult billingResult,
                List&lt;Purchase> purchases) {
            // process the result
        }
    }
);

After

Kotlin

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.SUBS)
        .build()
) { billingResult, purchaseList ->
    // Process the result
}

Java

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder().setProductType(ProductType.SUBS).build(),
    new PurchasesResponseListener() {
        public void onQueryPurchasesResponse(
                BillingResult billingResult,
                List<Purchase> purchases) {
            // Process the result
        }
    }
);

The steps to manage out of app purchases and pending transactions haven’t changed.

Managing subscription status

If you have a subscriptions status management component in your backend that checks the status and manages subscription purchases, you will need to use the Subscription Purchases API. For details on changes from previous versions, see the guide to the May 2022 new subscription features.