Home / Programming / Android / Play Billing Library Implementation (KOTLIN)

Play Billing Library Implementation (KOTLIN)

This is the only tutorial you’ll ever need on how to implement play billing library!

Prior to the introduction of Play Billing Library implementing and managing In-App Billing was a very- complicated process.
You had to:

  1. Install in-app billing library via Android Studio SDK Manager
  2. Add a Play Billing Manifest Permission
  3. Create an Android Interface Definition Language (AIDL) file
  4. Place the AIDL file inside a specific directory in a package called com.android.vending.BILLING
  5. Download inappbillingservice.aidl or copy it from the root In-app billing library directory (SDK_PATH/extras/play_billing) to Package defined in 4
  6. Bind your app to google play billing service using IabHelper
  7. Perform Service binding by calling start setup method on the instance of Iab helper you created
  8. You get the Idea, the process was arduous and definitely not beginner friendly!

If you would like to see how it was done watch this video

With the new Play Billing Library all of that crap goes out the window!

Definition of Terminologies

  • SkuID or Product ID – is a unique product identifier in small letter without space e.g remove_ads
  • SkuType  – this is either a subscription (SUBS) or a managed in-app purchase (INAPP)
  • Price –  Selling Price of the product for
  • Description – The text that describes what the user will see when attempting to buy the product
  1. A consumable in-app purchase means the product can be bought unlimited times eg Buying gas
  2. Non-Consumable in-app purchase means the product can be bought only once unless consumed eg. Buying a car until it’s damaged
  3. Managed In-App purchase -> is called ‘managed’ because an app publisher – you – can change the price of the product, at your will
  4. Subscriptions -> Prices are fixed cannot be changed or deleted. To change a subscription price  or remove it entirely you’ll have to update your app with another subscription unique product id (with the updated price) or remove the product id and update the app RESPECTIVELY.

Prerequisites

  1. Google Play Developer Account
  2. Installed Android Studio
  3. Basic Knowledge of KOTLIN
  4. Read These :
    – Everything You Need to Know About Google Play’s In-App Billing 
    Google Play Billing Security Best Practices
    Play Billing Codelab
    – How to verify purchase for android app in server side
    – Android In App Billing: securing application public key
    – Strong encryption for base64 encoded public key in Android

What are we going to Build?

In this tutorial we are going to demonstrate how to remove ads when user purchase any of 3 product and donate.

We are going to build a simple app that has four products. The products include:

  1. Remove Ads Permanently: our app will display banner ad, Purchasing this product eliminates it permanently
  2. Donate : I spent my valuable time writing this tutorial, it would be nice of you to tip me!
  3. One Month Remove Ads Subscription – kill ads for one month
  4. One Year Remove Ads Subscription – kill ads for one year
Grab source Code on Github

Detailed Implementation Process With Explanation

This new library has reduced the process to 5 simple stages.

  1. Connect to the billing client and start a service connection
  2. Buy a product (by launching the purchase flow dialog)
  3. Query for purchased product(s)
  4. validate purchased product
  5. Decide what to do with the response got in step 4.

