Add pre-order offers for one-time products

1. Introduction

In this codelab you'll focus on creating a one-time product (OTP) and add a pre-order offer for the product.

Note: Before starting this codelab, you need to request access for the pre-order feature by filling out the One-time products EAP Interest Form.

Audience

This codelab is targeted for Android app developers who are familiar with one-time products and want to understand how to add pre-order offers to their one-time products.

Pre-requisite

If you are new to one-time products, it's recommended that you complete the Unlock new markets with regional product pricing codelab.

What you'll learn...

  • How to use the Google Play Console for creating pre-order offers for your one-time products.
  • How to use the Play Billing Library APIs to query for one-time products and the corresponding pre-order offer details.

What you'll need...

2. Build the sample app

This codelab uses a sample Android app to teach you how to manage one-time products. The sample app is designed to be a fully functional android app that has the complete source code which shows the following aspects:

  • Integration the app with PBL.
  • Fetching of one-time products and the related pre-order offers.
  • Execute purchase flows for regional pricing.

The following demo video shows how the sample app will look and behave after it's deployed and run.

If you are already familiar with one-time products and Play Billing Library (PBL), you can download the sample app and play with it.

Pre-requisites

Before you build and deploy the sample app, do the following:

Build

The objective of this build step is to generate a signed Android app bundle file of the sample app.

To generate the Android app bundle, do the following steps:

  1. Download the sample app from GitHub.
  2. Build the sample app. Before you build, change the package name of the sample app and then build. If you have packages of other apps in your Play Console, ensure the package name you provide for the sample app is unique.

    Note: Building the sample app creates only an APK file that you can use for local testing. However, running the app doesn't fetch products and prices because the products haven't been configured in the Play Console.
  3. Generate a signed Android app bundle.
    1. Generate an upload key and keystore
    2. Sign your app with your upload key
    3. Configure Play App Signing

The next step is to upload the Android app bundle to Google Play Console.

3. Create OTP with pre-order in Play Console

To create one-time products (OTP) in the Google Play Console, you need to have an app in the Play Console. Create an app in the Play Console, and then upload the previously created signed app bundle.

Create an app

To create an app:

  1. Log-in to the Google Play Console, using your developer account.
  2. Click Create app. This opens the Create app page.
  3. Enter an app name, select the default language, and other app related details.
  4. Click Create app. This creates an app in the Google Play Console.

Now you can upload the signed app bundle of the sample app.

Upload the signed app bundle

  1. Upload the signed app bundle to Google Play's Console internal test track. Only after uploading, you can configure the monetization related features in the Play Console.
    1. Click Test and release > Testing > Internal release > Create new release.
    2. Enter a release name and upload the signed APK file.
    3. Click Next, and then click Save and publish.

Now, you can create the one-time products.

Create a one-time product

Now, create the one-time product that you want the users to purchase.

  1. Open the sample app in Google Play Console, and navigate to Monetize with Play > Products > One-time products.
  2. Click Create one-time product.
  3. Enter the following product details:
    • Product ID: Enter a unique ID. For example, upcoming_movie_1.
    • (Optional) Tags: Add relevant tags.
    • Name: Enter a product name. For example, Product Movie.
    • Description: Enter a product description. For example, Product Description.
    • (Optional) Add an Icon image: Upload an icon that represents your product.
    Note: For the purpose of this codelab, you can skip configuring the Tax, compliance, and programs section.
  4. Click Next.
  5. Add a purchase option and configure it's regional availability. A one-time product needs at least one purchase option, which defines how the entitlement is granted, its price, and regional availability. For this codelab, we will add the standard Buy option for the product.In the Purchase option section, enter the following details:
    • Purchase Option ID: Enter a unique ID. For example, buy-movie.
    • Purchase type: Select Buy.
    • (Optional) Tags: Add tags specific to this purchase option.
    • (Optional) Click Advanced options to configure the advanced options. For the purpose of this codelab, you can skip the advanced options configuration.
  6. Next, you must configure the regional availability and price for the purchase option. In the regional availability, you will specify the regions where your product is available, including regions where your app is yet to be published. By default, a purchase option will be available in all regions.In the Availability and pricing section, click Edit availability and access.
    1. Select Set to unavailable.
    Notice that all the regions are automatically selected and are set to Available.
    1. Un-select only the United States country, and then click Set to unavailable. Now the one-time product will be available only in the United States.
    2. In the All regions drop-down, select Available countries and regions. You should see only United States.
    3. Click the Price icon. This displays a dialog to set the price.
    4. Enter 10 USD and then click Save.
  7. Click Save as draft.

