Developer Console

Implement the Google Play Billing Interface

This page details how your app can implement in-app purchasing using the Appstore Billing Compatibility SDK. If you've already integrated your app with Google Play Billing Library, you'll make minimal or no code changes in most steps.

For a detailed API reference, see Appstore Billing Compatibility SDK API reference.

Initialize a BillingClient

You don't need to make code changes here.

After following the steps in Appstore Billing Compatibility SDK, initialize a BillingClient instance. A BillingClient object enables communication between Appstore Billing Compatibility APIs and your app. The BillingClient provides asynchronous convenience methods for many common billing operations.

Like Google Play Billing, it's strongly recommended that you instantiate only one BillingClient instance at a time. However, with the Appstore Billing Compatibility SDK, instantiating multiple BillingClient instances at a time doesn't result in multiple PurchasesUpdatedListener callbacks for a single purchase event. Instead, each new instantiation of BillingClient updates the PurchasesUpdatedListener to the new listener provided, even for other BillingClient instances.

Create a BillingClient using the newBuilder() method. To receive updates on purchases, add a listener by calling setListener(). Pass a PurchasesUpdatedListener object to the setListener() method.

The enablePendingPurchases() method is only available as a no-op method . It doesn't enable pending purchases.

The following code shows how to initialize a BillingClient.

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // To be implemented
    }
};

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

Connect to the Amazon Appstore

You don't need to make code changes here.

Unlike the requirement to establish a connection in Google Play, the Amazon Appstore doesn't have any concept of maintaining a connection. Since connection maintenance is not necessary in the Amazon Appstore, you don't need to monitor whether the connection is broken or not. Connection-related APIs are supported as no-op methods that assume the connection is always ready.

On calling startConnection(), the BillingClientStateListener always receives a callback with BillingResponseCode.OK. The onBillingServiceDisconnected() method is provided as a no-op method, which is never invoked by the Appstore Billing Compatibility SDK.

The following example demonstrates how to connect to the Amazon Appstore.

billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK) {
            // In the Appstore Billing Compatibility SDK, the BillingClientStateListener always receives
            // a callback with BillingResponseCode.OK.

            // Query products and purchases here.
        }
    }

    @Override
    public void onBillingServiceDisconnected() {
        // This is a no-op method, which is never invoked by the Appstore Billing Compatibility SDK.
    }
});

Show products available to buy

You don't need to make code changes here.

Before showing products to your users, make sure to query for product details to get localized product information. You can either call queryProductDetailsAsync() or querySkuDetailsAsync() to query for in-app product details.

The only error response codes the Appstore Billing Compatibility SDK returns are BillingResponseCode.DEVELOPER_ERROR and BillingResponseCode.ERROR. Other error response codes supported by Google Play Billing are available, but are never returned.

QueryProductDetailsAsync API

You can query for product details with the queryProductDetailsAsync() method. This method takes an instance of QueryProductDetailsParams. The QueryProductDetailsParams object specifies a list of product ID strings you created in the Amazon Developer Console, along with a ProductType. For consumables and entitlements, the ProductType is ProductType.INAPP. For subscriptions, the ProductType is ProductType.SUBS.

For subscription products, the API returns a list of subscription offer details, List<ProductDetails.SubscriptionOfferDetails>, that contains all offers available to the user. Each offer has a unique offer token, which you can access by using the getOfferToken() method. You must pass the offer token when launching the purchase flow. You can access the offer details of a one-time purchase in-app item with the getOneTimePurchaseOfferDetails() method of the API response.

To handle the result of the asynchronous operation, the queryProductDetailsAsync() method also requires a listener. This listener is your implementation of the ProductDetailsResponseListener interface, where you override onProductDetailsResponse(). The onProductDetailsResponse() method notifies the listener when the product details query finishes, as shown in the following example.

QueryProductDetailsParams queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.INAPP)
                    .build()))
        .build();

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
        public void onProductDetailsResponse(BillingResult billingResult,
                List<ProductDetails> productDetailsList) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Process the returned skuDetailsList.
            } else if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
                // Handle the error response.
            } else if (billingResult.getResponseCode() == BillingResponseCode.DEVELOPER_ERROR) {
                // Handle the developer error response.
            } else {
                // Other error codes are available, but are never returned by the
                // Appstore Billing Compatibility SDK.
            }
        }
    }
);

Unlike Google Play Billing, ProductDetails.getTitle() does not include the app name.

QuerySkuDetailsAsync API

You can query for SKU details with the querySkuDetailsAsync() method. This method takes an instance of SkuDetailsParams, which specifies a list of SKU strings created in the Amazon Developer Console, along with a SkuType. For consumables and entitlements, the SkuType is SkuType.INAPP. For subscriptions, the SkuType is SkuType.SUBS.

To handle the result of the asynchronous operation, querySkuDetailsAsync() also requires a listener. This listener is your implementation of the SkuDetailsResponseListener interface, where you override onSkuDetailsResponse(). The onSkuDetailsResponse() method notifies the listener when the SKU details query finishes, as shown in the following example.