Stage 1 – Implement Play Billing & Connect to Google Play Service

  1. add implementation ‘com.android.billingclient:billing:1.0’ to “build.gradle(Module : app)” and click ‘Sync Now’
  2. Define the Main Layout, activity_main.xml 
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:ads="http://schemas.android.com/apk/res-auto"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
            android:orientation="vertical"
            tools:context="com.edgedevstudio.testoutbilling.MainActivity">
    
            <TextView
                android:id="@+id/textView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:text="In-App Purchases"
                android:textColor="#000000"
                android:textSize="22sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
            <Button
                android:id="@+id/remove_ads_btn"
                android:layout_width="256dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                android:text="Remove Ads Permanently" />
    
            <Button
                android:id="@+id/donate_btn"
                android:layout_width="256dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:text="Donate" />
    
            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="32dp"
                android:text="Subscription Purchases"
                android:textColor="#000000"
                android:textSize="22sp"
                android:textStyle="bold" />
    
            <Button
                android:id="@+id/monthly_sub_btn"
                android:layout_width="256dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                android:text="One Month Remove Ads Subscription" />
    
            <Button
                android:id="@+id/yearly_sub_btn"
                android:layout_width="256dp"
                android:layout_height="wrap_content"
                android:text="One Year Remove Ads Subscription" />
        </LinearLayout>
    
        <com.google.android.gms.ads.AdView
            android:id="@+id/adView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            ads:adSize="SMART_BANNER"
            ads:adUnitId="ad_unit_id" />
    
    </RelativeLayout>
  3. Create class BillingManager.kt and Implement PurchasesUpdatedListener (Listener interface for purchase updates which happen when, for example, the user buys something within the app or by initiating a purchase from Google Play Store)
  4. for now, override PurchasesUpdatedListener ‘s method and log the response.
    Step 1-4 is shown below

    class BillingManager (/*soon to be occupied*/): PurchasesUpdatedListener{
    
        // keep cool, we're progressing
    
        init {
            // Some stuff to be implemented
        }
        override fun onPurchasesUpdated(responseCode: Int, purchases: MutableList?) {
            Log.d(TAG, "onPurchasesUpdated() response: " + responseCode)
        }
    }
  5. Make BillingManager’s constructor accept an object of Activity.
  6. Create a nullable member value object of BillingClient (Main interface for communication between the library and user application code) and appropriately initialise it in the init block
    class BillingManager (val activity : Activity): PurchasesUpdatedListener{
       val billingClient : BillingClient? = null
       init{
          billingClient = BillingClient.newBuilder(activity).setListener(this).build()
       }
    //.. other code
    }
  7. define private function startServiceConnectionIfNeeded(Runnable). This function provide a one time re-try policy to restart billingClient incase it is not responding (due to background upgrade or crash of google play service), disconnected or network errors.
    //.. other code
    private fun startServiceConnectionIfNeeded(executeOnSuccess: Runnable?) {
            if (billingClient.isReady()) {
                executeOnSuccess?.run()
            } else {
                billingClient.startConnection(object : BillingClientStateListener {
                    override fun onBillingSetupFinished(@BillingResponse billingResponse: Int) {
                        Log.i(TAG, "onBillingSetupFinished() BillingResponse: $billingResponse")
                        if (billingResponse == BillingResponse.OK) {
                            executeOnSuccess?.run()
                        } 
                    }
    
                    override fun onBillingServiceDisconnected() {
                        Log.i(TAG, "onBillingServiceDisconnected()")
                    }
                })
            }
        }
    //.. other code
  8. Define function destroyBillingClient to destroy the billingClient. This function should be called in onDestroy method of the implementing Activity or Fragment. This is useful in order to avoid app leaks and free up memory resources.
    >>Side Note: do not count on onDestroy() method being called as a place for saving data! For example, if an activity is editing data in a content provider, those edits should be committed in either onPause() or onSaveInstanceState(Bundle), not here. Read more about onDestroy and Android lifecycle. Sometimes when an app is terminated Activity onDestroy is never called or even if it is, only part of the code will be executed.

    // other code
    fun destroyBillingClient() {
       billingClient.endConnection()
    }
    // other code
  9. Define interface BillingUpdatesListener within BillingManager. It’s callbacks will provide billing updates like :
    – onPurchaseUpdated –  to be called when a user is done purchasing a product.
    – onConsumeFinished –  to be called when the Consumption of a purchase is complete.
    – onQueryPurchasesFinished – to be called when query of purchase is done.
    Further explanation and Implementation of this interface would be done in stage 2 and stage 3

    //  other code
    
       interface BillingUpdatesListener {
            fun onPurchaseUpdated(purchases: List<Purchase>, responseCode: Int)
            fun onConsumeFinished(token: String, @BillingClient.BillingResponse responseCode: Int)
            fun onQueryPurchasesFinished(purchases: List<Purchase>)
        }
    
    //  other code
  10. Make BillingManager accept an object of BillingUpdatesListener as a constructor parameter
  11. at the end of Stage 1, your code should look like this
    class BillingManager (val activity: Activity, val billingUpdatesListener: BillingUpdatesListener): PurchasesUpdatedListener{
    
        private val billingClient: BillingClient
        private val TAG = "BillingManager"
    
        init {
            billingClient = newBuilder(activity).setListener(this).build()
            startServiceConnectionIfNeeded(null)
        }
    
        private fun startServiceConnectionIfNeeded(executeOnSuccess: Runnable?) {
            if (billingClient.isReady()) {
                executeOnSuccess?.run()
            } else {
    
            // Starts up BillingClient setup process asynchronously. 
            // You will be notified through the BillingClientStateListener listener when the setup process is complete.
                billingClient.startConnection(object : BillingClientStateListener {
                    override fun onBillingSetupFinished(@BillingResponse billingResponse: Int) {
                        Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse)
                        if (billingResponse == BillingResponse.OK) {
                            executeOnSuccess?.run()
                        }
                    }
    
                    override fun onBillingServiceDisconnected() {
                        Log.i(TAG, "onBillingServiceDisconnected()")
                    }
                })
            }
        }
        /*
       - Implement this method to get notifications for purchases updates. Both purchases initiated by
       your app and the ones initiated by Play Store will be reported here.
       - When a new purchase is successful, it'll also be reported here
       */
         override fun onPurchasesUpdated(responseCode: Int, purchases: MutableList?) {
            Log.i(TAG, "onPurchasesUpdated() response: " + responseCode)
        }
        
        fun destroyBillingClient() {
            billingClient.endConnection()
        }
        interface BillingUpdatesListener {
            fun onPurchaseUpdated(purchases: List<Purchase>, responseCode: Int)
            fun onConsumeFinished(token: String, @BillingClient.BillingResponse responseCode: Int)
            fun onQueryPurchasesFinished(purchases: List<Purchase>)
        }
    }

