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
withQueryProductDetailsParams
- Switch the
BillingClient.querySkuDetailsAsync()
call to useBillingClient.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
forBillingFlowParams
, useProductDetailsParams
. - 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 toqueryPurchasesAsync()
, pass aQueryPurchasesParams
object that contains aBillingClient.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<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.