SkuDetailsParams skuDetailsParams =
    SkuDetailsParams.newBuilder()
            .setType(BillingClient.SkuType.INAPP)
            .setSkusList(skusList)
            .build();

billingClient.querySkuDetailsAsync(
    skuDetailsParams,
    new SkuDetailsResponseListener() {
        public void onSkuDetailsResponse(BillingResult billingResult,
               List<SkuDetails> skuDetailsList) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Process the returned skuDetailsList.
            } else if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
                // Handle the error response.
            } else if (billingResult.getResponseCode() == BillingResponseCode.DEVELOPER_ERROR) {
                // Handle the developer error response.
            } else {
                // Other error codes are available, but are never returned by the
                // Appstore Billing Compatibility SDK.
            }
        }
    }
);

Launch the purchase flow

You might need to make minimal code changes here.

Unlike Google Play Billing, the Appstore Billing Compatibility SDK allows at most one product in a single purchase. If the list has more than one item, the error BillingResponseCode.FEATURE_NOT_SUPPORTED is returned.

For your app to start the purchase flow, call launchBillingFlow() from your app's main thread. The launchBillingFlow() method takes a BillingFlowParams object, which contains a ProductDetails object. You can get ProductDetails by calling queryProductDetailsAsync(). Create a BillingFlowParams object by using the BillingFlowParams.Builder class. The following example shows how to launch the billing flow.

// An activity reference from which the billing flow is launched
Activity activity = ...;

ImmutableList productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // Call queryProductDetailsAsync to get productDetails
            .setProductDetails(productDetails)
            .build()
    );

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

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

The following code shows an example of setting an offer token for a purchase. For more details about the offer token, see QueryProductDetailsAsync API.

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.setOfferToken(offerDetails.getOfferToken())
.build();

Alternatively, the BillingFlowParams object can be initialized with a SkuDetails object obtained from calling querySkuDetailsAsync() as shown.

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
     .setSkuDetails(skuDetails)
     .build();

When you initialized your BillingClient, you used setLister() to add your implementation of PurchasesUpdatedListener as a listener. This listener overrides the onPurchasesUpdated() method, which delivers the result of your purchase. Your implementation of onPurchasesUpdated() must handle the possible response codes, as shown in the following example.

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK) {
        for (Purchase purchase : purchases) {
            handlePurchase(purchase);
        }
    } else if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
        // Handle an error in the purchase flow.
    } else if (billingResult.getResponseCode() == BillingResponseCode.DEVELOPER_ERROR) {
        // Handle a developer error in the purchase flow.
    } else {
        // Other error codes are available, but are never returned by 
        // the Appstore Billing Compatibility SDK.
    }
}

The only error response codes the Appstore Billing Compatibility SDK returns are BillingResponseCode.DEVELOPER_ERROR and BillingResponseCode.ERROR. Other error response codes supported by Google Play Billing are available, but are never returned.

When a purchase is successful, a purchase token is generated. A purchase token uniquely identifies a purchase and represents the user and the product ID associated with the purchase.

Unsupported fields

The following fields that are available in Google Play Billing are not supported in the Appstore Billing Compatibility SDK. Remove references to these fields from your code.

Request fields:

  • Account identifiers (Obfuscated Account ID and Obfuscated Profile ID)
  • VR Purchase Flow

Response fields:

  • Account identifiers (Obfuscated Account ID and Obfuscated Profile ID)
  • Order ID
  • Signature
  • Package name
  • Acknowledged

Note: The Appstore Billing Compatibility SDK supports only fields present in the IAB-4.0 specification, except the ones listed above.

Process purchases

You might need to make minimal code changes here.

After a user completes a purchase, your app needs to process that purchase. Your app is usually notified of purchases through your PurchasesUpdatedListener. However, there are cases where your app uses queryPurchasesAsync() to fetch purchases, as described in Fetch Purchases.

On completing a purchase, your app should give the content to the user. For entitlements, acknowledge delivery of the content using acknowledgePurchase(). For consumables, call consumeAsync() to acknowledge delivery and mark the item as consumed.

Review these differences between the Amazon Appstore's Google Play Billing API interface and the Google Play Billing Library:

  • Calling acknowledgePurchase() on a consumable doesn't just acknowledge the item, it also consumes it. Calling consumeAsync() on an entitlement or subscription only acknowledges the item, without consuming it. This happens because the Appstore Billing Compatibility SDK considers consumables and entitlements as separate entities internally, unlike Google Play Billing.
  • The Appstore Billing Compatibility SDK allows purchasing a consumable item again, even if it had already been purchased and not yet consumed (which can occur if consumeAsync() wasn't called on the previous purchase).
  • On non-acknowledgement of purchases, users do not automatically receive refunds. This is different from Google Play Billing, where purchases are revoked on non-acknowledgement for three days as detailed in the Android developer documentation.

The following example shows how to consume a product using the associated purchase token:

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from queryPurchasesAsync or your PurchasesUpdatedListener.
    if (purchase.getPurchaseState() != PurchaseState.PURCHASED) {
        return;
    }

    // Deliver item to user.

    // Consume item.
    ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Handle the success of the consume operation.
            } else if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
                // Handle the error response.
            } else {
                // Other error codes are available, but are never returned by
                // the Appstore Billing Compatibility SDK.
            }
        }
    };

    billingClient.consumeAsync(consumeParams, listener);
}

