I have a fragment with a button that has clicklistener on it to start the purchaseflow the Fragment is
class SubscriptionFragment: Fragment(), View.OnClickListener, PurchasesUpdatedListener {
private var _binding: FragmentSubscriptionBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentSubscriptionBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val btn: Button = view.findViewById(R.id.subbtn)
btn.setOnClickListener {
subscription?.subscribeProduct()
}
This is the subscription activity
class Subscription : AppCompatActivity(), PurchasesUpdatedListener {
private lateinit var itemArrayList: ArrayList<SubscriptionItem>
var isSuccess = false
var productId = 0
private var billingClient: BillingClient? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
billingClient = BillingClient.newBuilder(this)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
show_list()
}
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.ITEM_ALREADY_OWNED) {
Toast.makeText(this, "Already Subscribed", Toast.LENGTH_SHORT).show()
isSuccess = true
}
else {
Toast.makeText(this, "ERROR" + billingResult.debugMessage, Toast.LENGTH_SHORT).show()
}
}
var acknowledgePurchaseResponseListener = AcknowledgePurchaseResponseListener { billingResult ->
if(billingResult.responseCode == BillingClient.BillingResponseCode.OK){
isSuccess = true
}
}
fun handlePurchase(purchase: Purchase){
val consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build()
val listener = ConsumeResponseListener{billingResult, s ->
if(billingResult.responseCode == BillingClient.BillingResponseCode.OK){
}
}
billingClient!!.consumeAsync(consumeParams, listener)
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
Toast.makeText(this, "Invalid Purchase", Toast.LENGTH_SHORT).show()
return
}
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient!!.acknowledgePurchase(
acknowledgePurchaseParams,
acknowledgePurchaseResponseListener
)
isSuccess = true
} else {
Toast.makeText(this, "Already Subscribed", Toast.LENGTH_SHORT).show()
}
}
else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
Toast.makeText(this, "PENDING", Toast.LENGTH_SHORT).show()
}
else if (purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
Toast.makeText(this, "UNSPECIFIED_STATE", Toast.LENGTH_SHORT).show()
}
}
private fun verifyValidSignature(signedData: String, signature: String): Boolean {
return try {
val security = Security()
val base64Key="abc"
security.verifyPurchase(base64Key, signedData, signature)
}
catch (e: IOException){
false
}
}
fun show_list() {
billingClient?.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
val executorService = Executors.newSingleThreadExecutor()
val productList = listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("basicmonthly")
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
val params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
billingClient!!.queryProductDetailsAsync(params.build()){
billingResult, productDetailsList ->
for(productDetails in productDetailsList){
if (productDetails.subscriptionOfferDetails != null){
for (i in 0 until productDetails.subscriptionOfferDetails!!.size){
var PlanName: String = productDetails.name
var PlanIndex: Int = i
var phases = ""
var PlanPrice: String = productDetails.subscriptionOfferDetails?.get(i)?.pricingPhases?.pricingPhaseList?.get(0)?.formattedPrice.toString()
var billingPeriod: String = productDetails.subscriptionOfferDetails?.get(i)?.pricingPhases?.pricingPhaseList?.get(0)?.billingPeriod.toString()
var reccurenceMode: String = productDetails.subscriptionOfferDetails?.get(i)?.pricingPhases?.pricingPhaseList?.get(0)?.recurrenceMode.toString()
if(reccurenceMode == "2"){
when(billingPeriod){
"P1W" -> billingPeriod= "For 1 Week"
"P1M" -> billingPeriod= "For 1 Month"
"P1Y" -> billingPeriod= "For 1 Year"
}
} else{
when(billingPeriod){
"P1W" -> billingPeriod= "/Week"
"P1M" -> billingPeriod= "/Month"
"P1Y" -> billingPeriod= "/Year"
}
}
}
}
}
}
runOnUiThread {
Thread.sleep(1000)
}
}
override fun onBillingServiceDisconnected() {
}
} )
}
fun subscribeProduct(){
billingClient!!.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val productList = listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("basicmonthly")
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
val params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
billingClient!!.queryProductDetailsAsync(params.build()){
billingResult, productDetailsList ->
for(productDetails in productDetailsList){
val offerToken = productDetails.subscriptionOfferDetails?.get(productId)?.offerToken
val productDetailsParamsList = listOf(
offerToken?.let {
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(it)
.build()
}
)
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
val billingResult = billingClient!!.launchBillingFlow(this@Subscription, billingFlowParams)
}
}
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
Toast.makeText(applicationContext, "Service Disconnected ", Toast.LENGTH_SHORT).show()
}
})
}
override fun onDestroy() {
super.onDestroy()
if (billingClient != null){
billingClient!!.endConnection()
}
}
override fun onPurchasesUpdated(p0: BillingResult, p1: MutableList<Purchase>?) {
TODO("Not yet implemented")
}
}
Also I have a BillingClientLifecycle class that is
const val BASIC_MONTHLY_PLAN = "basicmonthly"
const val BASIC_YEARLY_PLAN = "basicyearly"
class BillingClientLifecycle public constructor(
private val applicationContext: Context,
private val externalScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Default)
) : DefaultLifecycleObserver, PurchasesUpdatedListener, BillingClientStateListener,
ProductDetailsResponseListener, PurchasesResponseListener {
private val _subscriptionPurchases = MutableStateFlow<List<Purchase>>(emptyList())
val subscriptionPurchases = _subscriptionPurchases.asStateFlow()
private var cachedPurchasesList: List<Purchase>? = null
val premiumSubProductWithProductDetails = MutableLiveData<ProductDetails?>()
val basicSubProductWithProductDetails = MutableLiveData<ProductDetails?>()
lateinit var billingClient: BillingClient
override fun onCreate(owner: LifecycleOwner) {
Log.d(TAG, "ON_CREATE")
billingClient = BillingClient.newBuilder(applicationContext)
.setListener(this)
.enablePendingPurchases() // Not used for subscriptions.
.build()
if (!billingClient.isReady) {
Log.d(TAG, "BillingClient: Start connection...")
billingClient.startConnection(this)
}
}
override fun onBillingSetupFinished(billingResult: BillingResult) {
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
Log.d(TAG, "onBillingSetupFinished: $responseCode $debugMessage")
if (responseCode == BillingClient.BillingResponseCode.OK) {
querySubscriptionProductDetails()
querySubscriptionPurchases()
}
}
override fun onBillingServiceDisconnected() {
Log.d(TAG, "onBillingServiceDisconnected")
}
private fun querySubscriptionProductDetails() {
Log.d(TAG, "querySubscriptionProductDetails")
val params = QueryProductDetailsParams.newBuilder()
val productList: MutableList<QueryProductDetailsParams.Product> = arrayListOf()
for (product in LIST_OF_SUBSCRIPTION_PRODUCTS) {
productList.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("basicmontly")
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
}
params.setProductList(productList).let { productDetailsParams ->
billingClient.queryProductDetailsAsync(productDetailsParams.build(), this)
}
}
private fun processPurchases(purchasesList: List<Purchase>?) {
Log.d(TAG, "processPurchases: ${purchasesList?.size} purchase(s)")
purchasesList?.let { list ->
if (isUnchangedPurchaseList(list)) {
Log.d(TAG, "processPurchases: Purchase list has not changed")
return
}
externalScope.launch {
val subscriptionPurchaseList = list.filter { purchase ->
purchase.products.any { product ->
product in listOf(PREMIUM_PRODUCT, BASIC_PRODUCT)
}
}
_subscriptionPurchases.emit(subscriptionPurchaseList)
}
logAcknowledgementStatus(list)
}
}
fun launchBillingFlow(activity: Activity, params: BillingFlowParams): Int {
if (!billingClient.isReady) {
Log.e(TAG, "launchBillingFlow: BillingClient is not ready")
}
val billingResult = billingClient.launchBillingFlow(activity, params)
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
Log.d(TAG, "launchBillingFlow: BillingResponse $responseCode $debugMessage")
return responseCode
}
@Volatile
private var INSTANCE: BillingClientLifecycle? = null
fun getInstance(applicationContext: Context): BillingClientLifecycle =
INSTANCE ?: synchronized(this) {
INSTANCE ?: BillingClientLifecycle(applicationContext).also { INSTANCE = it }
}
}
}
Now I am confused which function to use in the subscriptionframgment in the onclicklistener to start the purchase flow launchbillingflow from the BillingClientLifecycle or the subscribeProduct fun from the Subscribe activity. Because this code is not working. If anyone has worked before with the Google Play Billing then please help. Thank you