In-app integration guidance for alternative billing with user choice

This guide describes how to integrate the APIs to offer alternative billing with user choice in your app.

Play Billing Library setup

Add the Play Billing Library dependency to your Android app. To use the alternative billing APIs you need to use version 5.2 or higher. If you need to migrate from an earlier version, follow the instructions in the migration guide before you attempt to implement alternative billing.

Connect to Google Play

The first steps in the integration process are the same as the ones described in the Google Play Billing integration guide, with a few modifications when initializing your BillingClient:

  • You need to call a new method to indicate that you want to offer the user a choice of billing options: enableUserChoiceBilling.
  • You need to register an UserChoiceBillingListener for handling cases where the user chooses alternative billing.

The following example demonstrates initializing a BillingClient with these modifications:

Kotlin

val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // Handle new Google Play purchase.
   }

val userChoiceBillingListener =
   UserChoiceBillingListener { userChoiceDetails ->
       // Handle alternative billing choice.
   }

var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .enableUserChoiceBilling(userChoiceBillingListener)
   .build()

Java

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // Handle new Google Play purchase.
    }
};

private UserChoiceBillingListener userChoiceBillingListener = new UserChoiceBillingListener() {
    @Override
    public void userSelectedAlternativeBilling(
        UserChoiceDetails userChoiceDetails) {
        // Handle new Google Play purchase.
    }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases()
    .enableUserChoiceBilling(userChoiceBillingListener)
    .build();

After you initialize the BillingClient, you need to establish a connection to Google Play as described in the integration guide.

Display available products

You can display available products to the user in the same way as with a Google Play billing system integration. When your user has seen the products available for purchase and selects one to buy, launch the user choice billing flow as described in the following section.

Launch the user choice billing flow

Launch the user choice billing flow by calling launchBillingFlow(). This works the same as launching a purchase flow with a Google Play billing system integration: you provide a ProductDetails instance and an offerToken corresponding to the product and the offer that the user wants to acquire. If the user chooses Google Play's billing system, this information is used to continue the purchase flow.

When developers call launchBillingFlow(), the Google Play billing system performs the following check:

  • The system checks if the user's Google Play country is a country that supports alternative billing with user choice (i.e. a supported country). If the user's Google Play country is supported, Google Play checks whether alternative billing has been enabled based on the configuration of the BillingClient.
    • If alternative billing with user choice has been enabled, the purchase flow shows the user choice UX.
    • If alternative billing with user choice is not enabled, the purchase flow shows the standard Google Play billing system UX, without user choice.
  • If the user's Google Play country is not a supported country, the purchase flow shows the standard Google Play billing system UX, without user choice.

User's Play country is a supported country

User's Play country is not a supported country

enableUserChoiceBilling called during BillingClient setup

User sees user choice UX

User sees standard Google Play billing system UX

enableUserChoiceBilling not called during BillingClient setup

User sees standard Google Play billing system UX

User sees standard Google Play billing system UX

Handle the user selection

How you handle the rest of the purchase flow differs depending on whether the user selected Google Play's billing system or an alternative billing system.

When the user selects an alternative billing system

If the user chooses the alternative billing system, Google Play calls the UserChoiceBillingListener to notify the app that it needs to launch the purchase flow in the alternative billing system. In particular, the userSelectedAlternativeBilling() method is called.

The external transaction token provided in the UserChoiceDetails object represents a signature for the user's choice to enter the alternative billing flow. Use this token to report any transaction resulting from this choice as explained in the backend integration guide.

The UserChoiceBillingListener should perform the following actions:

  • Get the product or products being purchased by the user so that they can be presented in the purchase flow in the alternative billing system.
  • Collect the string received as the external transaction token and send it to your backend to persist it. This is used later to report the external transaction to Google Play if the user completes this specific purchase.
  • Launch the developer's alternative purchase flow.

If the user completes the purchase using the alternative billing system, you must report the transaction to Google Play by calling the Google Play Developer API from your backend within 24 hours, providing the externalTransactionToken and additional transaction details. See the backend integration guide for more details.

The following example demonstrates how to implement the UserChoiceBillingListener:

Kotlin

private val userChoiceBillingListener =
    UserChoiceBillingListener { userChoiceDetails ->
        // Get the products being purchased by the user.
        val products = userChoiceDetails.products

        // Send external transaction token to developer backend server
        // this devBackend object is for demonstration purposes,
        // developers can implement this step however best fits their
        // app to backend communication.
        devBackend.sendExternalTransactionStarted(
            userChoiceDetails.externalTransactionToken,
            user
        )

        // Launch alternative billing
        // ...
        // The developer backend handles reporting the transaction
        // to Google Play's backend once the alternative billing
        // purchase is completed.
    }

Java

private userChoiceBillingListener userChoiceBillingListener = new UserChoiceBillingListener() {
    @Override
    public void userSelectedAlternativeBilling(
           UserChoiceDetails userChoiceDetails) {
       // Get the products being purchased by the user.
       List<Product> products =
              userChoiceDetails.getProducts();

       // Send external transaction token to developer backend server
       // this devBackend object is for demonstration purposes,
       // developers can implement this step however best fits their
       // app to backend communication.
       devBackend.sendExternalTransactionStarted(
              userChoiceDetails.getExternalTransactionToken(),
              user
       );

       // Launch alternative billing
       // ...
       // The developer backend handles reporting the transaction
       // to Google Play's backend once the alternative billing
       // purchase is completed.
    }
};

When the user selects Google Play's billing system

If the user chooses Google Play's billing system, they continue with the purchase through Google Play.

  • See Processing purchases in the library integration guide for more information about how to handle new in-app purchases through Google Play's billing system.
  • See New subscriptions in the subscription management guide for additional guidance for subscription purchases.

Handle changes in subscription

For developers using alternative billing with user choice, purchases need to be either processed through Google Play's billing system or reported with an externalTransactionId, depending on the user's choice. Changes to existing subscriptions that were processed through the user choice flow can be made through the same billing system until expiration.

This section describes how to handle some common subscription change scenarios.

Upgrade and downgrade flows

Upgrade and downgrade flows should be handled differently depending on whether the subscription was originally bought through Google Play's billing system or through an alternative billing system.

Subscriptions bought through an alternative billing system

For subscriptions that were originally bought through the developer's alternative billing system after user choice, users requesting an upgrade or a downgrade should proceed through the developer's alternative billing system without going through the user choice experience again.

To do this, call launchBillingFlow() when the user requests an upgrade or a downgrade. Instead of specifying a SubscriptionUpdateParams object in the parameters, use setOriginalExternalTransactionId, providing the external transaction ID for the original purchase. This does not display the user choice screen, given that the user choice for the original purchase is preserved for upgrades and downgrades. The call to launchBillingFlow() in this case generates a new external transaction token for the transaction that you can retrieve from the callback.

Kotlin

// The external transaction ID from the current
// alternative billing subscription.
val externalTransactionId = //... ;

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(
        listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                // Fetched via queryProductDetailsAsync.
                .setProductDetails(productDetailsNewPlan)
                // offerIdToken can be found in
                // ProductDetails=>SubscriptionOfferDetails.
                .setOfferToken(offerTokenNewPlan)
                .build()
        )
    )
    .setSubscriptionUpdateParams(
        BillingFlowParams.SubscriptionUpdateParams.newBuilder()
            .setOriginalExternalTransactionId(externalTransactionId)
            .build()

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

// When the user selects the alternative billing flow,
// the UserChoiceBillingListener is triggered.

Java

// The external transaction ID from the current
// alternative billing subscription.
String externalTransactionId = //... ;

BillingFlowParams billingFlowParams =
        BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(
                ImmutableList.of(
                    ProductDetailsParams.newBuilder()
                        // Fetched via queryProductDetailsAsync.
                        .setProductDetails(productDetailsNewPlan)
                        // offerIdToken can be found in
                        // ProductDetails=>SubscriptionOfferDetails
                        .setOfferToken(offerTokenNewPlan)
                    .build()
                )
            )
            .setSubscriptionUpdateParams(
                SubscriptionUpdateParams.newBuilder()
                    .setOriginalExternalTransactionId(externalTransactionId)
                    .build()
            )
            .build();

BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

// When the user selects the alternative billing flow,
// the UserChoiceBillingListener is triggered.

When the upgrade or downgrade is completed in the alternative billing system, you need to report a new transaction using the external transaction token obtained through the previous call for the new subscription purchase.

Subscriptions bought through Google Play's billing system

Similarly, users that bought their current subscription through Google Play's billing system after user choice should be shown the upgrade or downgrade flow in Google Play's billing system. The following instructions describe how you would launch the purchase flow for an upgrade or downgrade through Google Play's billing system:

  1. Identify the offerToken of the selected offer for the new plan:

val offerTokenNewPlan = productDetailsNewPlan
             .getSubscriptionOfferDetails(selectedOfferIndex)
             .getOfferToken()

String offerTokenNewPlan = productDetailsNewPlan
                     .getSubscriptionOfferDetails(selectedOfferIndex)
                     .getOfferToken();

  1. Send the correct information to Google Play's billing system to process the new purchase, including the purchase token for the existing subscription:

val billingFlowParams =
    BillingFlowParams.newBuilder().setProductDetailsParamsList(
        listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(productDetailsNewPlan)
                .setOfferToken(offerTokenNewPlan)
                .build()
        )
    )
    .setSubscriptionUpdateParams(
        BillingFlowParams.SubscriptionUpdateParams.newBuilder()
            .setOldPurchaseToken(oldToken)
            .setReplaceProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE)
            .build()
        )
        .build()

