Best Android In-App Billing Libraries and Examples
There are various ways to monetize an android mobile app. One is by display advertisements. One is by making the app a premium app so that users pay some money before installing the app. Another way is by making parts of your free app only accessible after a user makes a payment. The last option is the most robust way but also does require some work.
In this article we want to look at several easy to use libraries to easen the integration of Google In App Purchase.
(a). Google IAP
Android Library for easing Google Play Billing to your apps with support for Subscriptions, In App Purchases and Consumables.
Here are it's features:
- Written in Kotlin
- No boilerplate code
- Easy initialization
- Supports InApp & Subscription products
- Simple configuration for consumable products
Here's a demo:
Step 1: Install Google IAP
The library is hosted in jitpack so start by adding jitpack in your root level build.gradle:
Then in your app level build.gradle under the dependencies closure, add the following implementation statement:
Step 2: Write Code
You start by establishing connection to Google Pay:
val iapConnector = IapConnector(
context = this, // activity / context
nonConsumableKeys = nonConsumablesList, // pass the list of non-consumables
consumableKeys = consumablesList, // pass the list of consumables
subscriptionKeys = subsList, // pass the list of subscriptions
key = "LICENSE KEY" // pass your app's license key
enableLogging = true // to enable / disable logging
)
Here's how you make a purchase:
And here's how you make a subscription:
You can remove the subscription using the following method:
In all these cases the SKU is the product id. You will typically get it after creating a product in your google play store account:
Then you handle the events, whether you are making a purchase or a subscription:
iapConnector.addPurchaseListener(object : PurchaseServiceListener {
override fun onPricesUpdated(iapKeyPrices: Map<String, String>) {
// list of available products will be received here, so you can update UI with prices if needed
}
override fun onProductPurchased(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered whenever purchase succeeded
}
override fun onProductRestored(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered fetching owned products using IapConnector
}
})
iapConnector.addSubscriptionListener(object : SubscriptionServiceListener {
override fun onSubscriptionRestored(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered upon fetching owned subscription upon initialization
}
override fun onSubscriptionPurchased(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered whenever subscription succeeded
}
override fun onPricesUpdated(iapKeyPrices: Map<String, String>) {
// list of available products will be received here, so you can update UI with prices if needed
}
})
Examples
Let's look at two examples one written in kotlin and the other in java:
package com.limerse.iapsample
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.limerse.iap.DataWrappers
import com.limerse.iap.IapConnector
import com.limerse.iap.PurchaseServiceListener
import com.limerse.iap.SubscriptionServiceListener
import com.limerse.iapsample.databinding.ActivityMainBinding
class KotlinSampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.bottomnavview.itemIconTintList = null
val nonConsumablesList = listOf("lifetime")
val consumablesList = listOf("base", "moderate", "quite", "plenty", "yearly")
val subsList = listOf("subscription")
val iapConnector = IapConnector(
context = this,
nonConsumableKeys = nonConsumablesList,
consumableKeys = consumablesList,
subscriptionKeys = subsList,
key = "LICENSE KEY",
enableLogging = true
)
iapConnector.addPurchaseListener(object : PurchaseServiceListener {
override fun onPricesUpdated(iapKeyPrices: Map<String, String>) {
// list of available products will be received here, so you can update UI with prices if needed
}
override fun onProductPurchased(purchaseInfo: DataWrappers.PurchaseInfo) {
when (purchaseInfo.sku) {
"base" -> {
}
"moderate" -> {
}
"quite" -> {
}
"plenty" -> {
}
"yearly" -> {
}
}
}
override fun onProductRestored(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered fetching owned products using IapConnector;
}
})
iapConnector.addSubscriptionListener(object : SubscriptionServiceListener {
override fun onSubscriptionRestored(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered upon fetching owned subscription upon initialization
}
override fun onSubscriptionPurchased(purchaseInfo: DataWrappers.PurchaseInfo) {
// will be triggered whenever subscription succeeded
when(purchaseInfo.sku){
"subscription"->{
}
}
}
override fun onPricesUpdated(iapKeyPrices: Map<String, String>) {
// list of available products will be received here, so you can update UI with prices if needed
}
})
binding.btPurchaseCons.setOnClickListener {
iapConnector.purchase(this, "base")
}
binding.btnMonthly.setOnClickListener {
iapConnector.subscribe(this, "subscription")
}
binding.btnYearly.setOnClickListener {
iapConnector.purchase(this, "yearly")
}
binding.btnQuite.setOnClickListener {
iapConnector.purchase(this, "quite")
}
binding.btnModerate.setOnClickListener {
iapConnector.purchase(this, "moderate")
}
binding.btnUltimate.setOnClickListener {
iapConnector.purchase(this, "plenty")
}
}
}
As for the Java example:
package com.limerse.iapsample;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.limerse.iap.DataWrappers;
import com.limerse.iap.IapConnector;
import com.limerse.iap.PurchaseServiceListener;
import com.limerse.iap.SubscriptionServiceListener;
import com.limerse.iapsample.databinding.ActivityMainBinding;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
class JavaSampleActivity extends AppCompatActivity {
private ActivityMainBinding binding;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
List<String> nonConsumablesList = Collections.singletonList("lifetime");
List<String> consumablesList = Arrays.asList("base", "moderate", "quite", "plenty", "yearly");
List<String> subsList = Collections.singletonList("subscription");
IapConnector iapConnector = new IapConnector(
this,
nonConsumablesList,
consumablesList,
subsList,
"LICENSE KEY",
true
);
iapConnector.addPurchaseListener(new PurchaseServiceListener() {
public void onPricesUpdated(@NotNull Map iapKeyPrices) {
}
public void onProductPurchased(DataWrappers.PurchaseInfo purchaseInfo) {
if (purchaseInfo.getSku().equals("plenty")) {
}
else if (purchaseInfo.getSku().equals("yearly")) {
}
else if (purchaseInfo.getSku().equals("moderate")) {
}
else if (purchaseInfo.getSku().equals("base")) {
}
else if (purchaseInfo.getSku().equals("quite")) {
}
}
public void onProductRestored(DataWrappers.PurchaseInfo purchaseInfo) {
}
});
iapConnector.addSubscriptionListener(new SubscriptionServiceListener() {
public void onSubscriptionRestored(DataWrappers.PurchaseInfo purchaseInfo) {
}
public void onSubscriptionPurchased(DataWrappers.PurchaseInfo purchaseInfo) {
if (purchaseInfo.getSku().equals("subscription")) {
}
}
public void onPricesUpdated(@NotNull Map iapKeyPrices) {
}
});
binding.btPurchaseCons.setOnClickListener(it ->
iapConnector.purchase(this, "base")
);
binding.btnMonthly.setOnClickListener(it ->
iapConnector.subscribe(this, "subscription")
);
binding.btnYearly.setOnClickListener(it ->
iapConnector.purchase(this, "yearly")
);
binding.btnQuite.setOnClickListener(it ->
iapConnector.purchase(this, "quite")
);
binding.btnModerate.setOnClickListener(it ->
iapConnector.purchase(this, "moderate")
);
binding.btnUltimate.setOnClickListener(it ->
iapConnector.purchase(this, "plenty")
);
}
}
Then here's the layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
android:background="@color/light_black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:menu="@menu/top"
app:navigationIcon="@drawable/ic_wealth"
app:title="@string/app_name"
app:titleTextColor="@color/white" />
<androidx.gridlayout.widget.GridLayout
android:id="@+id/gridView"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="10dp"
android:paddingLeft="8dp"
android:paddingTop="8dp"
android:paddingRight="8dp"
app:alignmentMode="alignMargins"
app:columnCount="3"
app:layout_constraintBottom_toTopOf="@id/bottomnavview"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:rowCount="3">
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
app:cardBackgroundColor="@color/cardBackground"
app:cardCornerRadius="8dp"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_row="0"
app:layout_rowWeight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/basic"
android:textColor="@color/yellow"
android:textSize="@dimen/_16sdp" />
<ImageView
android:id="@+id/btPurchaseCons"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_money" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
app:cardBackgroundColor="@color/cardBackground"
app:cardCornerRadius="8dp"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_row="0"
app:layout_rowWeight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/moderate"
android:textColor="@color/yellow"
android:textSize="@dimen/_16sdp" />
<ImageView
android:id="@+id/btnModerate"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_earn" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:cardBackgroundColor="@color/cardBackground"
app:cardCornerRadius="8dp"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_row="1"
app:layout_rowWeight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/quite"
android:textColor="@color/yellow"
android:textSize="@dimen/_16sdp" />
<ImageView
android:id="@+id/btnQuite"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_rich_man" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/cardBackground"
app:cardCornerRadius="8dp"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_row="1"
app:layout_rowWeight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/ultimate"
android:textColor="@color/yellow"
android:textSize="@dimen/_16sdp" />
<ImageView
android:id="@+id/btnUltimate"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_money_bag" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:cardBackgroundColor="@color/cardBackground"
app:cardCornerRadius="8dp"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_row="2"
app:layout_rowWeight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/monthly"
android:textColor="@color/light_blue"
android:textSize="@dimen/_16sdp" />
<ImageView
android:id="@+id/btnMonthly"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_bonus" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/cardBackground"
app:cardCornerRadius="8dp"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_row="2"
app:layout_rowWeight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/yearly"
android:textColor="@color/light_blue"
android:textSize="@dimen/_16sdp" />
<ImageView
android:id="@+id/btnYearly"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_gift_box" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.gridlayout.widget.GridLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomnavview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/light_black"
app:itemTextColor="@color/white"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom" />
</androidx.constraintlayout.widget.ConstraintLayout>
You will need to get do the following first:
- Add your products to the developer console
- Replace the key with your App's License Key
Then Run it and you will get the following:
Reference
Find complete reference here.
(b). Android Checkout
Library for Android In-App Billing (Version 3+).
Checkout is an implementation of Android In-App Billing API (v3+). Its main goal is to make integration of in-app products as simple and straightforward as possible: developers should not spend much time on implementing boring In-App Billing API but should focus on more important things - their apps. With this in mind, the library was designed to be fast, flexible and secure.
Checkout solves common problems that developers face while working with billing on Android, such as:
- How to cancel all billing requests when Activity is destroyed?
- How to query purchase information in the background? See also Querying for Items Available for Purchase
- How to verify a purchase? See also Security And Design
- How to load all the purchases using
continuationToken
or SKU details (one request is limited by 20 items)? - How to add billing with a minimum amount of boilerplate code?
Step 1: Install Android Checkout
The libraru is hosted in jcenter so start by adding the following implementation statement in your app level build.gardle file:
Step 2: Write Code
Say there is an app that contains one in-app product with "sku_01" id.
Create Application class
Then Application class might look like this:
public class MyApplication extends Application {
private static MyApplication sInstance;
private final Billing mBilling = new Billing(this, new Billing.DefaultConfiguration() {
@Override
public String getPublicKey() {
return "Your public key, don't forget about encryption";
}
});
public MyApplication() {
sInstance = this;
}
public static MyApplication get() {
return sInstance;
}
public Billing getBilling() {
return mBilling;
}
}
Create Activity
public class MyActivity extends Activity implements View.OnClickListener {
private class PurchaseListener extends EmptyRequestListener<Purchase> {
@Override
public void onSuccess(Purchase purchase) {
// here you can process the loaded purchase
}
@Override
public void onError(int response, Exception e) {
// handle errors here
}
}
private class InventoryCallback implements Inventory.Callback {
@Override
public void onLoaded(Inventory.Products products) {
// your code here
}
}
private final ActivityCheckout mCheckout = Checkout.forActivity(this, MyApplication.get().getBilling());
private Inventory mInventory;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCheckout.start();
mCheckout.createPurchaseFlow(new PurchaseListener());
mInventory = mCheckout.makeInventory();
mInventory.load(Inventory.Request.create()
.loadAllPurchases()
.loadSkus(ProductTypes.IN_APP, "sku_01"), new InventoryCallback());
}
@Override
protected void onDestroy() {
mCheckout.stop();
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mCheckout.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onClick(View v) {
mCheckout.whenReady(new Checkout.EmptyListener() {
@Override
public void onReady(BillingRequests requests) {
requests.purchase(ProductTypes.IN_APP, "sku_01", null, mCheckout.getPurchaseFlow());
}
});
}
}
Examples
Find example usages of Checkout here.
More
Find complete rerefence here.
(c). Android-InApp-Billing-v3
This is a lightweight implementation of Android In-app Billing Version 3.
This is a simple, straight-forward implementation of the Android v3 In-app billing API. It supports: In-App Product Purchases (both non-consumable and consumable) and Subscriptions.
Step 1: Install it
Install the library from mavenCentral:
repositories {
mavenCentral()
}
dependencies {
implementation 'com.anjlab.android.iab.v3:library:1.0.44'
}
Step 2: Add Permission
You need to add billing permission in your android manifest:
Step 3: Write Code
Create instance of BillingProcessor class and implement callback in your Activity source code. Constructor will take 3 parameters:
- Context
- Your License Key from Google Developer console. This will be used to verify purchase signatures. You can pass NULL if you would like to skip this check (You can find your key in Google Play Console -> Your App Name -> Services & APIs)
- IBillingHandler Interface implementation to handle purchase results and errors (see below)
public class SomeActivity extends Activity implements BillingProcessor.IBillingHandler {
BillingProcessor bp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bp = new BillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE", this);
bp.initialize();
// or bp = BillingProcessor.newBillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE", this);
// See below on why this is a useful alternative
}
// IBillingHandler implementation
@Override
public void onBillingInitialized() {
/*
* Called when BillingProcessor was initialized and it's ready to purchase
*/
}
@Override
public void onProductPurchased(String productId, TransactionDetails details) {
/*
* Called when requested PRODUCT ID was successfully purchased
*/
}
@Override
public void onBillingError(int errorCode, Throwable error) {
/*
* Called when some error occurred. See Constants class for more details
*
* Note - this includes handling the case where the user canceled the buy dialog:
* errorCode = Constants.BILLING_RESPONSE_RESULT_USER_CANCELED
*/
}
@Override
public void onPurchaseHistoryRestored() {
/*
* Called when purchase history was restored and the list of all owned PRODUCT ID's
* was loaded from Google Play
*/
}
}
override Activity's onActivityResult
method:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (!bp.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
Then Call purchase
method for a BillingProcessor instance to initiate purchase or subscribe
to initiate a subscription:
Without a developer payload:
bp.purchase(YOUR_ACTIVITY, "YOUR PRODUCT ID FROM GOOGLE PLAY CONSOLE HERE");
bp.subscribe(YOUR_ACTIVITY, "YOUR SUBSCRIPTION ID FROM GOOGLE PLAY CONSOLE HERE");
With a developer payload:
bp.purchase(YOUR_ACTIVITY, "YOUR PRODUCT ID FROM GOOGLE PLAY CONSOLE HERE", "DEVELOPER PAYLOAD HERE");
bp.subscribe(YOUR_ACTIVITY, "YOUR SUBSCRIPTION ID FROM GOOGLE PLAY CONSOLE HERE", "DEVELOPER PAYLOAD HERE");
IMPORTANT: when you provide a payload, internally the library prepends a string to your
Finally release you BillingProcessor:
Reference
Find more below:
Number | Link |
---|---|
1. | Download Example |
2. | Read more |
(d). pay-me
Android library for handling In-App-Billing V3.
An Android library for handling In-App-Billing V3 (IABv3), based on Google's marketbilling sample code. The goal of this project is to build a reliable and tested library which can easily be included as an apklib in your (Maven based) projects.
Google's sample code has been refactored and made testable - at the moment there are over 100 unit tests covering most of the code base. It is currently used in the 1.5.x version of SMS Backup+.
Step 1: Install
Install the apklib to your local maven repository (it has not been published yet).
Step 2: Write Code
@Override public void onCreate(Bundle bundle) {
mIabHelper = new IabHelper(this, "Base64EncodedPublicKey");
mIabHelper.startSetup(new OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (result.isSuccess()) {
// helper is ready to use
mIabHelper.launchPurchaseFlow(this,
"android.test.purchased",
ItemType.IN_APP,
0,
new OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
// handle purchase result
}
}, null
);
}
}
});
}
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mIabHelper.handleActivityResult(requestCode, resultCode, data);
}
Reference
Find more below:
Number | Link |
---|---|
1. | Download Example |
2. | Read more |
(e). google-inapp-billing
A simple implementation of the Android In-App Billing API.
It supports: in-app purchases (both consumable and non-consumable) and subscriptions.
Here is a demo:
This library requires API 16+.
Step 1: Install it
Add the JitPack repository to your project's build.gradle file:
Step 2: Build Setup
Important Notice
- For builds that use
minSdkVersion
lower than24
it is very important to include the following in your app's build.gradle file:
android {
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
}
- For builds that use
minSdkVersion
lower than21
add the above and also this:
This step is required to enable support for some APIs on lower SDK versions that aren't available natively only starting from minSdkVersion 24
.
Step 3: Add Permission
Add Billing permission in your android manifest:
Add the dependency in your app's build.gradle file:
Step 4: Write code
Instantiate BillingConnector class, passing in the Context as well as the License:
billingConnector = new BillingConnector(this, "license_key")
.setConsumableIds(consumableIds)
.setNonConsumableIds(nonConsumableIds)
.setSubscriptionIds(subscriptionIds)
.autoAcknowledge()
.autoConsume()
.enableLogging()
.connect();
- Implement the listener to handle event results and errors:
billingConnector.setBillingEventListener(new BillingEventListener() {
@Override
public void onProductsFetched(@NonNull List<SkuInfo> skuDetails) {
/*Provides a list with fetched products*/
}
@Override
public void onPurchasedProductsFetched(@NonNull List<PurchaseInfo> purchases) {
/*Provides a list with fetched purchased products*/
}
@Override
public void onProductsPurchased(@NonNull List<PurchaseInfo> purchases) {
/*Callback after a product is purchased*/
}
@Override
public void onPurchaseAcknowledged(@NonNull PurchaseInfo purchase) {
/*Callback after a purchase is acknowledged*/
/*
* Grant user entitlement for NON-CONSUMABLE products and SUBSCRIPTIONS here
*
* Even though onProductsPurchased is triggered when a purchase is successfully made
* there might be a problem along the way with the payment and the purchase won't be acknowledged
*
* Google will refund users purchases that aren't acknowledged in 3 days
*
* To ensure that all valid purchases are acknowledged the library will automatically
* check and acknowledge all unacknowledged products at the startup
* */
}
@Override
public void onPurchaseConsumed(@NonNull PurchaseInfo purchase) {
/*Callback after a purchase is consumed*/
/*
* CONSUMABLE products entitlement can be granted either here or in onProductsPurchased
* */
}
@Override
public void onBillingError(@NonNull BillingConnector billingConnector, @NonNull BillingResponse response) {
/*Callback after an error occurs*/
switch (response.getErrorType()) {
case CLIENT_NOT_READY:
//TODO - client is not ready yet
break;
case CLIENT_DISCONNECTED:
//TODO - client has disconnected
break;
case SKU_NOT_EXIST:
//TODO - sku does not exist
break;
case CONSUME_ERROR:
//TODO - error during consumption
break;
case ACKNOWLEDGE_ERROR:
//TODO - error during acknowledgment
break;
case ACKNOWLEDGE_WARNING:
/*
* This will be triggered when a purchase can not be acknowledged because the state is PENDING
* A purchase can be acknowledged only when the state is PURCHASED
*
* PENDING transactions usually occur when users choose cash as their form of payment
*
* Here users can be informed that it may take a while until the purchase complete
* and to come back later to receive their purchase
* */
//TODO - warning during acknowledgment
break;
case FETCH_PURCHASED_PRODUCTS_ERROR:
//TODO - error occurred while querying purchased products
break;
case BILLING_ERROR:
//TODO - error occurred during initialization / querying sku details
break;
case USER_CANCELED:
//TODO - user pressed back or canceled a dialog
break;
case SERVICE_UNAVAILABLE:
//TODO - network connection is down
break;
case BILLING_UNAVAILABLE:
//TODO - billing API version is not supported for the type requested
break;
case ITEM_UNAVAILABLE:
//TODO - requested product is not available for purchase
break;
case DEVELOPER_ERROR:
//TODO - invalid arguments provided to the API
break;
case ERROR:
//TODO - fatal error during the API action
break;
case ITEM_ALREADY_OWNED:
//TODO - failure to purchase since item is already owned
break;
case ITEM_NOT_OWNED:
//TODO - failure to consume since item is not owned
break;
}
}
});
Then purchase or subscribe:
- Purchase a non-consumable/consumable product:
- Purchase a subscription:
- Cancel a subscription:
Reference
Find more below:
Number | Link |
---|---|
1. | Download Example |
2. | Read more |
(f). Billing-Suspend
Suspendable Helper for access BillingClient.
It also includes a client-side token validation, if there is not server available.
It features:
- Suspendable Billing Access
- Client-Side Token Validation
Here is how you use it:
Step 1: Install it
Start by installing it using the following implementation statement:
Step 2: Handle Purchase
Here's a code sample:
val billingHelper: BillingHelper = BillingHelper(activity) {
enablePendingPurchases()
}
val purchases = billingHelper.queryPurchases(BillingClient.SkuType.SUBS)
purchases?.forEach {
if (it.isAcknowledged.not()) {
billingHelper.acknowledgePurchase(it.purchaseToken)
}
}
billingHelper.purchase(sku = arrayOf("productId_here"), type = BillingClient.SkuType.SUBS)
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Read more |
2. | Follow code author |
3. | Code: Apache 2.0 License |