Before we Proceed

Before you can sell (subscription or in-app) products you need to add them to your App on Google play developer console. If you already have your published on play store skip step 1 – 4.

  1. Within Android Studio, at top menu Click Build then Generate Signed Apk…
    (a) If you do not have an alias key click create new and follow the prompt or else proceed to (b)
    – under keystore path select a path on your computer you would like the key store file to be generated
    – under alias enter a camel cased text without space
    – use the same password you used for key store for alias password, just for easy recall. For added security you may enter a different password
    – enter the rest of the field as you deem fit.
    (b) Now that you have your key alias, key store password, key password. Enter them into their respective fields, then click next
    (d) check v1 (jar signature) and v2 (full apk signature) and click finish
    (e) wait for signed apk to be generated. Once done, locate the generated apk
  2. Go to Google play app dashboard
  3. Click Create Application Button, Enter the Name of your app then click create
  4. Upload signed Apk App to Beta Channel.
    on the left Navigation menu
    – Click App Releases.
    – under Open Track
    – under Beta click Manage
    – click Create Release
    – click continue on App signing by Google Play dialog
    – Upload Apk generated in in step 1. After successful upload, click save. After successful save click review.
    – Click Start Rollout to Beta
    – Wait for Google to review the uploaded app
    – Go back to Beta Release, Click manage. Enter 1000 into Maximum number of testers, your developer email address into Feedback Channel and click save.
    – Once your app release has been published you will get an Opt-in URL to download the updated app from play store.
  5. Add Products. On the left navigation menu of Google Play Console Dashboard expand Store Presence, click in-app products
    -> under MANAGED PRODUCTS click create managed product

    (i) add a remove ads product
    a- under Product ID enter remove_ads
    b- Title : Remove All Forms of Ads
    c- Description : This purchase will eliminate all form of ads within this app
    d- Status : Active
    e- Price : 2.99 US Dollar, select all countries in the list then click APPLY
    f- Finally click SAVE
    (ii) add a donate product
    a-  Product ID enter donate
    b- Title : Donate to the developer of this app. He Deserves it!
    c- Description : This is a consumable purchase meaning you can donate unlimited number of times!
    d- Status : Active
    e- Price : 0.99 US Dollar, select all countries in the list then click APPLY
    f- Finally click SAVE-> under SUBSCRIPTIONS click CREATE SUBSCRIPTION
    (i) add a monthly subscription product
    a-  Product ID enter monthly_sub
    b- Title : Subscribe to a Monthly Product!
    c- Description : Purchasing this Product will Remove Ads within this app for ONE whole Month!
    d- Status : Active
    e- Price : 0.99 US Dollar, select all countries in the list then click APPLY
    f-  Billing Period : Monthly
    g- Free Trial : 3 days
    i- Grace Period : 3 days
    j- Finally click SAVE
    (ii) add a yearly subscription product
    a-  Product ID enter yearly_sub
    b- Title : Subscribe to a Yearly Product!
    c- Description : Purchasing this Product will Remove Ads within this app for ONE whole Year!
    d- Status : Active
    e- Price : 1.99 US Dollar, select all countries in the list then click APPLY
    f-  Billing Period : Monthly
    g- Free Trial : 3 days
    i- Grace Period : 3 days
    j- Finally click SAVE