BillingClient.launchBillingFlow(activity, billingFlowParams)

BillingFlowParams billingFlowParams =
        BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(
                ImmutableList.of(
                    ProductDetailsParams.newBuilder()
                        // Fetched via queryProductDetailsAsync
                        .setProductDetails(productDetailsNewPlan)
                        // offerIdToken can be found in
                        // ProductDetails=>SubscriptionOfferDetails.
                        .setOfferToken(offerTokenNewPlan)
                        .build()
                )
            )
            .setSubscriptionUpdateParams(
                SubscriptionUpdateParams.newBuilder()
                    // purchaseToken can be found in
                    // Purchase#getPurchaseToken
                    .setOldPurchaseToken("old_purchase_token")
                    .setReplaceProrationMode(ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE)
                    .build()
            )
            .build();

BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

This purchase proceeds in Google Play's billing system, and your app receives the PurchasesUpdatedListener.onPurchaseUpdated call with the result of the purchase. If the purchase was successful, the onPurchaseUpdated() method also receives the new purchase information, and your backend receives a SUBSCRIPTION_PURCHASED Real Time Developer Notification. When pulling the status for the new purchase, a linkedPurchaseToken attribute links to the old subscription purchase so that you can retire it as recommended.

Subscription cancellations and restorations

Users should be able to cancel their subscription at any time. When a user cancels a subscription, the termination of the entitlement may be deferred until the paid period ends. For example, if a user cancels a monthly subscription halfway through the month, they may continue to access the service for the remaining ~2 weeks until their access is removed. During this period, the subscription is still technically active, so the user can use the service.

