How to identify the subscription duration (monthly or yearly) from a purchase in Google Play Billing Library?

PressRex profile image
by PressRex
How to identify the subscription duration (monthly or yearly) from a purchase in Google Play Billing Library?

I am using the Google Play Billing Library in my Kotlin app to allow users to purchase subscriptions. I have a single product with two subscription offers: one for a monthly duration and the other for a yearly duration.

After a user purchases a subscription, I can retrieve their purchase details using the BillingClient.queryPurchasesAsync() method, but it doesn't display the duration.

I want to determine whether the user selected the monthly or yearly subscription offer after the purchase has been made.

What I’ve Tried:

I queried the available ProductDetails using BillingClient.queryProductDetailsAsync() to retrieve the subscription offers (SubscriptionOfferDetails).

I attempted to compare the offerToken from the Purchase object with the offerToken in SubscriptionOfferDetails, but they are not the same.

How can I correctly determine the subscription duration (monthly or yearly) for a completed purchase when using the Google Play Billing Library? Is there a proper way to map the purchase details to the specific offer details?

Any guidance or examples would be greatly appreciated!

fun startBillingConnection() {
    billingClient.startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                Log.d(TAG, "Billing response OK")
                queryPurchases()
                queryProductDetails()
                _billingState.update { it.copy(isClientConnected = true) }
            } else {
                Log.e(TAG, billingResult.debugMessage)
                handleBillingError(billingResult, ErrorContext.CONNECTION)
                _billingState.update { it.copy(isClientConnected = false) }
            }
        }

        override fun onBillingServiceDisconnected() {
            Log.i(TAG, "Billing connection disconnected")
            _billingState.update { it.copy(isClientConnected = false) }
        }
    })
}

private fun querySubscriptionProducts() {
    val params = QueryProductDetailsParams.newBuilder()
    val productList = SUBS_PRODUCTS.map { productId ->
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId(productId)
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    }

    params.setProductList(productList).let { productDetailsParams ->
        billingClient.queryProductDetailsAsync(productDetailsParams.build(), this)
    }
}

override fun onProductDetailsResponse(
    billingResult: BillingResult,
    productDetailsList: MutableList<ProductDetails>
) {
    when (billingResult.responseCode) {
        BillingClient.BillingResponseCode.OK -> {
            if (productDetailsList.isEmpty()) {
                Log.e(TAG, "onProductDetailsResponse: Found null or empty ProductDetails.")
            } else {
                val newProducts = productDetailsList.associateBy { it.productId }
                _billingState.update {
                    it.copy(products = it.products + newProducts)
                }
            }
        }

        else -> {
            handleBillingError(billingResult, ErrorContext.PRODUCT_DETAILS)
        }
    }
}

Source: View source

PressRex profile image
by PressRex

Subscribe to New Posts

Lorem ultrices malesuada sapien amet pulvinar quis. Feugiat etiam ullamcorper pharetra vitae nibh enim vel.

Success! Now Check Your Email

To complete Subscribe, click the confirmation link in your inbox. If it doesn’t arrive within 3 minutes, check your spam folder.

Ok, Thanks

Read More