inappbilling is a Google Play Billing library that demonstrates how to implement in-app purchases and subscriptions in your Android application
In your project-level build.gradle or settings.gradle file, add the JitPack repository:
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
}
In your app-level build.gradle file, add the library dependency. Use the latest version:
implementation 'com.github.hypersoftdev:inappbilling:x.x.x'
Initialize the BillingManager with the application context
:
private val billingManager by lazy { BillingManager(context) }
Retrieve a debugging ID for testing and ensure the purchaseDetailList
parameter contains all active purchases and their details:
val subsProductIdList = listOf("subs_product_id_1", "subs_product_id_2", "subs_product_id_3")
val productInAppConsumable = when (BuildConfig.DEBUG) {
true -> listOf("product_id_1")
false -> listOf("product_id_1", "product_id_2")
}
val productInAppNonConsumable = when (BuildConfig.DEBUG) {
true -> listOf(billingManager.getDebugProductIDList())
false -> listOf("product_id_1", "product_id_2")
}
billingManager.initialize(
productInAppConsumable = productInAppConsumable,
productInAppNonConsumable = productInAppNonConsumable,
productSubscriptions = subsProductIdList,
billingListener = object : BillingListener {
override fun onConnectionResult(isSuccess: Boolean, message: String) {
Log.d("BillingTAG", "Billing: initBilling: onConnectionResult: isSuccess = $isSuccess - message = $message")
}
override fun purchasesResult(purchaseDetailList: List<PurchaseDetail>) {
if (purchaseDetailList.isEmpty()) {
// No purchase found, reset all sharedPreferences (premium properties)
}
purchaseDetailList.forEachIndexed { index, purchaseDetail ->
Log.d("BillingTAG", "Billing: initBilling: purchasesResult: $index) $purchaseDetail ")
}
}
}
)
Access comprehensive details of the currently purchased item using the PurchaseDetail
class:
/**
productId: Product Id for both inapp/subs (e.g. product_ads/product_weekly_ads)
planId: Plan Id for subs (e.g. plan_weekly_ads)
productTitle: Title of the Product
planTitle: Title of the Plan
productType: Product purchase type (e.g. InApp/Subs)
purchaseToken: a unique token for this purchase
purchaseTime: For subscriptions, this is the subscription signup time. It won't change after renewal.
purchaseTimeMillis: UnixTimeStamp (starts from Jan 1, 1970)
isAutoRenewing: Only in case of 'BillingClient.ProductType.SUBS'
*/
data class PurchaseDetail(
val productId: String,
var planId: String,
var productTitle: String,
var planTitle: String,
val purchaseToken: String,
val productType: ProductType,
val purchaseTime: String,
val purchaseTimeMillis: Long,
val isAutoRenewing: Boolean,
)
Monitor all active in-app and subscription products:
val subsProductIdList = listOf("subs_product_id_1", "subs_product_id_2", "subs_product_id_3")
val subsPlanIdList = listOf("subs-plan-id-1", "subs-plan-id-2", "subs-plan-id-3")
billingManager.productDetailsLiveData.observe(viewLifecycleOwner) { productDetailList ->
Log.d("BillingTAG", "Billing: initObservers: $productDetailList")
productDetailList.forEach { productDetail ->
if (productDetail.productType == ProductType.inapp) {
when (productDetail.productId) {
"inapp_product_id_1" -> { /* Handle in-app product 1 */ }
"inapp_product_id_2" -> { /* Handle in-app product 2 */ }
}
} else {
when (productDetail.productId) {
"subs_product_id_1" -> if (productDetail.planId == "subs-plan-id-1") {
val freeTrial = productDetail.pricingDetails.find { it.recurringMode == RecurringMode.FREE }
val discounted = productDetail.pricingDetails.find { it.recurringMode == RecurringMode.DISCOUNTED }
val original = productDetail.pricingDetails.find { it.recurringMode == RecurringMode.ORIGINAL }
}
"subs_product_id_2" -> if (productDetail.planId == "subs-plan-id-2") { /* Handle plan2 subscription */ }
"subs_product_id_3" -> if (productDetail.planId == "subs-plan-id-3") { /* Handle plan3 subscription */ }
}
}
}
}
Retrieve comprehensive details of the item using the ProductDetail
class:
@param productId: Unique ID (Console's ID) for product
@param planId: Unique ID (Console's ID) for plan
@param productTitle: e.g. Gold Tier
@param productType: e.g. InApp / Subs
@param pricingDetails: e.g. list of pricing containing price, currencyCode, planTitle, billingCycleCount. etc
- Weekly: P1W (One week)
- Every 4 weeks: P4W (Four weeks)
- Monthly: P1M (One month)
- Every 2 months (Bimonthly): P2M (Two months)
- Every 3 months (Quarterly): P3M (Three months)
- Every 4 months: P4M (Four months)
- Every 6 months (Semiannually): P6M (Six months)
- Every 8 months: P8M (Eight months)
- Yearly: P1Y (One year)
data class ProductDetail(
var productId: String,
var planId: String,
var productTitle: String,
var productType: ProductType,
var pricingDetails: List<PricingPhase>
) {
constructor() : this(
productId = "",
planId = "",
productTitle = "",
productType = ProductType.subs,
pricingDetails = listOf(),
)
}
@param recurringMode: e.g. FREE, DISCOUNTED, ORIGINAL
@param price: e.g. Rs 750.00
@param currencyCode: e.g. USD, PKR, etc
@param planTitle: e.g. Weekly, Monthly, Yearly, etc
@param billingCycleCount: e.g. 1, 2, 3, etc
@param billingPeriod: e.g. P1W, P1M, P1Y, etc
@param priceAmountMicros: e.g. 750000000
@param freeTrialPeriod: e.g. 3, 5, 7, etc
data class PricingPhase(
var recurringMode: RecurringMode,
var price: String,
var currencyCode: String,
var planTitle: String,
var billingCycleCount: Int,
var billingPeriod: String,
var priceAmountMicros: Long,
var freeTrialPeriod: Int,
) {
constructor() : this(
recurringMode = RecurringMode.ORIGINAL,
price = "",
currencyCode = "",
planTitle = "",
billingCycleCount = 0,
billingPeriod = "",
priceAmountMicros = 0,
freeTrialPeriod = 0,
)
}
billingManager.makeInAppPurchase(activity, productId, object : OnPurchaseListener {
override fun onPurchaseResult(isPurchaseSuccess: Boolean, message: String) {
Log.d("BillingTAG", "makeInAppPurchase: $isPurchaseSuccess - $message")
}
})
billingManager.makeSubPurchase(activity,
productId = "subs_product_id_1,
planId = "subs-plan-id-1,
object : OnPurchaseListener {
override fun onPurchaseResult(isPurchaseSuccess: Boolean, message: String) {
Log.d("BillingTAG", "makeSubPurchase: $isPurchaseSuccess - $message")
}
})
billingManager.updateSubPurchase(
activity,
oldProductId = "subs_product_id_1",
oldPlanId = "subs_plan_id_1",
productId = "subs_product_id_2",
planId = "subs_plan_id_2",
object : OnPurchaseListener {
override fun onPurchaseResult(isPurchaseSuccess: Boolean, message: String) {
Log.d("BillingTAG", "updateSubPurchase: $isPurchaseSuccess - $message")
}
}
)
billingManager.updateSubPurchase(
activity,
oldProductId = "mOldProductID",
productId = "New Product ID",
planId = "New Plan ID",
object : OnPurchaseListener {
override fun onPurchaseResult(isPurchaseSuccess: Boolean, message: String) {
Log.d("BillingTAG", "updateSubPurchase: $isPurchaseSuccess - $message")
}
}
)
To add products and plans on the Play Console, consider using the following recommended subscription tags to generate plans.
Product ID: product_id_weekly
- Plan ID: plan-id-weekly
Product ID: product_id_monthly
- Plan ID: plan-id-monthly
Product ID: product_id_yearly
- Plan ID: plan-id-yearly
If you purchase a product and want to retrieve an old purchase from Google, it won't return the plan ID, making it impossible to identify which plan was purchased. To address this, you should save the purchase information on your server, including the product and plan IDs. This way, you can maintain a purchase list for future reference. Alternatively, you can use Option 1
, where each product ID is associated with only one plan ID. This ensures that when you fetch a product ID, you can easily determine the corresponding plan that was purchased
For Gold Subscription
Product ID: gold_product
- Plan ID: gold-plan-weekly
- Plan ID: gold-plan-monthly
- Plan ID: gold-plan-yearly
and so on...
Fixed billing periods for subscriptions:
- Weekly
- Every 4 weeks
- Monthly
- Every 2 months (Bimonthly)
- Every 3 months (Quarterly)
- Every 4 months
- Every 6 months (Semiannually)
- Every 8 months
- Yearly
Tip
Note: Use the BillingManager tag to observe the states
Copyright 2023 Hypersoft Inc
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.