Note: Don't activate the purchase option yet. We will activate it after configuring the pre-order offer. This is because you can't add a pre-order offer to an active purchase option whose regional availability is set.

Add a pre-order offer

Now, you'll add a pre-order offer for the buy purchase option that you created previously. A pre-order offer allows users to purchase your item before its official release. Note that pre-order offers are only supported for the Buy purchase option and can only be configured for new products in a region.

Adding a pre-order offer involves the following 2 steps:

  1. Prepare the Buy purchase option for the pre-order offer.
  2. Add the pre-order offer for the purchase option.

Prepare the Buy purchase option for the pre-order offer

  1. Open the sample app in Google Play Console, and navigate to Monetize with Play > Products > One-time products.
  2. In the One-time products page, click the right arrow for your product (upcoming_movie_1). This opens the Edit one-time product page.
  3. Click the right arrow for the buy-movie purchase option that you created previously. This opens the Edit purchase option page.
  4. Click Edit availability and access and then select Set to available and allow users to pre-order.
  5. From the All regions drop-down, select Available countries and regions. This should show only United States that you had configured previously.
  6. Select the country, and then click Set to available for pre-order only.
  7. Click Save.

Note that you still haven't added a pre-order offer to your purchase option. The next step is to add the pre-order offer.

Add a pre-order offer

  1. Open the sample app in Google Play Console, and navigate to Monetize with Play > Products > One-time products.
  2. In the One-time products page, click Add offer > Pre-order for your product (upcoming_movie_1). This opens the Add pre-order page.
  3. Enter Pre-order Details:
    • Pre-order ID: Enter preorder-offer-1.
    • (Optional) Add a discount: You can select None, Percentage, or Absolute discount. For the purpose of this codelab, select None.
    • (Optional) Tags: Add relevant tags.
    • Start date and time: Set a date at least 3 days in the future.
    • End date and time: Set a date at least 24 hours after the start date.
    • Availability after pre-order: Choose whether the product becomes available immediately after the pre-order period or on a specific later date/time.
    • (Optional) Lower price guarantee: Select this option if you want users to be charged the lowest price between their pre-order price and the price at release. This can be a strong incentive for early buyers.
  4. Click Save.
  5. Open the Edit one-time product page for your one-time product (upcoming_movie_1).
  6. Click Activate for the buy purchase option (buy-movie).
  7. Click Activate for the pre-order offer (preorder-offer-1) under the buy purchase option. This activates the pre-order offer and makes it live on date that you previously configured in the pre-order details.

Pre-order offer creation video

The following video shows the pre-order offer creation steps that are previously described.

4. Integrate with PBL

