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
- Connect to the Amazon Appstore
- Show products available to buy
- Launch the purchase flow
- Process purchases
- Fetch purchases
- Unsupported fields
- Unsupported features
- Test your app
- Related topics
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. CallingconsumeAsync()
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.
Purchase verification (optional)
Server-side purchase verification is available for the Amazon Appstore, but it differs from Google Play Billing. This is an optional step.
After a successful purchase, you can verify that the purchase receipt has come from an authorized source. To verify the correctness of a receipt, the Appstore provides the Receipt Verification Service (RVS), which requires you to make a call to the RVS API from your server. For details, see RVS for Consumables and Entitlements and RVS for Subscriptions.
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.
queryPurchaseHistoryAsync
API. For details on this API, see the Android developer documentation.showInAppMessages
API. For details on this API, see the Android developer documentation.
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.
Related topics
- Appstore Billing Compatibility Subscriptions
- Appstore Billing Compatibility SDK—overview and integration guide
- Appstore Billing Compatibility SDK Troubleshooting
- Appstore Billing Compatibility SDK Best Practices
- Appstore Billing Compatibility API Reference
Last updated: Oct 14, 2024