Android in-app purchases: configuration and adding to the project
Updated: August 21, 2024
13 min read
This is the first article from the series dedicated to in-app purchases in Android apps. In this series, we will cover several topics starting with the creation of in-app purchases and leading up to server validation and analytics.
In this article, we’ll explain how to:
- Create a product in Google Play Console;
- Configure subscriptions: how to specify duration, price, trials;
- Add a list of products in an app.
What is an in-app purchase?
In-app purchases, especially subscriptions, are the most popular methods to monetize an app. On the one hand, a subscription allows a developer to invest into developing content and the product, on the other hand, it helps the users to get a more high-quality app. In general, in-app subscriptions can be split into several categories:
- Subscriptions:
- Auto-renewable subscriptions – ones that are prolonged via regular payments.
- Non-renewing subscriptions – ones that offer premium access for a limited period of time.
- Consumable in-apps – purchased once to be used once (usually used in games as in-game currency, power-ups, etc.)
- Non-consumable in-apps – purchased once to give permanent access to a certain feature (cosmetic items, locked characters, etc.)
In this series, we’ll focus on subscriptions mostly.
Android in-app purchase implementation process steps
- Android in-app purchases, part 1: configuration and adding to the project
- Android in-app purchases, part 2: processing purchases with the Google Play Billing Library.
- Android in-app purchases, part 3: retrieving active purchases and subscription change.
- Android in-app purchases, part 4: error codes from the Billing Library and testing.
- Android in-app purchases, part 5: server-side purchase validation.
The whole process of in-app purchase implementation is covered in 5 articles – though right now we’ll focus on part 1 only, make sure to check the rest as well. As you may have already guessed, 5 articles imply that the process of in-app purchase integration is rather complex, so if you don’t want to spend an eternity on coding, I’d recommend checking Adapty as a much quicker and easier way of implementing in-app subscriptions.
How to create a subscription for a Google Play app
Before we start, make sure you:
- Have a developer account for Google Play.
- Have signed all agreements and are ready to start working.
Now, let’s get down to business and create our first product.
Google Play Transaction Fees
It’s worth mentioning that In-app purchases are subject to 30% commission, but if the user has been subscribed for more than a year or an app earns less than $1М per year, the commission is 15%. Commission fees in this case is nothing peculiar, for example Apple charges the same fee for the iOS apps. It’s just the way huge corporations like Google, Apple, or Microsoft earn their money as businesses. So bear in mind that you won’t be getting 100% of the revenue from your in-app purchases.
Android subscription configuration
Switch to your developer account and choose the app.
Then, in the menu on the left, find the Products section, select Subscriptions and click Create a Subscription.
Then, we’ll see the subscription configurator. Here are some important points.
- Create the ID that will be used in the app. It’s a good idea to add a subscription period or some other useful information to ID, thus, you can create products in one style, and make analyzing the sales statistics easier.
- The name of the subscription that the user will see in the store.
- Subscription description. The user will see it, too.
Scroll down and choose the subscription period. In our case – it’s a week. Set up the price.
Usually, you set the price in the basic account currency, and the system converts the price automatically. But you can also edit the price for a specific country manually.
Please notice that Google shows the tax for every country. It’s great, App Store Connect doesn’t do so.
Scroll down and choose (if needed):
- Free trial period.
- Introductory price, which is an offer for the first payment periods.
- Grace period. If a user has payment issues, you can still provide them with premium access for some number of days.
- An opportunity to resubscribe from the Play Store, not from the app, after cancellation.
Comparing the purchase process in Play Console and App Store Connect
Regardless of the fact that subscriptions are monetized more effectively on iOS, Play Console’s admin board is more convenient, it’s organized and localized better, and it works faster.
The process of product creation is made as simple as possible. Here we told how to create products on iOS.
Getting a list of products in an Android app
Once the products are created, let’s work on the architecture for accepting and processing purchases. In general, the process looks like this:
- Add a Billing Library.
- Develop a class for interaction with products from Google Play.
- Implement all methods to process purchases.
- Add server validation of a purchase.
- Collect analytics.
In this part, let’s take a closer look at the first two points.
Adding Billing Library to a project:
implementation "com.android.billingclient:billing:4.0.0"
At the time of this writing, the latest version is 4.0.0. You can replace it with any other version at any moment.
Let’s create a wrapper class that will cover the logic of interaction with Google Play and initialize BillingClient from the Billing Library in it. Let’s call this class BillingClientWrapper.
This class will implement PurchasesUpdatedListener interface. We will override a method for it now – onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) – it’s needed right after a purchase is made, but we will describe the implementation process in the next article.
import android.content.Context
import com.android.billingclient.api.*
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
private val billingClient = BillingClient
.newBuilder(context)
.enablePendingPurchases()
.setListener(this)
.build()
override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
// here come callbacks about new purchases
}
}
Connections between BillingClient and Google Play
Google recommends to avoid having more than one active connection between BillingClient and Google Play to prevent a callback about a purchase made from being executed several times. Thus, you should have one unique BillingClient in a singleton class. The class in the example isn’t a singleton, but we can use dependency injection (for example, with the help of Dagger or Koin) in this way, allowing only one instance to exist at a single point in time.
To make any request with Billing Library, BillingClient must have an active connection with Google Play at the moment when the request is being made, but the connection may be lost at some moment. For the sake of convenience, let’s write a wrapper allowing us to make any requests only when the connection is active.
Requesting products
To get the products, we need their IDs that we set in the market. But it’s not enough for a request, we also need the product type (subscriptions or one-time purchases) that’s why we can get a general list of products by “combining” the results of two requests.
The request for products is asynchronous, so we need a callback that will either provide us with a list of products or return an error model. When an error occurs, Billing Library returns one of its BillingResponseCodes, as well as debugMessage. Let’s create callback interface and a model for an error:
interface OnQueryProductsListener {
fun onSuccess(products: List < SkuDetails > )
fun onFailure(error: Error)
}
class Error(val responseCode: Int, val debugMessage: String)
Here’s the code for a private method for getting data about a specific type of products and a public method that will “combine” the results of two requests and provide a user with the final list of products or display an error message.
fun queryProducts(listener: OnQueryProductsListener) {
val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")
queryProductsForType(
skusList,
BillingClient.SkuType.SUBS
) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val products = skuDetailsList ?: mutableListOf()
queryProductsForType(
skusList,
BillingClient.SkuType.INAPP
) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
products.addAll(skuDetailsList ?: listOf())
listener.onSuccess(products)
} else {
listener.onFailure(
Error(billingResult.responseCode, billingResult.debugMessage)
)
}
}
} else {
listener.onFailure(
Error(billingResult.responseCode, billingResult.debugMessage)
)
}
}
}
private fun queryProductsForType(
skusList: List<String>,
@BillingClient.SkuType type: String,
listener: SkuDetailsResponseListener
) {
onConnected {
billingClient.querySkuDetailsAsync(
SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
listener
)
}
}
Getting the list of SKProduct
Thus, we got valuable information about the products (SkuDetails) where we can see localized names, prices, product type, as well as billing period and information about introductory price and trial period (if it’s available for this user) for subscriptions. Here’s what the final class looks like:
import android.content.Context
import com.android.billingclient.api.*
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
interface OnQueryProductsListener {
fun onSuccess(products: List<SkuDetails>)
fun onFailure(error: Error)
}
class Error(val responseCode: Int, val debugMessage: String)
private val billingClient = BillingClient
.newBuilder(context)
.enablePendingPurchases()
.setListener(this)
.build()
fun queryProducts(listener: OnQueryProductsListener) {
val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")
queryProductsForType(
skusList,
BillingClient.SkuType.SUBS
) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val products = skuDetailsList ?: mutableListOf()
queryProductsForType(
skusList,
BillingClient.SkuType.INAPP
) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
products.addAll(skuDetailsList ?: listOf())
listener.onSuccess(products)
} else {
listener.onFailure(
Error(billingResult.responseCode, billingResult.debugMessage)
)
}
}
} else {
listener.onFailure(
Error(billingResult.responseCode, billingResult.debugMessage)
)
}
}
}
private fun queryProductsForType(
skusList: List<String>,
@BillingClient.SkuType type: String,
listener: SkuDetailsResponseListener
) {
onConnected {
billingClient.querySkuDetailsAsync(
SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
listener
)
}
}
private fun onConnected(block: () -> Unit) {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
block()
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
// here come callbacks about new purchases
}
}
That’s all for today. In the next articles, we’re going to tell you about purchase implementation, testing, and error handling.
If you are an Android app developer or a product owner looking to escalate your conversion rates and in-app subscriptions, look no further! 🚀 Schedule a free demo call with us today! We’ll guide you through integrating Adapty SDK, an essential tool to maximize your Android in-app subscription revenue swiftly and efficiently. 💰 Don’t miss the opportunity to redefine your app’s success and profitability with Adapty!