To integrate your app with Play Billing Library (PBL), do the following steps:

  1. Add the Play Billing Library dependency to the sample app.
    dependencies {
    val billing_version = "8.1.0"
    
    implementation("com.android.billingclient:billing-ktx:$billing_version")
    }
    
  2. Initialize the BillingClient. The BillingClient is the client SDK that resides on your app and communicates with the Play Billing Library. The following code snippet shows how to initialize the billing client.
    private BillingClient createBillingClient() {
    return BillingClient.newBuilder(activity)
        .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
        // For one-time products, add a listener to process and acknowledge the purchases. This will notify
        // Google the purchase was processed.
        // For client-only apps, use billingClient.acknowledgePurchase().
        // If you have a secure backend, you must acknowledge purchases on your server using the
        // server-side API.
        // See https://developer.android.com/google/play/billing/security#acknowledge
        // In this sample snippet purchases aren't processed. You must
        // implement your business logic to process and acknowledge the purchases.
        .setListener((billingResult, purchases) -> {})
        .enableAutoServiceReconnection()
        .build();
     }
    
  3. Connect to Google Play.The following code snippet shows how to connect to Google Play.
    /**
    * Starts the billing connection with Google Play. This method should be called exactly once
    * before any other methods in this class.
    *
    * @param productList The list of products to query for after the connection is established.
    */
    public void startBillingConnection(List<Product> productList) {
        billingClient.startConnection(
            new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                Log.d(TAG, "Billing Client Connection Successful");
                queryProductDetails(productList);
                } else {
                Log.e(TAG, "Billing Client Connection Failed: " + billingResult.getDebugMessage());
                listener.onBillingSetupFailed(billingResult); // Propagate the error to the listener to show a message to the user.
                }
            }
    
            @Override
            public void onBillingServiceDisconnected() {
                Log.e(TAG, "Billing Client Connection Lost");
                listener.onBillingError("Billing Connection Lost");
            }
            });
    }
    
  4. Fetch the one-time product details.After integrating your app with PBL, you must fetch the one-time product details into your app. The following code snippet shows how to fetch the one-time product details in your app.
    private void queryProductDetails(List<Product> productList) {
        QueryProductDetailsParams queryProductDetailsParams =
            QueryProductDetailsParams.newBuilder().setProductList(productList).build();
    
        billingClient.queryProductDetailsAsync(
            queryProductDetailsParams,
            new ProductDetailsResponseListener() {
            @Override
            public void onProductDetailsResponse(
                BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) {
                if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                List<ProductDetails> productDetailsList =
                    productDetailsResponse.getProductDetailsList();
                    listener.onProductDetailsResponse(productDetailsList);
                } else {
                Log.e(TAG, "QueryProductDetailsAsync Failed: " + billingResult.getDebugMessage());
                listener.onBillingError("Query Products Failed: " + billingResult.getResponseCode());
                }
            }
            });
    }
    
    Fetching the one-time product (upcoming_movie_1 in this example) in ProductDetails, gives you a response similar to the following:
    {
        "productId": "upcoming_movie_1",
        "type": "inapp",
        "title": "Purrfect Mayhem: The Final Playback (Movies All Day | Play Samples)",
        "name": "Purrfect Mayhem: The Final Playback",
        "description": "Yolo and Thorne must reach the original broadcasting site to initiate the \"Final Playback\" and save the timeline. Follow them through their race against the Clockinators.",
        "skuDetailsToken": "<---skuDetailsToken--->",
        "oneTimePurchaseOfferDetails": {},
        "oneTimePurchaseOfferDetailsList": [
            {
                "priceAmountMicros": 8500000,
                "priceCurrencyCode": "USD",
                "formattedPrice": "$8.50",
                "offerIdToken": "<---offerIdToken--->",
                "offerId": "preorder",
                "purchaseOptionId": "buy-option",
                "offerTags": [],
                "validTimeWindow": {
                    "startTimeMillis": 1756771200000,
                    "endTimeMillis": 1785542400000
                },
                "preorderDetails": {
                    "preorderReleaseTimeMillis": 1785542400000,
                    "preorderPresaleEndTimeMillis": 1785542400000
                }
            }
        ]
    }
    
    Notice the pre-order offer details are available in the oneTimePurchaseOfferDetailsList. This list has 1 purchase option (buy-option) for which pre-order offer was configured in the Play Console. You can identify each purchase option uniquely by its offerIdToken.
  5. Fetch the offer token along with the pre-order offer details. You need the offer token to launch the billing flow in step 6.
    @Override
    public void onProductDetailsResponse(List<ProductDetails> productDetailsList) {
    
    if (productDetailsList != null && !productDetailsList.isEmpty()) {
    
    // 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();
          }
        }
      }
      }
      } else {
            Log.e(TAG, "No product details found for " + productId);
        }
    }
    
  6. Launch the billing flow.
    /**
     * Launches the billing flow for the product with the given offer token.
    *
    * @param activity The activity instance from which the billing flow will be launched.
    * @param productDetails The product details of the product to purchase.
    * @param offerToken The offer token of the product to purchase.
    * @return The result of the billing flow.
    */
    public void launchPurchase(Activity activity, ProductDetails productDetails, String offerToken) {
        ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
            ImmutableList.of(
                BillingFlowParams.ProductDetailsParams.newBuilder()
                    .setProductDetails(productDetails)
                    .setOfferToken(offerToken)
                    .build());
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(productDetailsParamsList)
            .build();
        billingClient.launchBillingFlow(activity, billingFlowParams);
    }
    

5. Test purchase options

Before making your one-time products available in your live app, you can test your PBL integration using license testers and the Play Billing Lab.

To understand how you can test your purchase options by using the Play Billing Lab, see the Unlock new markets with regional product pricing codelab.

6. Next steps

Reference docs

7. Congratulations!

Congratulations! You've successfully navigated the Google Play Console to create a pre-order offer for a one-time product. You now have a deeper understanding of Google Play's flexible product catalog for one-time purchases.

Survey

Your feedback on this codelab is highly valued. Consider taking a few minutes to complete our survey.