Similarly, the following example shows how to acknowledge a purchase using the associated purchase token:

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        AcknowledgePurchaseParams acknowledgePurchaseParams =
            AcknowledgePurchaseParams.newBuilder()
                .setPurchaseToken(purchase.getPurchaseToken())
                .build();

        AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

        billingClient.acknowledgePurchase(
            acknowledgePurchaseParams,
            acknowledgePurchaseResponseListener);
    }
}

The only error response code returned by the Appstore Billing Compatibility SDK when consuming an item is BillingResponseCode.ERROR. Other error response codes supported by Google Play Billing are available, but are never returned.

Fetch purchases

You might need to make minimal code changes here.

Although your app is notified of purchases when listening through PurchasesUpdatedListener, certain scenarios might cause your app to be unaware of a purchase a user has made. Scenarios where your app could be unaware of purchases are:

  • Network issues: A user makes a successful purchase, but their device has a network connection failure before being notified of the purchase through PurchasesUpdatedListener.
  • Multiple devices: A user buys an item on a device, switches to another device, and expects to see the item they purchased.
  • Subscription lifecycle events: Subscription lifecycle events like renewals occur periodically without API calls from the billing client.

You can handle these scenarios by calling queryPurchasesAsync() in your onResume() method. This ensures all purchases are successfully processed, as described in Process purchases. Unlike Google Play Billing, queryPurchasesAsync() makes a network call when the local cache expires, which affects the time it takes for the listener callback to occur. To reduce the call times in the Appstore Billing Compatibility SDK, limit the number of SKUs to 100 in a queryPurchasesAsync() call.

The queryPurchasesAsync() method returns only non-consumed in-app purchases for entitlements and non-consumed consumables and active subscriptions. The following example shows how to fetch a user's in-app purchases:

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
      .setProductType(ProductType.INAPP)
      .build(),
    new PurchasesResponseListener() {
      public void onQueryPurchasesResponse(BillingResult billingResult, List purchases) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK) {
            // Process returned purchase list (display the in-app items the user owns).
        } else if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
            // Handle the error response.
        } else if (billingResult.getResponseCode() == BillingResponseCode.DEVELOPER_ERROR) {
            // Handle the developer error response.
        } else {
            // Other error codes are available, but are never returned by
            // the Appstore Billing Compatibility SDK.
        }
      }
    }
);

For subscriptions, pass ProductType.SUBS, while creating QueryPurchasesParams as shown here.

QueryPurchasesParams.newBuilder()
.setProductType(ProductType.SUBS)
.build()

Alternatively, queryPurchasesAsync() can be called with a string specifying the SkuType instead of a QueryPurchasesParams object as shown.

billingClient.queryPurchasesAsync(
     SkuType.INAPP,
     purchasesResponseListener
 );

The only error response codes the Appstore Billing Compatibility SDK returns are BillingResponseCode.DEVELOPER_ERROR and BillingResponseCode.ERROR. Other error response codes supported by Google Play Billing are available, but are never returned.

Unlike Google Play Billing, consumables that aren't consumed (if consumeAsync() wasn't called) are only returned on the device from which they were purchased, and not other devices. If the version of the app changes on the same device (for example, when the app is upgraded), consumables that have not yet been consumed might not be returned immediately, but they are eventually returned.

Unsupported fields

The following fields that are available in Google Play Billing are not supported in the Appstore Billing Compatibility SDK in the response. Remove references to these fields from your code.

  • Account identifiers (Obfuscated Account ID and Obfuscated Profile ID)
  • Order ID
  • Signature
  • Package name
  • Acknowledged

Note: The Appstore Billing Compatibility SDK supports only fields present in the IAB-4.0 specification, except the ones listed above.

Unsupported features

The Appstore Billing Compatibility SDK doesn't support the following features and APIs.

Test your app

To test your app and verify the integration with the Appstore Billing Compatibility SDK, follow these guidelines.

  • Use the Live App Testing service to test your app in a live production environment with a select group of users.
  • Create and submit the in-app purchase items for your Appstore Billing Compatibility SDK integrated app before you start Live App Testing.
  • Keep the SKUs for the in-app items on the Amazon Developer Console the same as the product IDs on Google Play Console for the app. Otherwise, you will have to update the product IDs in your app as well.

Last updated: Oct 27, 2023