How to implement Google Play Billing Library properly?

93 views Asked by At

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

0

There are 0 answers