So I have been trying to migrate to Play Billing Library 6, and while I was able to successfully make my in-app purchases work, I’m unable to make my subscriptions work. Subscriptions specifically needs the setOfferToken parameter. My code below:
class AccessAllActivity: AppCompatActivity() {
private lateinit var binding: ActivityAccessAllBinding
private lateinit var billingClient: BillingClient
private lateinit var productDetails: ProductDetails
private val onBackPressedCallback: OnBackPressedCallback = object: OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
backPress()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
binding = ActivityAccessAllBinding.inflate(layoutInflater)
setContentView(binding.root)
billingClient = BillingClient.newBuilder(this)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object: BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Billing response OK")
queryPurchases()
queryAvailableProducts()
} else {
Log.e(TAG, billingResult.debugMessage)
}
}
override fun onBillingServiceDisconnected() {
Log.i(TAG, "Billing connection disconnected")
}
})
binding.btnClose.setOnClickListener {
backPress()
}
binding.btnSubscribeNow.setOnClickListener {
startSub()
}
}
fun queryPurchases() {
if (!billingClient.isReady) {
Log.e(TAG, "queryPurchases: BillingClient is not ready")
}
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build()
) { billingResult, _ ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
/*if (purchaseList.isNotEmpty()) {
//_purchases.value = purchaseList
} else {
//_purchases.value = emptyList()
}*/
} else {
Log.e(TAG, billingResult.debugMessage)
}
}
}
private fun queryAvailableProducts() {
val subProductList =
listOf(
Product.newBuilder()
.setProductId("subscription")
.setProductType(BillingClient.ProductType.SUBS)
.build(),
)
val subParams = QueryProductDetailsParams.newBuilder().setProductList(subProductList).build()
billingClient.queryProductDetailsAsync(subParams) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList.isNotEmpty()) {
val appSubProduct = productDetailsList.find { p-> p.productId == "subscription" }
if (appSubProduct != null) {
startSub()
}
}
}
}
private fun startSub() {
if (!productDetails.subscriptionOfferDetails.isNullOrEmpty()) {
val productDetailsParamsList =
listOf(
ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(productDetails.subscriptionOfferDetails!!.first().offerToken)
.build()
)
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
billingClient.launchBillingFlow(this, billingFlowParams)
} else {
println("Check productDetails, because is null")
}
}
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
Toast.makeText(this, string.payment_cancelled, Toast.LENGTH_LONG).show()
} else {
Toast.makeText(this, string.payment_error, Toast.LENGTH_LONG).show()
Log.i(TAG, "Billing service error ${billingResult.responseCode}")
}
}
private fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.consumeAsync(consumeParams) { _, _ ->
finish()
}
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
Toast.makeText(this, string.payment_error, Toast.LENGTH_LONG).show()
}
}
override fun onDestroy() {
super.onDestroy()
billingClient.endConnection()
}
private fun backPress() {
finish()
}
companion object {
private const val TAG = "ActivityAccessAll"
}
}
I tried the documentation and all answers here on StackOverflow, but I’m still unable to figure this out. Any help would be greatly appreciated.
Firtly call setUpBillingClient() after start activity/fragment and found your products. productDetails must be defined before click to button.
binding.btnSubscribeNow.setOnClickListener { startSub() }
private fun startSub() {
if (productDetails != null && !productDetails?.subscriptionOfferDetails.isNullOrEmpty()) {
val productDetailsParamsList =
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails!!)
.setOfferToken(productDetails!!.subscriptionOfferDetails!!.first().offerToken)
.build()
)
val billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
// Launch the billing flow
billingClient?.launchBillingFlow(requireActivity(), billingFlowParams)
} else {
println("Check productDetails, because is null")
}
}