This topic describes how to migrate away from a billing integration that uses the Android Interface Definition Language (AIDL). Accessing Google Play's billing system 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 { def billing_version = "3.0.0" implementation "com.android.billingclient:billing:$billing_version" }
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
BillingClientStateListener
to receive callbacks about service status. - Call
startConnection()
on yourBillingClient
instance. - 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
Bundle
toSkuDetailsParams
. - Switch the
mService.getSkuDetails()
call to instead useBillingClient.querySkuDetailsAsync()
- Convert your buy intent
Bundle
to aBillingFlowParams
object. - 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()
.