This topic describes how to migrate away from a Google Play Billing integration that uses the Android Interface Definition Language (AIDL). Accessing Google Play Billing using AIDL is deprecated, and all integrations must use the Google Play Billing Library in the future.
Migration Steps
Import Google Play Billing Library
First, add a dependency to the Google Play Billing Library. If you are using
Gradle, you can add the following to your app's build.gradle file:
dependencies {
implementation 'com.android.billingclient:billing:2.2.0'
}
You can delete any "glue" code, such as
IabHelper,
which you might have copied from previous reference code. The functionality
offered by IabHelper is now part of the Google Play Billing Library.
Remove the com.android.vending.BILLING permission
The Google Play Billing Library embeds the com.android.vending.BILLING
permission inside its manifest. It is no longer necessary to explicitly add this
permission inside your app's manifest.
Connect to Google Play Billing
The Google Play Billing Library's
BillingClient
handles connection management for you. To migrate, make the following
changes to your app:
- Create an instance of
BillingClient. - Implement a
BillingClientStateListenerto receive callbacks about service status. - Call
startConnection()on yourBillingClientinstance. - Remove
onActivityResult()code related to in-app purchase and move toPurchasesUpdatedListener.
The following examples show how your app might look before and after making these changes:
Before
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
...
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
...
}
};
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
List<ResolveInfo> intentServices = mContext.getPackageManager()
.queryIntentServices(serviceIntent, 0);
if (intentServices != null && !intentServices.isEmpty()) {
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
} else {
// Handle errors.
...
}
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IabResult result;
if (requestCode != mRequestCode || data == null) {
// Handle errors.
...
}
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
if (resultCode != Activity.RESULT_OK || responseCode != BILLING_RESPONSE_RESULT_OK) {
// Handle errors.
...
}
// Process successful purchase.
...
return true;
}
After
Kotlin
class MyBillingImpl(private var billingClient: BillingClient) : PurchasesUpdatedListener {
init {
billingClient = BillingClient.newBuilder(activity).setListener(this).build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult?) {
// Logic from ServiceConnection.onServiceConnected should be moved here.
}
override fun onBillingServiceDisconnected() {
// Logic from ServiceConnection.onServiceDisconnected should be moved here.
}
})
}
override fun onPurchasesUpdated(
billingResult: BillingResult?,
purchases: MutableList<Purchase>?
) {
// Logic from onActivityResult should be moved here.
}
}
Java
public class MyBillingImpl implements PurchasesUpdatedListener {
private BillingClient billingClient;
...
public void initialize() {
billingClient = BillingClient.newBuilder(activity).setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
// Logic from ServiceConnection.onServiceConnected should be moved here.
}
@Override
public void onBillingServiceDisconnected() {
// Logic from ServiceConnection.onServiceDisconnected should be moved here.
}
});
}
@Override
public void onPurchasesUpdated(
@BillingResponse int responseCode, @Nullable List<Purchase> purchases) {
// Logic from onActivityResult should be moved here.
}
}
Making a purchase
To launch the purchase dialog, do the following:
- Convert your SKU details
BundletoSkuDetailsParams. - Switch the
mService.getSkuDetails()call to instead useBillingClient.querySkuDetailsAsync() - Convert your buy intent
Bundleto aBillingFlowParamsobject. - Switch the
mService.getBuyIntent()call to instead useBillingClient.launchBillingFlow(). - Remove any in-app purchase-related code from
onActivityResult(), and move this code to aPurchasesUpdatedListener.
The following examples show how your app might look before and after making these changes:
Before
// Query Skus
String skuToSell = "premium_upgrade";
ArrayList<String> skus = new Arraylist<>();
skus.add(skuToSell);
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skus);
Bundle skuDetails = mService.getSkuDetails(3,
mContext.getPackageName(),
itemType,
querySkus);
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
// Handle errors.
...
}
// Launch Buy Flow
Bundle buyIntentBundle = mService.getBuyIntent(3,
mContext.getPackageName(),
skuToSell,
"Inapp",
"");
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
// Handle errors.
...
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode,
new Intent(),
Integer.valueOf(0),
Integer.valueOf(0),
Integer.valueOf(0));
// Purchase is handled in onActivityResult illustrated in the previous section.
After
Kotlin
val skuToSell = "premium_upgrade"
val skuList = mutableListOf<String>()
skuList.add(skuToSell)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
billingClient.querySkuDetailsAsync(params.build(),
object : SkuDetailsResponseListener {
override fun onSkuDetailsResponse(
billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
// Process the result.
}
})
// SkuDetails object obtained above.
val skuDetails = ...
val purchaseParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(activity, purchaseParams)
// Purchase is handled in onPurchasesUpdated illustrated in the previous section
Java
String skuToSell = "premium_upgrade";
List<String> skuList = new ArrayList<> ();
skuList.add(skuToSell);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
// Process the result.
...
}
});
// SkuDetails object obtained above.
SkuDetails skuDetails = ...;
BillingFlowParams purchaseParams =
BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
mBillingClient.launchBillingFlow(mActivity, purchaseParams);
// Purchase is handled in onPurchasesUpdated illustrated in the previous section.
Consuming purchases
To consume purchases using the Google Play Billing Library, do the following:
- Instead of calling
consumePurchase(), callBillingClient.consumeAsync(). - Implement
ConsumeResponseListener.
The following examples show how your app might look before and after making these changes:
Before
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
int responseCode = data.getIntExtra(RESPONSE_CODE);
JSONObject purchaseData =
new JSONObject(data.getStringExtra("INAPP_PURCHASE_DATA"));
String token = purchaseData.get("purchaseToken");
...
// Consume purchase
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
if (response != BILLING_RESPONSE_RESULT_OK) {
// Handle errors.
...
}
// Handle successful consumption.
}
After
Kotlin
class MyBillingImpl(private val billingClient: BillingClient) :
... , ConsumeResponseListener {
fun consumePurchase(purchaseToken: String) {
val consumeParams = ConsumeParams
.newBuilder()
.setPurchaseToken(purchaseToken)
.build()
}
override fun onConsumeResponse(
billingResult: BillingResult?,
purchaseToken: String?) {
// Handle consumption
}
}
Java
public class MyBillingImpl implements ..., ConsumeResponseListener {
private BillingClient billingClient;
...
public void consumePurchase(String purchaseToken) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
}
@Override
void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
// Handle consumption.
...
}
}
Acknowledge purchases
Starting with version 2.0 of the Google Play Billing Library, your app must consume or acknowledge all purchases.
If you don't consume or acknowledge a purchase within three days, Google automatically revokes the purchase and refunds the user. For more information, see Acknowledge a purchase.
Recognizing out-of-app purchases
To migrate out-of-app purchase handling to the Google Play Billing Library, do the following:
- Ensure that your app calls
BillingClient.queryPurchases()in your app'sonResume()callback. - Remove the broadcast receiver for
com.android.vending.billing.PURCHASES_UPDATED, and move the corresponding callback code to yourPurchasesUpdatedListener.
Previously, when integrating Google Play Billing with AIDL, your app
would need to register a listener to receive the
com.android.vending.billing.PURCHASES_UPDATED intent to handle
purchases made outside of your app.
With the Google Play Billing Library, your app should always call
queryPurchases()
in the
onResume()
callback of your app as a first step to ensure that all purchases made while
your app wasn't running are recognized. While your app is running, the library
listens for out-of-app purchases automatically, notifying you through the
PurchasesUpdatedListener.
Handling pending transactions
Starting with version 2.0 of the Google Play Billing Library, your app must handle pending transactions that need additional action after purchasing before granting entitlement. For example, a user might choose to purchase your in-app product at a physical store using cash. This means that the transaction is completed outside of your app. In this scenario, you should grant entitlement only after the user has completed the transaction.
For more information, see Support pending transactions.
Developer payload
Developer payload has historically been used for various purposes, including fraud prevention and attributing purchases to the correct user. Now that the Google Play Billing Library supports these use cases, we have deprecated developer payload starting with version 2.2 of the Google Play Billing Library. For more information, see Developer payload.
Detailed error messages
Starting with version 2.0 of the Google Play Billing Library, all errors
have corresponding debugging-related messages. These messages can be obtained
by calling
BillingResult.getDebugMessage().

