This document details the integration of your one-time products (OTPs) with the Play Billing Library. It further explains how to integrate various purchase options and offers related to your one-time products.
You can configure multiple purchase options and offers for your one-time products. For example, you can configure a buy purchase option and a pre-order offer for the same one-time product.
Prerequisites
To configure multiple offers for one-time products, you must use the
queryProductDetailsAsync()
API. The deprecated querySkuDetailsAsync()
API isn't supported. For information on how to use
queryProductDetailsAsync()
and the version of launchBillingFlow()
that takes ProductDetailsParams
as input, see migration steps.
Query the product details
If you have configured multiple offers or purchase options for your one-time
product, the ProductDetails
object returned by the
queryProductDetailsAsync()
method can have more than one available buy
and (or) rent purchase option per one-time product. To get the list of all the
eligible offers for each ProductDetails
object, use the
getOneTimePurchaseOfferDetailsList()
method. Only offers and purchase options
for which the user is eligible will be returned as part of this list. Your code
in the onProductDetailsResponse()
method should handle the returned
offers.
Launch the billing flow
To start a purchase request from your app, call the launchBillingFlow()
method from your app's main thread. This method takes a reference to a
BillingFlowParams
object that contains the relevant ProductDetails
object obtained from calling queryProductDetailsAsync()
. To create a
BillingFlowParams
object, use the BillingFlowParams.Builder
class. Note that you must set the offer token corresponding to the offer
selected by the user when creating the BillingFlowParams
object.
The following sample shows how to launch the purchase flow for a one-time product with multiple offers:
Java
// An activity reference from which the billing flow will launch. Activity activity = ...; ImmutableList<ProductDetailsParams> productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder() // retrieve a value for "productDetails" by calling queryProductDetailsAsync() .setProductDetails(productDetails) // to get an offer token, call // ProductDetails.getOneTimePurchaseOfferDetailsList() for a list of offers // that are available to the user .setOfferToken(selectedOfferToken) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); // Launch the billing flow BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
The offerToken
can be found as part of the OneTimePurchaseOfferDetails
. When
you show the offer to the user, make sure you configure the billing flow
parameters with the correct offer token which you can get from the
oneTimePurchaseOfferDetails.getOfferToken()
method.
Purchase options and offers
A purchase option lets you define how the entitlement is granted to a user, its price, and in which region the product is available. A single product can have multiple purchase options, which can represent where and how you sell your product.
Google Play supports the following purchase options for one-time products:
- Buy purchase option
- Rent purchase option
Offers refer to a pricing scheme that you can create for your one-time products. For example, you can create a discount offer for your one-time product.
Google Play supports the following purchase offers for one-time products:
- Pre-order offer (supported only for the buy purchase option)
- Discount offer (supported for both buy and rent purchase options)
Buy purchase option
A buy purchase option represents a standard, outright purchase of the one-time product. It has an optional legacyCompatible field, indicating whether this purchase option will be available in older Play Billing Library (version 7 or older) flows that don't support the new model. For backwards compatibility, at least one buy purchase option should be marked as legacy compatible.
The steps for integrating both the buy and the rent purchase options with PBL are the same. To understand how to integrate the buy purchase option with PBL, see Integrate rent purchase option with PBL.
Rent purchase option
The rent purchase option lets users access the one-time products for a specified time duration. You can specify the rental period and it's expiration. This document describes the steps to integrate the rent purchase option with the Play Billing Library (PBL).
Integrate the rent purchase option with PBL
This section describes how to integrate the rent purchase option with the Play Billing Library (PBL). It assumes that you are familiar with the initial PBL integration steps such as, adding the PBL dependency to your app, initializing the BillingClient, and connecting to Google Play. This section focuses on the PBL integration aspects that are specific to the rent purchase option.
To configure products available for rent, you will need to use the new
monetization.onetimeproducts
service of the Play Developer API or the Play
Developer Console UI. To use the service, you can call the REST API directly, or
use the Java client library.
Launch a purchase flow for the rent option
To launch a purchase flow for a rental offer, do the following steps:
Fetch the rent purchase option metadata by using the
ProductDetails.oneTimePurchaseOfferDetails.getRentalDetails()
method.The following sample shows how to get the rent purchase metadata:
Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { // Checks if the offer is a rent purchase option. if (oneTimePurchaseOfferDetails.getRentalDetails() != null) { // process the returned RentalDetails OneTimePurchaseOfferDetails.RentalDetails rentalDetails = oneTimePurchaseOfferDetails.getRentalDetails(); // Get rental period in ISO 8601 format. String rentalPeriod = rentalDetails.getRentalPeriod(); // Get rental expiration period in ISO 8601 format, if present. if (rentalDetails.getRentalExpirationPeriod() != null) { String rentalExpirationPeriod = rentalDetails.getRentalExpirationPeriod(); } // Get offer token String offerToken = oneTimePurchaseOfferDetails.getOfferToken(); // Get the associated purchase option ID if (oneTimePurchaseOfferDetails.getPurchaseOptionId() != null) { String purchaseOptionId = oneTimePurchaseOfferDetails.getPurchaseOptionId(); } } } } } });
Launch the billing flow.
To start a purchase request from your app, call the
launchBillingFlow()
method from your app's main thread. This method takes a reference to aBillingFlowParams
object that contains the relevantProductDetails
object obtained from callingqueryProductDetailsAsync()
. To create aBillingFlowParams
object, use theBillingFlowParams.Builder
class. Note that you must set the offer token corresponding to the offer selected by the user when creating theBillingFlowParams
object. If a user is eligible for the rent purchase option, they will receive an offer with RentalDetails and offerId inqueryProductDetailsAsync()
.The following sample shows how to launch the billing flow:
Kotlin
// An activity reference from which the billing flow will be launched. val activity : Activity = ...; val productDetailsParamsList = listOf( BillingFlowParams.ProductDetailsParams.newBuilder() // retrieve a value for "productDetails" by calling queryProductDetailsAsync() .setProductDetails(productDetails) // Get the offer token: // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList() // for a list of offers that are available to the user. // b. For subscriptions, call ProductDetails.subscriptionOfferDetails() // for a list of offers that are available to the user. .setOfferToken(selectedOfferToken) .build() ) val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build() // Launch the billing flow val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
Java
// An activity reference from which the billing flow will be launched. Activity activity = ...; ImmutableList<ProductDetailsParams> productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder() // retrieve a value for "productDetails" by calling queryProductDetailsAsync() .setProductDetails(productDetails) // Get the offer token: // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList() // for a list of offers that are available to the user. // b. For subscriptions, call ProductDetails.subscriptionOfferDetails() // for a list of offers that are available to the user. .setOfferToken(selectedOfferToken) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); // Launch the billing flow BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
The
offerToken
can be found as part of theOneTimePurchaseOfferDetails
. When you show the offer to the user, make sure you configure the billing flow parameters with the correct offer token which you can get from theoneTimePurchaseOfferDetails.getOfferToken()
method.
Pre-order offer
Pre-order lets you set up one-time products to be bought before the item is released. When a user pre-orders your product, they agree to pay for the item when the product is released, unless the user cancels the pre-order before the release date. On the release date, a buyer is charged and Play will notify them by email that the item is released.
This document describes the steps to integrate the pre-order purchase offer with the Play Billing Library (PBL).
Integrate pre-order offer with PBL
This section describes how to integrate the pre-order offer with the Play Billing Library (PBL). It assumes that you are familiar with the initial PBL integration steps such as, adding the PBL dependency to your app, initializing the BillingClient, and connecting to Google Play. This section focuses on the PBL integration aspects that are specific to the pre-order offer.
Launch a purchase flow for a pre-order offer
To launch a purchase flow for a pre-order offer, do the following steps:
Fetch the pre-order offer metadata by using the
ProductDetails.oneTimePurchaseOfferDetails.getPreorderDetails()
method. The following sample shows how to get the pre-order offer metadata:Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { // Checks if the offer is a preorder offer. if (oneTimePurchaseOfferDetails.getPreorderDetails() != null) { // process the returned PreorderDetails OneTimePurchaseOfferDetails.PreorderDetails preorderDetails = oneTimePurchaseOfferDetails.getPreorderDetails(); // Get preorder release time in millis. long preorderReleaseTimeMillis = preorderDetails.getPreorderReleaseTimeMillis(); // Get preorder presale end time in millis. long preorderPresaleEndTimeMillis = preorderDetails.getPreorderPresaleEndTimeMillis(); // Get offer ID String offerId = oneTimePurchaseOfferDetails.getOfferId(); // Get the associated purchase option ID if (oneTimePurchaseOfferDetails.getPurchaseOptionId() != null) { String purchaseOptionId = oneTimePurchaseOfferDetails.getPurchaseOptionId(); } } } } } });
Launch the billing flow.
To start a purchase request from your app, call the
launchBillingFlow()
method from your app's main thread. This method takes a reference to aBillingFlowParams
object that contains the relevantProductDetails
object obtained from calling queryProductDetailsAsync(). To create aBillingFlowParams
object, use theBillingFlowParams.Builder class
. Note that you must set the offer token corresponding to the offer selected by the user when creating theBillingFlowParams
object. If a user is eligible for the pre-order offer, they will receive an offer with PreorderDetails and offerId in thequeryProductDetailsAsync()
method.The following sample shows how to launch the billing flow:
Java
// An activity reference from which the billing flow will launch. Activity activity = ...; ImmutableList productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder() // retrieve a value for "productDetails" by calling queryProductDetailsAsync() .setProductDetails(productDetails) // to get an offer token, call // ProductDetails.getOneTimePurchaseOfferDetailsList() for a list of offers // that are available to the user .setOfferToken(selectedOfferToken) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); // Launch the billing flow BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
The
offerToken
can be found as part of theOneTimePurchaseOfferDetails
. When you show the offer to the user, make sure you configure the billing flow parameters with the correct offer token which you can get from theoneTimePurchaseOfferDetails.getOfferToken()
method.
Discount offer
This section describes how you can configure discount offers for your one-time products.
There are four different parameters you can configure in a one-time product discount offer:
Discounted offer price: Specifies details about either the percentage discounted off or absolute price off the original price.
Countries or Regions eligibility: Specifies the availability of one-time product offers in a country or a region.
Purchase limit (optional): Lets you to determine how many times a user can redeem the same offer. If a user exceeds the purchase limit, the user will be ineligible for the offer.
Limited time (optional): Specifies the time period in which the offer is available. Outside of the time period, the offer is ineligible for purchase.
Retrieve discounted offer price information
For a discounted offer, you can retrieve the percentage of discount or the absolute discount offered.
Example 1: Retrieve discounted offer's percentage discount
The following sample shows how to get the discounted offer's original full price and its percentage discount. Note that the percentage discount information is only returned for discounted offers.
Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult){ // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { long discountedOfferPriceMicros = oneTimePurchaseOfferDetails.getPriceAmountMicros(); // process the returned fullPriceMicros and percentageDiscount. if (oneTimePurchaseOfferDetails.getFullPriceMicros() != null) { long fullPriceMicros = oneTimePurchaseOfferDetails.getFullPriceMicros(); } if (oneTimePurchaseOfferDetails.getDiscountDisplayInfo() != null) { long percentageDiscount = oneTimePurchaseOfferDetails .getDiscountDisplayInfo() .getPercentageDiscount(); } // … } } } });
Example 2: Retrieve discounted offer's absolute discount
The following example shows how to get the discounted offer's original full price and its absolute discount in micros. Note that the absolute discount in micros information is only returned for discounted offers. Either the absolute discount or percentage discount must be specified for a discount offer.
Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { long discountedOfferPriceMicros = oneTimePurchaseOfferDetails.getPriceAmountMicros(); // process the returned fullPriceMicros and absolute DiscountAmountMicros. if (oneTimePurchaseOfferDetails.getFullPriceMicros() != null) { long fullPriceMicros = oneTimePurchaseOfferDetails.getFullPriceMicros(); } if (oneTimePurchaseOfferDetails.getDiscountDisplayInfo() != null) { long discountAmountMicros = oneTimePurchaseOfferDetails .getDiscountDisplayInfo() .getDiscountAmount() .getDiscountAmountMicros(); } // … } } } });
Get the valid time window of an offer
You can use OneTimePurchaseOfferDetails.getValidTimeWindow()
method to get the
valid time window for an offer. This object contains the time window start and
end time in milliseconds.
The following sample shows how to get the valid time window of an offer:
Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { if (oneTimePurchaseOfferDetails.getValidTimeWindow() != null) { // process the returned startTimeMillis and endTimeMillis. ValidTimeWindow validTimeWindow = oneTimePurchaseOfferDetails.getValidTimeWindow(); long startTimeMillis = validTimeWindow.getStartTimeMillis(); long endTimeMillis = validTimeWindow.getEndTimeMillis(); // … } } } } });
Limited quantity at the discount offer level
You can specify the maximum quantity limit at the discount offer level, that is applied only at the offer level. Here is an example to illustrate:
- Super screensavers has 2 offers for the screensaver product: purchase option
screensaver and discount screensaver.
- The purchase option screensaver doesn't have a limited quantity set up.
- The discount screensaver has the offer level maximum allowed quantity set to 3.
- The screensaver product does not have product level maximum allowed quantity, so users can buy unlimited quantities of this product.
- The user owns 1 discount screensaver, and they plan to buy another one with the discount screensaver.
- When retrieving the available offers, the LimitedQuantityInfo for the purchase option screensaver is null and the remaining quantity value for the discount screensaver is 2.
The following sample shows how to get the limited quantity at the discount offer level:
Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { if (oneTimePurchaseOfferDetails.getLimitedQuantityInfo() != null) { // process the returned maximumQuantity and remainingQuantity. LimitedQuantityInfo limitedQuantityInfo = oneTimePurchaseOfferDetails.getLimitedQuantityInfo(); int maximumQuantity = limitedQuantityInfo.getMaximumQuantity(); int remainingQuantity = limitedQuantityInfo.getRemainingQuantity(); // … } } } } });
When users use up the maximum quantity redemptions for an offer, the offer isn't
returned by the getOneTimePurchaseOfferDetailsList()
method.
Calculate redemption limit
The following example shows how to get the limited quantity information on a certain discount offer. You can get the maximum allowed quantity and the remaining quantity for the current user. Note that limited quantity feature is applicable for both the consumable and non-consumable one-time product offers. This feature is supported only at the offer level.
Google Play calculates the remaining quantity by subtracting the user's owned quantity from the maximum allowed quantity you have set up. When counting the user's owned quantity, Google Play considers consumed purchases or pending purchases. Purchases that were canceled, refunded or charged-back don't count towards the user's owned quantity. For example:
Super screensavers set up a discount offer with the maximum allowed quantity of one, so users can buy up to one discounted screensaver.
The user buys one of the discounted screensaver. If the user then tries to buy the second discounted screensaver, it will error out and the
PurchasesUpdatedListener
will get an ITEM_UNAVAILABLE response code.The user asks for a refund of the originally purchased discounted screensaver, and successfully receives the refund. The user tries to buy one of the discounted screensaver, and the purchase will succeed.
Country and region eligibility
You can choose the countries or regions where purchase option offer or discount
offer will be available to users. Google Play will evaluate user eligibility
based on Play country. When you configure regional availability for an offer, it
will only be returned as part of getOneTimePurchaseOfferDetailsList()
if the
user is in a targeted country or region, otherwise it won't be part of the list
of offers returned when you call queryProductDetailsAsync()
.
Offer tags
The following sample shows how to retrieve the offer tags associated with an offer.
Java
billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { // check billingResult // … // process productDetailsList returned by QueryProductDetailsResult for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails : productDetails.getOneTimePurchaseOfferDetailsList()) { // process the returned offer tags. ImmutableList<String> offerTags = oneTimePurchaseOfferDetails.getOfferTagsList(); // … } } } });
Inheritance of offer tags
You can set offer tags for either product, purchase option or discount offer. Discount offers inherit the offer tags from its purchase option offer. Similarly, if offer tags are specified at product level, both purchase option offer and discount offers inherit the product offer tags.
For example, Super screensavers has two offers for the screensaver product; purchase option screensaver and a discount screensaver.
- Super screensaver has the product offer tag
SSProductTag
. - The purchase option screensaver has the offer tag
SSPurchaseOptionTag
. - The discount screensaver has the offer tag
SSDiscountOfferTag
.
In this example, the oneTimePurchaseOfferDetails.getOfferTagsList()
method for
the purchase option offer returns SSProductTag
and SSPurchaseOptionTag
. For
the discount offer, the method returns SSProductTag
, SSPurchaseOptionTag
,
and SSDiscountOfferTag
.