Stage 2 – Launch Purchase Flow and Buy a Product

  1. Within BillingManager.kt define a function launchPurchaseFlow that accepts string of SKU Id and billing type.
    //... other code
    fun launchPurchaseFlow(skuId: String, billingType: String) {
         val billingFlowParams = BillingFlowParams.newBuilder().setType(billingType).setSku(skuId).build()
         val purchaseFlowRunnable = Runnable {
            billingClient.launchBillingFlow(activity, billingFlowParams)
         }
         startServiceConnectionIfNeeded(purchaseFlowRunnable)
    }
    //... other code
    
  2. MainActivity.kt
    – Implement BillingManager.BillingUpdatesListener on MainActivity, then override all it’s methods.
    – Implement View.OnClickListener on MainActivity then override it’s method (onClick).
    – define a nullable BillingManger object
    – define nullable Button objects for each layout button
    – define function startPurchaseFlow(skuId, skuType)
    Within onCreate method of MainActivity :
    – set content view to activity_main.xml
    – Link layout buttons to button objects
    – Set on click listener on the button objects
    Tapping a button launches a purchase flow by calling launchPurchaseFlow (with the a specified skuId and SKUType) on BillingManager object.

    class MainActivity : AppCompatActivity(), View.OnClickListener, BillingManager.BillingUpdatesListener {
        companion object {
            val TAG = "MainActivity"
        }
        var remove_ads_perm_btn: Button? = null
        var donate_btn: Button? = null
        var sub_monthly: Button? = null
        var sub_yearly: Button? = null
        var adView : AdView? = null
        var billingManager: BillingManager? = null
    
        val REMOVE_ADS_PERMANENTLY_SKU_ID = "remove_ads"
        val DONATE_SKU_ID = "donate"
        val MONTH_SUB_SKU_ID = "monthly_sub"
        val YEAR_SUB_SKU_ID = "yearly_sub"
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            adView = findViewById(R.id.adView) as AdView
            val adRequest = AdRequest.Builder().build()
            adView?.loadAd(adRequest)
    
            remove_ads_perm_btn = findViewById(R.id.remove_ads_btn)
            donate_btn = findViewById(R.id.donate_btn)
            sub_monthly = findViewById(R.id.monthly_sub_btn)
            sub_yearly = findViewById(R.id.yearly_sub_btn)
    
            remove_ads_perm_btn?.setOnClickListener(this)
            donate_btn?.setOnClickListener(this)
            sub_monthly?.setOnClickListener(this)
            sub_yearly?.setOnClickListener(this)
    
            billingManager = BillingManager(this, this)
        }
    
        override fun onClick(view: View?) {
            when (view?.id) {
                R.id.remove_ads_btn -> startPurchaseFlow(REMOVE_ADS_PERMANENTLY_SKU_ID, BillingClient.SkuType.INAPP)
                R.id.donate_btn -> startPurchaseFlow(DONATE_SKU_ID, BillingClient.SkuType.INAPP)
                R.id.monthly_sub_btn -> startPurchaseFlow(MONTH_SUB_SKU_ID, BillingClient.SkuType.SUBS)
                R.id.yearly_sub_btn -> startPurchaseFlow(YEAR_SUB_SKU_ID, BillingClient.SkuType.SUBS)
            }
        }
    
        fun startPurchaseFlow(skuId : String, @BillingClient.SkuType skuType: String) {
            billingManager?.launchPurchaseFlow(skuId, skuType)
        }
    
        override fun onDestroy() {
            super.onDestroy()
            billingManager?.destroyBillingClient()
        }
    
         override fun onPurchaseUpdated(purchases: List, responseCode: Int) {
            Log.i(TAG, "onPurchaseUpdated, responseCode = $responseCode, size of purchases = ${purchases.size}")
            for (purchase in purchases) {
                displayMsgForPurchaseCheck(purchase)
            }
        }
    
        override fun onConsumeFinished(token: String, responseCode: Int) {
            Log.i(TAG, "onConsumePurchase Successful : BillingResponseCode = $responseCode, token = $token")
            showToast("Thank You for Donating!\nYou may consider donating a few more times!")
    
        }
    
        override fun onQueryPurchasesFinished(purchases: List) {
            Log.i(TAG, "onQueryPurchasesFinished, size of verified Purchases = ${purchases.size}")
            for (purchase in purchases) {
               displayMsgForPurchaseCheck(purchase)
            }
        }
        private fun showToast(msg : String, length : Int = Toast.LENGTH_LONG){
            Toast.makeText(this, msg, length).show()
        }
    
       private fun displayMsgForPurchaseCheck(purchase: Purchase) {
            if (DONATE_SKU_ID.equals(purchase.sku)) {
                billingManager?.consumePurchase(purchase)
            } else {
                adView?.visibility = View.GONE
                showToast("Thank You for Purchase! Ads have been Eliminated!")
            }
        }
    }