It is not uncommon that users decide to reverse the cancellation during this active period. In this guide, this is called a restoration. The following sections describe how to handle restoration scenarios in your alternative billing API integration.

Subscriptions bought through an alternative billing system

If you have an external transaction ID for a canceled subscription, it's not necessary to call launchBillingFlow() to restore the subscription, so it shouldn't be used for this type of activation. If a user restores their subscription while still in the active period of a canceled subscription, no transaction occurs at that time; you can just continue reporting renewals when the current cycle expires and the next renewal occurs. This includes cases where the user receives a credit or special renewal price as part of the restoration (for example, a promotion to encourage the user to continue their subscription).

Subscriptions bought through Google Play's billing system

Generally, users can restore subscriptions on Google Play's billing system. For canceled subscriptions that were originally purchased on Google Play's billing system, the user may choose to undo the cancellation while the subscription is active through Google Play's Resubscribe feature. In that case, you receive a SUBSCRIPTION_RESTARTED Real Time Developer Notification in your backend, and a new purchase token is not issued—the original token is used to continue the subscription. To learn how to manage restoration in Google Play's billing system, see Restorations in the subscription management guide.

You can also trigger a restoration in Google Play's billing system from the app by calling launchBillingFlow(). See Before subscription expiration - in-app for an explanation of how to do this. In the case of users that went through the user choice flow for the original purchase (which was canceled but is still active), the system automatically detects their choice and displays the user interface for restoring these purchases. They are asked to confirm their re-purchase of the subscription through Google Play, but they don't need to go through the user choice flow again. A new purchase token is issued for the user in this case. Your backend receives a SUBSCRIPTION_PURCHASED Real Time Developer Notification, and the linkedPurchaseToken value for the new purchase status is set as in the case of an upgrade or downgrade, with the old purchase token for the subscription that was canceled.

Resubscriptions

If a subscription completely expires, whether it is due to cancellation or payment decline without recovery (an expired account hold), then the user must resubscribe if they want to restart the entitlement.

Resubscribing can also be enabled through the app by processing it similarly to a standard signup. Users should be able to choose which billing system they want to use. launchBillingFlow() may be called in this case, as described in Launch the user choice billing flow.

Next steps

Once you've finished in-app integration, you're ready to integrate your backend.