Stage 3  – Query for Purchases

  1. within BillingManager, define an ArrayList called verifiedPurchaseList with type a cast of Purchase (Represents an in-app billing purchase) to hold a list of verified purchases. Verifying user purchase(s) is done in stage 4.
    class BillingManager(val activity: Activity, val billingUpdatesListener: BillingUpdatesListener) : PurchasesUpdatedListener {
        // other code
        private val verifiedPurchaseList = ArrayList<Purchase>()
    //.. other code
    }
  2. Define a queryPurchases() function within BillingManager.kt, to query for user’s previous purchase(s)
    > define an object of Runnable called purchaseQueryRunnable an within it :
    – clear verifiedPurchaseList due to initiating a new query
    – calling queryPurchases on BillingClient object to query for in-app purchases then handle the response
    – check if user device supports subscription billing
    – if user device supports subscription billing, call queryPurchases on BillingClient object to query for sub purchases then handle the response
    – For all the purchase, we loop through and verify each. as we will see in stage 4.
    – In Stage 4 each verified purchase will be added to verifiedPurchaseList.
    – once done, use the onQueryPurchasesFinished(verifiedPurchaseList) callback to notify the implementing activity or fragment of process completion.
    > pass purchaseQueryRunnable into startServiceConnectionIfNeeded to execute the runnable

    //...other code
        fun queryPurchases() {
            val purchaseQueryRunnable = Runnable {
                verifiedPurchaseList.clear() // We cleared the verified purchase list, we'll talk more about this in stage 4
                val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP) // querying for in app purchases
    
                // if response was good
                if (purchasesResult.responseCode == BillingClient.BillingResponse.OK) {
                    purchasesResult.purchasesList.addAll(purchasesResult.purchasesList)
                }
    
                // Not all clients support subscriptions so we have to check
                // If there are subscriptions supported, we add subscription rows as well
                if (areSubscriptionsSupported()) {
                    val subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
                    if (subscriptionResult.responseCode == BillingClient.BillingResponse.OK) {
                        //a succinct way of adding all elements to a list instead of using for each loop
                        purchasesResult.purchasesList.addAll(subscriptionResult.purchasesList)
                    }
                } else {
                    Log.d(TAG, "Subscription are not supported for this client!")
                }
    
                for (purchase in purchasesResult.purchasesList) {
                    handlePurchase(purchase)
                }
                billingUpdatesListener.onQueryPurchasesFinished(verifiedPurchaseList)
            }
            startServiceConnectionIfNeeded(purchaseQueryRunnable)
        }
        fun areSubscriptionsSupported(): Boolean {
            // Checks if subscriptions are supported for current client
            val responseCode = billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS)
            return responseCode == BillingClient.BillingResponse.OK
        }
    // ..other code
    

Stage 4  – Verify User Purchase

There are two ways to validate a purchase :
1.  within your app (less secure, not recommended)
2. on a server (highly recommended)

1. Validating Purchase within App

  1. Go to your app dashboard > Development tools > Services & APIs
  2. Copy Base64-encoded RSA public key
  3. In BillingManager
    – declare a value BASE_64_ENCODED_PUBLIC_KEY, assign it the copied key.
    – declare a value SIGNATURE_ALGORITHM, assign it SHA1withRSA
    – declare a value KEY_FACTORY_ALGORITHM, assign it RSA
    – define a function generatePublicKey() to generate a public key from BASE_64_ENCODED_PUBLIC_KEY
    – define a function isValidSignature() to validate the signature of the purchase
    – define a function consumePurchase to consume in-app purchase (donate)
    – define a function called handlePurchase() as shown below

        private val BASE_64_ENCODED_PUBLIC_KEY = "replace_with_your_Base64-encoded_RSA_public_key"
        private val SIGNATURE_ALGORITHM = "SHA1withRSA"
        private val KEY_FACTORY_ALGORITHM = "RSA"
    
    // other code 
    
        private fun handlePurchase(purchase: Purchase) {
            if (isValidSignature(purchase.originalJson, purchase.signature)) {
                verifiedPurchases.add(purchase)
            }
        }
    
        private fun isValidSignature(signedData: String, signature: String): Boolean {
            val publicKey = generatePublicKey(BASE_64_ENCODED_PUBLIC_KEY)
    
            val signatureBytes: ByteArray
            try {
                signatureBytes = Base64.decode(signature, Base64.DEFAULT)
            } catch (e: IllegalArgumentException) {
                BillingHelper.logWarn(TAG, "Base64 decoding failed.")
                return false
            }
    
            try {
                val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
                signatureAlgorithm.initVerify(publicKey)
                signatureAlgorithm.update(signedData.toByteArray())
                if (!signatureAlgorithm.verify(signatureBytes)) {
                    BillingHelper.logWarn(TAG, "Signature verification failed.")
                    return false
                }
                return true
            } catch (e: NoSuchAlgorithmException) {
                // "RSA" is guaranteed to be available.
                throw RuntimeException(e)
            } catch (e: InvalidKeyException) {
                BillingHelper.logWarn(TAG, "Invalid key specification.")
            } catch (e: SignatureException) {
                BillingHelper.logWarn(TAG, "Signature exception.")
            }
    
            return false
        }
    
        @Throws(IOException::class)
        private fun generatePublicKey(encodedPublicKey: String): PublicKey {
            try {
                val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
                val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
                return keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
            } catch (e: NoSuchAlgorithmException) {
                // "RSA" is guaranteed to be available.
                throw RuntimeException(e)
            } catch (e: InvalidKeySpecException) {
                val msg = "key specification: $e"
                BillingHelper.logWarn(TAG, msg)
                throw IOException(msg)
            }
        }
    
       fun consumePurchase(purchase: Purchase) {
            val consumePurchaseRunnable = Runnable {
                billingClient.consumeAsync(purchase.purchaseToken, ConsumeResponseListener {
                    responseCode, purchaseToken ->
                    if (responseCode == BillingClient.BillingResponse.OK){
                        billingUpdatesListener.onConsumeFinished(responseCode, purchaseToken)
                    }
                })
            }
            startServiceConnectionIfNeeded(consumePurchaseRunnable)
        }
    // other code
  4. in MainActivity.kt
    – update onConsumeFinished() callback method
    – update onQueryPurchasesFinished() callback method

    // other code  
       override fun onQueryPurchasesFinished(purchases: List<Purchase>) {
            Log.i(TAG, "onQueryPurchasesFinished, size of Purchases = ${purchases.size}")
            for (purchase in purchases) {
                if (DONATE_SKU_ID.equals(purchase.sku)) {
                    billingManager?.consumePurchase(purchase)
                }else{
                    adView.visibility= View.GONE
                }
            }
        }
    // other code

2. Validating Purchase through Google Server (COMING SOON!)

Now, you know how to implement play billing library. I command you to go and make money from all nations of the world!

 

About Edge Developer

Hello there, my name is Opeyemi Olorunleke. I am a Software Developer (majorly Android, GitHub Profile), Digital Marketer, Udemy Instructor, Technical Writer, Blogger & Webmaster.

Check Also

Android Admob Consent SDK : All you need to know + Example

First of all, let me address Google’s complacency to help app developers implement the GDPR …

Leave a Reply

Your email address will not be published. Required fields are marked *