Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

297
728
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

We will go over:

Add IN APP products to your app from the Google Play Console. Launch a Purchase Flow UI to allow users to purchase the items verify the purchase with a Back-end server using Firebase Cloud Functions and the Firestore DatabaseAcknowledge the purchase to finalize and receive payments how to unlock the product so the user purchases the item again

Adding the Google Play Billing Library

To begin, go to the Manifests file and add the INTERNET permission.

<uses-permission android:name=”android.permission.INTERNET”></uses-permission>

Then go to the app’s build.gradle file, implement the Google Play Billing Library, and sync the project.

dependencies {    …    def billing_version = “4.0.0”    implementation “com.android.billingclient:billing:$billing_version”}

Setup for Testing

Go to the Build tab at the top and Generate a Signed App Bundle.

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Once the process is complete, go to the Google Play Console and set up for testing. Go to play.google.com/console. 

On the left, under “Setup,” go to “License testing” and add your testing account.

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Then go to the “All apps” page and click on the “Create app” button to create the console for your app. 

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

When you’re in your app’s console page, scroll down until you find the Internal testing tab on the left. 

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Click on the “Create new release“ button and upload the app bundle.

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

When the upload is finished, scroll down and give the release a name. Save the changes, click on “Review release,” and then ”Start rollout to Internal testing”.  

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

When the release is created, click on the Testers tab and add the testing account that you added earlier in License Testing. 

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

The next step we need to do to, is to have an emulator that supports Google Play. Open the Android Virtual Device Manager. 

If your emulators do not have the Play Store icon next to it, you’ll need to create one that does.

Click on the “Create Virtual Device…” button and select the emulator with the Play Store icon.

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Opt-in as a Tester

Start the emulator. 
Open the Google Play App.
Sign in with the testing account.

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

The last step we need to do is to opt-in as a tester. 
Go back to your app’s console page. In the Internal testing tab, scroll down until you see the section call “How testers join your test”.

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Click on the ”Copy Link.” 

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Open the Chrome app on the Emulator. Paste the link in the address bar and then accept the invite to test the app. 

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Adding In-App Products

To add IN-APP products to your app, go to your app’s console page and scroll down until you see the Monetize section on the left. 
Under “Products,” click on “In-app products.” 

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Click on the “Create Product” button on the right.
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

On this screen, we can enter an ID, Name, Description and Price for the item. 

The Product ID is a unique name for the product. It cannot be changed or reused in another item once it is created. 
Copy
The name and description are used to describe the item so users would know what they’ll be getting.
Copy
For the price, click on “Set price” and enter a price for the item. 
Copy
Click on “Apply prices,” “Save,” and then “Activate“ to complete the process.
If we go back to the In-app products page, the new item should be there. 
Image from Codeible.com
Repeat the steps one more time so you’ll have at least 2 products for this tutorial. 
Displaying the Items
Now that we have some items, let’s see how we can display in our app. 

Go to the Main Activity file of your project. Declare a BillingClient variable and use the newBuilder() and build() methods from BillingClient to create the object. 



private BillingClient billingClient;


@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        billingClient = BillingClient.newBuilder(this).build();
}


The BillingClient object is used to communicate with Google Play’s billing system. We can use it to get all the information from the products list.


Before we can connect to Google Play, it is required by us to support pending purchases. Call enablePendingPurchases() right before we build the client and then add a PurchasesUpdatedListener() so we can handle all the incoming purchases from the user.


 billingClient = BillingClient.newBuilder(this)
    .enablePendingPurchases()
    .setListener(new PurchasesUpdatedListener() {
          @Override
           public void onPurchasesUpdated(@NonNull BillingResult billingResult, 
                       @Nullable List<Purchase> list) {
                        
           }
     })
.build();Now that we have our BillingClient, create a method call connectToGooglePlayBilling(). 


Inside the method, take the BillingClient and call startConnection().


private void connectToGooglePlayBilling(){
    billingClient.startConnection();
}


It takes in a BillingClientStateListener that is used to check if we have connected to the Google Play Billing System.

billingClient.startConnection(
    new BillingClientStateListener() {
       @Override
       public void onBillingServiceDisconnected() {


       }


       @Override
       public void onBillingSetupFinished(@NonNull BillingResult billingResult) {


       }
});


It is advisable that we restart the connection if we have been disconnected. Call the connectToGooglePlayBilling() method inside the onBillingServiceDisconnected() callback to reconnect. 

@Override

public void onBillingServiceDisconnected() {
      connectToGooglePlayBilling();
}

The onBillingSetupFinished() callback is used to let us know what the current connection state we’re in. If the response code we received from the billingResult object is OK, it means we have connected to the Google Play Billing System.

@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {


      if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {


      }


}

Copy
To get the products that we have added in the Google Play Console, create a method call getProductDetails() and then call it inside the onBillingSetupFinished() callback.

private void getProductInformation(){


}


Inside the getProductDetails() method, create a List call productIds and add the product id for each product you have in the Google Play Console. 


For now, I only want to show one item so I will just add 1.


List<String> productIds = new ArrayList<>();
productIds.add(“skill_upper_cut”);

Copy
Once you have the list of products ids, call querySkuDetailsAsync() from the billingClient to actually get the product information.
billingClient.querySkuDetailsAsync()
Copy
It takes 2 arguments, the first asks for a SkuDetailsParams. 


We use it to generate a query to get the product details using the list of product ids we have.


Declare a SkuDetailsParams variable call getProductDetailsQuery and call the newBuilder() and build() methods from SkuDetailsParams to create the object.
SkuDetailsParams getProductDetailsQuery = SkuDetailsParams.newBuilder().build();


Call setSkusList() to add the list and call setType() to set the type. 

SkuDetailsParams getProductDetailsQuery =
      SkuDetailsParams
         .newBuilder()
         .setSkusList(productIds)
         .setType(BillingClient.SkuType.INAPP)
         .build();


Then insert the query object into the querySkuDetailsAsync() method. 

billingClient.querySkuDetailsAsync(getProductDetailsQuery, );



For the second argument, it takes in a SkuDetailsResponseListener that we can use to access the results from the query.


The list object represents the products information we requested.



billingClient.querySkuDetailsAsync(
     getProductDetailsQuery,
     new SkuDetailsResponseListener() {
           @Override
            public void onSkuDetailsResponse(
                 @NonNull BillingResult billingResult,
                 @Nullable List<SkuDetails> list) {
            }
     });

Now that we retrieved the products’ information we can display it so users can see it. 


Normally, you’ll use a Recycler View or a Card View to display the items but since we only have 1 item, we’ll use a TextView to display the item’s name and a button to display the price. 


Go to MainAcitivty XML Layout File and add a TextView and Button inside. Set the constraints so that the button is on the right and the TextView is on the left.  


Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Rename the TextView id to itemName and the button to itemPrice.
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Go back to the MainActivity file. In the SkuDetailsReponse() callback, create a reference to the TextView and Button.

TextView itemNameTextView = findViewById(R.id.itemName);
Button itemPriceButton = findViewById(R.id.itemPrice);

Since we only have 1 item, create a single SkuDetails variable and get the first item in the list. Then set the text of the TextView by getting the title and the Button by getting the price. 

SkuDetails itemInfo = list.get(0);
itemNameTextView.setText(itemInfo.getTitle());
itemPriceButton.setText(itemInfo.getPrice());If we run the app, we should see the item.
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Launching the Purchase Flow
Now that we have a way for users to see the item. We want them to be able to purchase it. 


Add an onClickListener to the price button.


itemPriceButton.setOnClickListener(
      new View.OnClickListener() {
             @Override
             public void onClick(View view) {


             }
      });


We want to launch the Purchase Flow UI so users can choose how to pay when they click on the button. 


Grab the billingClient and call launchBillingFlow(). 


It takes 2 parameters. a context, and a BillingFlowParams object. 

Use the newBuilder() and build() methods from BillingFlowParams to create the one. 


Then setSkuDetails() to add the item details before building it.


 Activity activity = this;
….


itemPriceButton.setOnClickListener(
        new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                   billingClient.launchBillingFlow(
                          activity,
                          BillingFlowParams
                            .newBuilder()
                            .setSkuDetails(itemInfo)
                            .build());
             }
         }
);


If we run the app, and click on the item, a purchase flow UI should come up.
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Verifying the Purchase with Back-end
Now let’s see how we can verify the purchase when the user pays for the item.


Whenever someone purchases an item, it will bring us back to the onPurchasesUpdated() callback. The billingResult object returns the status of all the purchases made and the purchase list represents all the purchases that were made by the user at the time.


This is where we need place the code to verify the purchase through a back-end server and acknowledge the payment to receive the money. 


 @Override
public void onPurchasesUpdated(
       @NonNull BillingResult billingResult, 
       @Nullable List<Purchase> list) {
            
        }
});


First make sure that the there was a successful purchase. Check if we got an OK response code and then check if the purchase list is not empty inside the onPurchasesUpdated() callback.
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && 
    list != null) {


}To verify if the purchases are real requires 2 steps.


The first step is to make sure that the item is in the PURCHASED state and was not acknowledged before. 


Use the For loop to iterate through all the purchases that are coming in. Then use the if statement to validate the state and acknowledgement.



if(billingResult.getResponseCode() == …) {
          for(Purchase purchase: list) {
                if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED &&
                             !purchase.isAcknowledged()) {
                                             
                }
          }
}

Copy
The second step is to verify the purchase using a back-end server. 


We’ll need to get the purchase token for each purchase, send it to our back-end server, and then check to see if the token was never used. If the token was never used, it means it is a valid token. 


When we verified that the token is valid, we can store that purchase information in an online database and then send a message back to the app to notify the user.


For this part, we’ll use Firebase Cloud Functions as the back-end server and Firestore as the database.


Begin by creating a Cloud Functions project.  
Creating a Cloud Functions Project
Go to console.firebase.google.com. Sign in with your Google account and create a project. If you do not have a Google account, go to accounts.google.com/signup to create an account and then go back to Firebase.
 console.firebase.google.com
Copy
accounts.google.com/signup
Copy
Once you have created a project, make sure you have node and the firebase-tools installed on your computer.


Run node -v in the terminal to get  the version of node you have installed and the firebase – -version for firebase-tools. 

node -v
Copy
firebase –version
Copy
If you do not have them installed, go to nodejs.org and follow the steps to install node.
nodejs.org
Copy
For firebase-tools, run npm install -g firebase-tools after you have node installed.
npm install -g firebase-toolsWhen you have node and firebase-tools installed, create a folder where it is easy to locate. Open a terminal for that directory and log into firebase. 
firebase login
Copy
Afterwards, initialize Firebase Cloud Functions using the firebase init command. 
firebase init
Copy
Select the Firebase project you have for the app and continue. 


Select “Yes” to the install the dependencies and continue.

Once Cloud Functions is set, open the project in a code editor. Expand the functions folder and go to the index.js file. 


In here, we can define our validation functions. 


Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Type exports, then use the dot operator to name the function and assign a request handler function to it.

exports.verifyPurchases = functions.https.onRequest(
    (req,res) => {


    }
);

Copy

To store the token’s data that we’ll be receiving, create a JSON object call purchaseInfo. 


It should at least contain these 4 properties, purchaseToken, orderId, purchaseTime, and isValid.


To get the purchase token, grab the request object, call query, and then access the purchaseToken property. 


For the orderId and purchaseTime, we’ll do the same. 


For isValid, set it to false as the default value.


var purchaseInfo = {
   purchaseToken: req.query.purchaseToken,
   orderId: req.query.orderId,
   purchaseTime: req.query.purchaseTime,
   isValid: false
}

Now that we have the purchase information in a JSON object, we need to connect to Firestore and check if the token was used or not. 


Import the admin module and initialize the app. 


const functions = require(“firebase-functions”);

const admin = require(“firebase-admin”);
admin.initializeApp();




exports.verifyPurchases = functions.https.onRequest(
    (req,res) => {
        …
    }
);


Inside the validate function, create an instance of Firestore by taking the admin object and calling the firestore() function.

var purchaseInfo = {
   …
}


var firestore = admin.firestore();




To check if a token exists in Firestore, we just need to see if there is a document with the same token id as the current token. If there is no such document, it means it is a new token.  


Grab the Firestore object, call doc(), get(), and then(). 


Inside the doc() function, enter the path to the collection where you would store all your purchase information. Use purchaseToken as the ID of the document id.


For then(), pass in an anonymous function with a result parameter.


firestore.doc(`purchases/${purchaseInfo.purchaseToken}`)
             .get()
             .then((result) => {
              
              }
);


Result represents the document that was retrieve from Firestore. We can use the exists property to check if there was a document with the same purchase token. 

if(result.exists) {


}



If there is, send a the purchase information back to let the app know that it was not a valid purchase token.

if(result.exists) {
   res.send(purchaseInfo);
}



If it does not exist, we want to add the purchase information to the database. 


Set the isValid property from the purchaseInfo object to true.


Grab the firestore object, call doc(), set(), and then(). 


Inside the doc() function, pass in the same path to the document. 


For set, pass in the purchaseInfo object, and for then, pass in an anonymous function and send the updated purchase information back.


 if(result.exists) {
    …
} else {
    purchaseInfo.isValid = true;
    firestore.doc(`purchases/${purchaseInfo.purchaseToken}`)
                 .set(purchaseInfo)
                 .then(() => {
                            res.send(purchaseInfo);
                  });
}


This is all we have to do to validate the purchase. 


The next step is to upload this function to Cloud Functions so we can use it. Open the terminal and run the deploy command. 

firebase deploy –only functions

Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

Make sure your Firebase project is upgraded to the Blaze plan before executing the command or you will not be able to deploy the function.  
Once the deployment is completed, go to the Firebase console. In the Functions tab, you should see the function in the system.
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Copy the Request URL for the function, and paste it inside your Android project so we have it. We’ll need to use it to send data back and forth between the server and the app.

The idea is to call the function in the server using the Request URL, and when the server is done doing whatever it needs, it’ll send message back to let us know whether or not the purchase was valid. 
Implementing Volley
To communicate with Cloud Functions using the Request URL, go to the app’s build gradle file and implement Volley. 

dependencies {
   …
    def volley_version = “1.2.0”
    implementation “com.android.volley:volley:$volley_version”
}


Go back to the MainActivity file and create a new method call verifyPurchase() with a purchase object parameter.

private void verifyPurchase(Purchase purchase) {


}


Inside the method create a String variable for the Request URL. 
String requestUrl = “https://us-central1-playbillingtutorial.cloudfunctions.net/verifyPurchases”
Copy
If we take a look at Cloud Functions, we are trying to get the purchaseToken, purchaseTime, and orderId from an object call query. 
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

To add data to the query object, we need to add a question mark (?) symbol at the end of the URL to represent the start of the query parameter. For each set of data, we need to insert a key and value pair and separate each set with a ampersand (&)symbol.

String requestUrl = “https://us-central1-playbillingtutorial.cloudfunctions.net/verifyPurchases?” +
                “purchaseToken=” + purchase.getPurchaseToken() + “&” +
                “purchaseTime=” + purchase.getPurchaseTime() + “&” +
                “orderId=” + purchase.getOrderId();


Make sure that the name of each key is identical to what you defined in the JSON object in Cloud Functions.


Now that we have our URL set up, create a POST request using StringRequest from the Volley library. 


It takes 4 arguments. 


The first is the type of request we are trying to make, and we’re trying to make a POST request.  


The second argument is the Request URL.


The third argument is the response listener callback function which will be called when we receive a response back from the server.


The fourth argument is a callback for errors.



 StringRequest stringRequest = new StringRequest(
     Request.Method.POST,
     requestUrl,
     new Response.Listener<String>() {
         @Override
         public void onResponse(String response) {
                        
     },
     new Response.ErrorListener() {
          @Override
           public void onErrorResponse(VolleyError error) {


           }
     }
);



Now that we have our Request Setup, we need to send it to the server. 


Create a RequestQueue using newRequestQueue() from Volley and add the StringRequest to the queue.


StringRequest stringRequest = new StringRequest(…)



Volley.newRequestQueue(this).add(stringRequest);

Remember to call the verifyPurchase() method inside the  onPurchasesUpdated() callback.

for(Purchase purchase: list) {
   if(purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED &&
            !purchase.isAcknowledged()) {
               verifyPurchase(purchase);
   }
}


If we run the app, and purchase the item, it should be uploaded to Firestore. 
Go to the Firebase Console. Change the Firestore rules to true so we can read and write data. 
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

If we run the app, and purchase the item, it should be uploaded to Firestore.
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay
Selling In-App Products on Android: Implementing Google Play Library version 4 GooglePlay

If we try to purchase the item again, we’ll get an error. 


This is because all In-App purchase products are defaulted to one-time purchase only. In order the actually get the money for each purchase, we need to acknowledge it, either by calling acknowledgePurchase() or consumeAsync(). 



We use acknowledgePurchase() if the product is a one-time purchase item and we use consumeAsync() if the item is purchasable multiple times. 


Acknowledging the Purchases
Go back to the verifyPurchase() method. Inside the response listener callback, create a JSONObject using the response from the server. 
Then use an If statement to check if the isValid property is true. It if is, we can start acknowledging the purchase.

try {


  JSONObject purchaseInfoFromServer = new JSONObject(response);


  if(purchaseInfoFromServer.getBoolean(“isValid”)) {


  }


} catch (Exception err) {


}


To use acknowledgePurchase(), create a AcknowledgePurchaseParams object using newBuilder() and build() from AcknowledgePurchaseParams. 


Then add the purchase token of the purchase you want to acknowledge using setPurchaseToken() right before you build the params object.



AcknowledgePurchaseParams acknowledgePurchaseParams = 
                             AcknowledgePurchaseParams
                            .newBuilder()

                            .setPurchaseToken(purchase.getPurchaseToken())
                            .build();


Next, grab the billingClient and call acknowledgePurchase(). 


Pass in the purchase params and add a AcknowledgePurchaseResponseListener.


billingClient.acknowledgePurchase(
    acknowledgePurchaseParams,
    new AcknowledgePurchaseResponseListener() {
           @Override
           public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) {
                if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                       Toast.makeText(activity, “Consumed!”, Toast.LENGTH_LONG).show();
                }
           }
    }
);


Inside the response listener, check if the result was OK. If it is, we need to put the logic to notify and give the item to the user. For now, we’ll use a Toast message. 
If we run the app and purchase the item again, it will get acknowledged. If you are still getting the “You already own this item” error, wait a few minutes for it to reset and try again.
Unlocking the Item
Now let’s see how we can acknowledge consumables. Go back to the getProductDetails() method and replace the old item with a new one.

private void getProductDetails(){


        List<String> productIds = new ArrayList<>();
        productIds.add(“small_potion”);


        …
}



To acknowledge consumables, we need to use consumeAsync() instead of acknowledgePurchase().


Go back to the verifyPurchase() method. Delete the acknowledgePurchase code and call the consumeAsync() method from the billingClient.

billingClient.consumeAsync()

It takes 2 arguments. The first is a ConsumeParams object. Create one by using the newBuilder() and build() methods from ConsumeParams. Then add the purchase token by calling setPurchaseToken().


The second argument is for the ConsumeResponseListener.


ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();


billingClient.consumeAsync(
        consumeParams,
        new ConsumeResponseListener() {
            @Override
            public void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String s) {


            }
        }
);

Inside the response listener, check if the result was OK. If it is, we need to put the logic to notify and give the item to the user. For now, we’ll use a Toast message again.

if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
       Toast.makeText(activity, “Consumed!”, Toast.LENGTH_LONG).show();
}


If we run the app, we can purchase the item multiple times.

Handling Purchases That Were Made While the App is Closed
This is all we need to do to verify the purchases. But as best practice, we should not rely solely on the onPurchasesUpdated() callback. This is because the callback will only be available when the app is opened. 


To handle purchases that were made while the app was closed, we need to override the onResume() method.


@Override
protected void onResume() {
     super.onResume();
}


The idea is to retrieve all the purchases when the user returns to the app and then we’ll verify each purchase accordingly. 


Inside the method, grab the BillingClient and call queryPurchasesAsync(). 


The queryPurchasesAsync() method returns the purchases made by the user. It takes 2 arguments.


The first argument is the type of product we want to get. 


The second argument is for a purchasesResponseListener() which give us access to the result of the query.

Copy

billingClient.queryPurchasesAsync(
   BillingClient.SkuType.INAPP,
   new PurchasesResponseListener() {
        @Override
        public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> list) {


          }
    }
);


Use the BillingResult object and check if the status was OK. Inside the if statement, iterate through all the purchases that were made and check if the state was purchased and not yet acknowledged. If it was not acknowledged, we want to verify the purchase.

if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
     for(Purchase purchase: list) {
          if(purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED &&
                                !purchase.isAcknowledged()) {
                    verifyPurchase(purchase);
          }
     }
 }


That is all for this tutorial. If you find this helpful, please give it a like, share, and subscribe to support the channel.

297 COMMENTS

  1. Its such as you read my thoughts! You seem to
    know so much about this, such as you wrote the guide in it
    or something. I think that you just could do with a few
    percent to drive the message home a bit, however other than that,
    that is magnificent blog. A great read. I will definitely be
    back.

  2. Hmm it appears like your blog ate my first comment (it was extremely
    long) so I guess I’ll just sum it up what I had written and say, I’m thoroughly enjoying your blog.
    I too am an aspiring blog writer but I’m still new to the whole thing.
    Do you have any recommendations for inexperienced blog writers?
    I’d definitely appreciate it.

  3. I am curious to find out what blog system you are working with?
    I’m having some small security issues with my latest website and I would like to find something more risk-free.
    Do you have any suggestions?

  4. hey there and thank you for your info – I have definitely picked up anything new from right here.

    I did however expertise several technical issues using this web site, as I experienced
    to reload the site many times previous to I could get it to load
    properly. I had been wondering if your hosting is OK?
    Not that I am complaining, but sluggish loading instances times
    will sometimes affect your placement in google and could
    damage your quality score if ads and marketing with Adwords.
    Well I am adding this RSS to my email and can look out for a lot more of your respective fascinating content.
    Ensure that you update this again soon.

  5. [url=https://cheapviagra100mgnorx.monster/]generic viagra lowest prices[/url] [url=https://onlineviagrawithoutprescription.monster/]viagra from mexico[/url] [url=https://viagrabestdrugstore.monster/]where can i buy viagra in south africa[/url] [url=https://orderviagra100prescription.quest/]viagra canadian pharmacy prices[/url] [url=https://ordercialistabnorx.quest/]otc cialis 2018[/url]

  6. I’ve been browsing on-line greater than 3 hours lately, but I by no means found any attention-grabbing article like yours. It is pretty price sufficient for me. Personally, if all website owners and bloggers made just right content material as you probably did, the web might be a lot more helpful than ever before.

  7. [url=https://cialisbed.com/]buy cialis 40 mg online[/url] [url=https://ordersildenafiltablets.com/]sildenafil where to get[/url] [url=https://nolvadex.quest/]nolvadex prices[/url] [url=https://buyhydroxyzine.com/]atarax 10mg prescription[/url] [url=https://bupropion.live/]bupropion australia[/url] [url=https://buytrimox.com/]amoxicillin buy no prescription cheapest[/url] [url=https://tadalafilxgeneric.com/]tadalafil online purchase[/url] [url=https://propecia.monster/]canadian purchase propecia[/url] [url=https://ivermectinqtab.com/]how to get ivermectin[/url] [url=https://wellbutrin.today/]where can i buy wellbutrin[/url]

  8. [url=https://gentadalafil.com/]tadalafil 20mg coupon[/url] [url=https://tadalafilxgeneric.com/]tadalafil 5mg coupon[/url] [url=https://chloroquine.live/]buy chloroquine online canada[/url] [url=https://genericcialistablets.com/]cialis pill 5mg[/url] [url=https://sildenafiledrem.com/]sildenafil in usa[/url] [url=https://sildenafilaac.com/]can i buy sildenafil citrate[/url] [url=https://sildenafilyeah.com/]sildenafil 50mg uk[/url] [url=https://viagratobuy.com/]where can u buy viagra[/url] [url=https://zoloftforsale.online/]average cost of zoloft[/url] [url=https://synthroid.quest/]synthroid 150 mg[/url]

  9. [url=https://tadlafil.quest/]tadalafil tablets 20 mg uk[/url] [url=https://cialiprice.quest/]buy cialis free shipping[/url] [url=https://viarga100mg.quest/]how can i get viagra prescription[/url] [url=https://tadalalif.quest/]cheap tadalafil 40 mg[/url] [url=https://buyviagonline.quest/]generic viagra in the usa[/url]

  10. [url=http://sildenafl.quest/]sildenafil online no prescription[/url] [url=http://cial.quest/]cialis over the counter usa[/url] [url=http://viaagra.quest/]viagra brand price[/url] [url=http://buyviagonline.quest/]order viagra us[/url] [url=http://tadalafialis.quest/]cialis daily use price[/url] [url=http://viagaraonline.quest/]buy viagra online canada[/url] [url=http://viaqra.quest/]best price viagra 25mg[/url] [url=http://viagasildnafil.quest/]viagra 4 sale[/url] [url=http://cialprescription.quest/]cialis daily prescription[/url] [url=http://buysildenafl.quest/]sildenafil for sale canada[/url]

  11. [url=https://cialiprice.quest/]cialis 20mg india price[/url] [url=https://vigra100.quest/]buy generic viagra in india[/url] [url=https://viaga.quest/]real viagra for sale online[/url] [url=https://buyviagr.quest/]generic prescription viagra[/url] [url=https://calis5.quest/]cialis 300mg[/url]

  12. En ucuz ve kaliteli siteden uygun fyatlara satın alabilmek için takip2018 in bütün faydalarından hemen faydalanabilirsiniz,
    Türk takipçi paketleri 1000 adet takipçiden başlayarak 100bin takipçiye kadar
    alabilirsiniz.
    100bin takipçiyi sadece 12 saat içerisinde profilinize
    yükleyebiliyorlar.
    Takipçi Satın Al sitesinden takipçi almak için hemen takip2018.com a giderek alın.

  13. [url=https://permethrin.online/]order elimite online[/url] [url=https://amoxil.today/]amoxil pills[/url] [url=https://flagyl.monster/]flagyl 500 mg generic[/url] [url=https://amoxicillin.monster/]amoxicillin online pharmacy uk[/url] [url=https://abilify.monster/]buy abilify[/url] [url=https://molnupiravirbuy.com/]molnupiravir europe[/url] [url=https://flomax.quest/]flomax for ed[/url] [url=https://ivermectindtabs.com/]ivermectin 2mg[/url] [url=https://ivermectinjtab.online/]where to buy ivermectin[/url] [url=https://prednisolone.online/]prednisolone 10 mg no prescription[/url]

  14. [url=https://ivermectinatabs.online/]where to buy stromectol[/url] [url=https://modafinilpill.online/]buy provigil[/url] [url=https://tadalafillx.com/]buy tadalafil europe[/url] [url=https://buyantabuse.online/]prescription antabuse[/url] [url=https://kamagratabs.online/]cheap kamagra uk paypal[/url] [url=https://tadalafilbuyr.com/]tadalafil 20[/url] [url=https://prednisone.live/]prednisone brand name canada[/url] [url=https://tadalafilctabs.com/]cost of tadalafil 20 mg[/url] [url=https://buynolvadex.quest/]buy nolvadex online usa[/url] [url=https://ivermectinvtabs.com/]ivermectin 3[/url]

  15. [url=https://prozac.today/]where can i buy prozac online[/url] [url=https://ipviagra.com/]order viagra paypal[/url] [url=https://disulfiram.monster/]disulfiram india[/url] [url=https://viagradi.com/]cheap viagra usa[/url] [url=https://diviagra.com/]best viagra pills uk[/url]

  16. [url=https://viagrarxn.com/]best viagra for women[/url] [url=https://buynolvadex.quest/]nolvadex 10mg tablets price[/url] [url=https://hdcialis.com/]cialis daily for sale[/url] [url=https://tadalafillx.com/]online tadalafil 20mg[/url] [url=https://advair.monster/]advair diskus 250 cheap[/url] [url=https://zithromax.today/]can i buy zithromax over the counter[/url] [url=https://nrtadalafil.com/]tadalafil over the counter australia[/url] [url=https://buytadalafildrug.com/]tadalafil 5mg price[/url] [url=https://sildenafilbuybest.com/]cheap sildenafil pills[/url] [url=https://gabapentin.monster/]gabapentin 600 mg[/url]

  17. [url=https://cialisdtabs.com/]buying cialis in nz[/url] [url=https://nrtadalafil.com/]tadalafil us[/url] [url=https://bupropion.today/]zyban weight loss[/url] [url=https://cialisngeneric.com/]female cialis tadalafil[/url] [url=https://tadalafilnih.com/]generic tadalafil 40 mg[/url] [url=https://disulfiram.today/]disulfiram brand in india[/url] [url=https://sildenafilgtab.com/]sildenafil 50 mg tablet price in india[/url] [url=https://gabapentin.monster/]how much is gabapentin[/url] [url=https://cytotec.monster/]where can i get misoprostol pills in south africa[/url] [url=https://ecsildenafil.com/]sildenafil online purchase[/url]

  18. Woah! I’m really loving the template/theme of this
    blog. It’s simple, yet effective. A lot of times it’s tough to get that “perfect balance” between superb usability and
    visual appeal. I must say you’ve done a excellent job with this.
    Additionally, the blog loads extremely quick for me on Firefox.
    Exceptional Blog!

  19. [url=https://zithromax.online/]zithromax from mexico[/url] [url=https://diviagra.com/]viagra tablet 25 mg[/url] [url=https://modafinil.today/]best modafinil[/url] [url=https://retinoa.live/]retino 0.05 price[/url] [url=https://prozac.today/]how to get prozac uk[/url]

  20. Check out 1this guide abouthow to get free diamonds in cooking fever

    gddd23jas
    The game is a lot more fun when you have unlimited gems.If you enjoy phone games like this you should check out the site above

  21. Check 1this website to learn about how to get free diamonds in cooking fever

    gddd23jas
    Cooking Fever is a whole lot more fun when you have unlimited free gems.If you like mobile games like this you ought to check out this guide

  22. [url=http://piroxicam.live/]feldene gel prices[/url] [url=http://cialisfirst.com/]generic cialis 20 mg safe website[/url] [url=http://tadalafildh.com/]tadalafil 40 mg cheap[/url] [url=http://buyhydrochlorothiazide.online/]hydrochlorothiazide online[/url] [url=http://sildenafilcipill.com/]sildenafil 100mg[/url] [url=http://viagraddf.com/]best cheap viagra[/url] [url=http://buystromectol.online/]ivermectin 6mg[/url] [url=http://sildenafilgmx.com/]sildenafil usa[/url] [url=http://buyseroquel.quest/]seroquel 10mg tablet[/url] [url=http://genericviagramedication.com/]viagra 200mg tablets[/url]

  23. I will immediately clutch your rss as I can’t find your email subscription hyperlink or e-newsletter
    service. Do you have any? Kindly let me know so that I may just subscribe.
    Thanks.

  24. One other issue is when you are in a situation where you will not have a co-signer then you may really need to try to make use of all of your money for college options. You’ll find many grants and other grants that will provide you with money that can help with school expenses. Thanks a lot for the post.

  25. [url=http://ivermectinjtab.com/]ivermectin 20 mg[/url] [url=http://tadalafildh.com/]tadalafil 20mg from canada[/url] [url=http://metformin.monster/]metformin 500mg canada[/url] [url=http://tamoxifen.monster/]tamoxifen price[/url] [url=http://neurontin.quest/]neurontin capsule 600mg[/url]

  26. [url=http://ivermectinxtabs.com/]ivermectin 200mg[/url] [url=http://genericviagrapharm.com/]generic viagra online canada[/url] [url=http://ivermectinctab.com/]ivermectin 1 cream 45gm[/url] [url=http://sildenafilcipill.com/]sildenafil-citrate[/url] [url=http://tadalafilam.com/]tadalafil 5mg price in india[/url] [url=http://cialisfirst.com/]canada cialis[/url] [url=http://ivermectinrtabs.com/]stromectol xr[/url] [url=http://buygabapentin.online/]neurontin 600[/url] [url=http://neurontingabapentin.online/]2400 mg gabapentin[/url] [url=http://buytamoxifen.online/]how much is tamoxifen[/url]

  27. It is perfect time to make some plans for the future and it’s time to
    be happy. I have read this post and if I could I wish to suggest you some interesting things or suggestions.

    Perhaps you could write next articles referring
    to this article. I wish to read more things about it!

  28. Howdy! This article couldn’t be written much better!
    Going through this post reminds me of my previous roommate!
    He continually kept preaching about this. I am going to send this information to him.
    Fairly certain he will have a great read. Thank you for sharing!

  29. [url=http://metformin.monster/]metformin online without prescription[/url] [url=http://buyvardenafil.quest/]buy generic levitra uk[/url] [url=http://piroxicam.live/]feldene 20 mg tablets[/url] [url=http://lopressor.live/]lopressor cost[/url] [url=http://tadalafilmedstore.com/]buy tadalafil online india[/url]

  30. [url=https://cheapcialis20mgnorx.monster/]real cialis 20mg[/url] [url=https://ordersildenafilcitratepills.online/]cheapest sildenafil 50 mg[/url] [url=https://buyviagra100mgbestprice.quest/]generic viagra professional[/url] [url=https://viagracheapdrugonlinedrugstore.quest/]buying generic viagra[/url] [url=https://bestviagratabsnorx.monster/]viagra online 150mg[/url]

  31. Takip2018 den sende dilersen Türk dilersen yabancı takipçi alabilirsin
    instagram Takipçi Satın Al
    hizmet ve işlemi gerçekleştirerek Hemen fenomen adayları
    arasında yerini alabilirsin.
    İster iş ister real hayatınız da herzaman 1 adım önde olmak istemezmisiniz?

    Herkes ister tabiki ,
    O halde instagram takipçi satın almalısınız.
    Ucuz ve en güvenilir takipçi sitesini kullanarak instagram takipçi satın alabilrisiniz

  32. [url=https://bestcialispillsbuyingonline.quest/]cheap cialis 5mg australia[/url] [url=https://onlinecialissale.quest/]generic cialis drugstore[/url] [url=https://buymedrol.quest/]medrol 8mg tablet price[/url] [url=https://norxorder.online/]zofran prescription cost[/url] [url=https://buybactrim.quest/]bactrim pills[/url] [url=https://efxcialis.online/]cialis price south africa[/url] [url=https://hydrochlorothiazidetabs.online/]zestoretic cost[/url] [url=https://bestcialisbuy.quest/]cialis for sale over the counter[/url] [url=https://viagrabestmedicationdrugstore.quest/]order sildenafil online uk[/url] [url=https://buyviagra150bestprice.quest/]sildenafil 20 mg price comparison[/url]

  33. [url=https://onlinecialisforsaleonline.monster/]cialis canada[/url] [url=https://cheapcialismedicationwithoutrx.monster/]cialis 100mg price[/url] [url=https://marchpharmacy.online/]online pharmacy no prescription needed[/url] [url=https://genericviagramedicineforsaleonline.quest/]otc viagra pills[/url] [url=https://efxcialis.online/]cialis discount prices online[/url] [url=https://onlinecialissale.quest/]cialis 5mg daily canada[/url] [url=https://bellatabs.online/]chloromycetin 1[/url] [url=https://bestcialispillsbuyingonline.quest/]buy brand cialis online usa[/url] [url=https://pharmacydrugmart.online/]legitimate online pharmacy uk[/url] [url=https://bestcialispillsnorx.monster/]buy cialis pills[/url]

  34. I’ve been browsing online more than 3 hours today, yet I never found
    any interesting article like yours. It is pretty worth enough
    for me. Personally, if all web owners and bloggers made good content as you did,
    the web will be much more useful than ever before.

  35. [url=http://viagra100pills.quest/]cheap viagra online india pharmacy[/url] [url=http://cheapcialis40noprescription.monster/]order cialis canadian pharmacy[/url] [url=http://genericcialis10withoutrx.monster/]can i buy cialis in canada[/url] [url=http://bestcialis5rx.quest/]cialis 20mg price[/url] [url=http://buygenericcialis5mg.monster/]cheap prices for cialis[/url] [url=http://cialisbestpillforsaleonline.monster/]buy cialis online no prescription[/url] [url=http://bestviagra50mgtablet.quest/]female viagra in canada[/url] [url=http://sildenafilxc.online/]cost of sildenafil in india[/url] [url=http://genericcialistablet.online/]genuine cialis online[/url] [url=http://onlineviagra200tabs.quest/]viagra free delivery[/url]

  36. [url=http://sixtabs.online/]where can you buy proscar[/url] [url=http://bestviagra50noprescription.monster/]can you buy viagra in europe[/url] [url=http://buyivermectindrug.online/]ivermectin 200mg[/url] [url=http://ordercialismedicinewithnorx.quest/]cost of cialis in uk[/url] [url=http://viagrabestpillsale.quest/]where to buy viagra in canada[/url] [url=http://cialistabsdrugstore.monster/]buy brand cialis canada[/url] [url=http://bestcialistabletsbuy.monster/]cialis over the counter mexico[/url] [url=http://orderviagra200mgtabs.quest/]how to buy viagra online safely in india[/url] [url=http://genericviagramedicineprescription.monster/]where can i order generic viagra online[/url] [url=http://buyinggenericcialis5.monster/]cialis online free shipping[/url]

  37. How would I go very nearly creating a extra blog that could become well-off in less than a year. I have a lot of ideas of alternating things I could include, in view of that I don’t know that content would be an issue. What are fine ways to announce a extra blog and is it greater than before to jump approximately taking into consideration alternative topics or pin to one? What else can I attain to create it successful??.

  38. What get people think would be a fine blog hosting website for creating a blog on? There are a lot I think in view of that I don’t know which would be most useful and versatile..

  39. I have been surfing online more than 4 hours today, yet I never
    found any interesting article like yours. It is pretty worth enough
    for me. In my view, if all webmasters and bloggers
    made good content as you did, the internet will be much more useful
    than ever before.

  40. [url=http://genericviagratabletorder.quest/]average price of viagra[/url] [url=http://icialis.quest/]canada cialis no prescription[/url] [url=http://modafiniltbs.online/]modafinil for sale in us[/url] [url=http://aurograonline.online/]aurogra 100mg tablets[/url] [url=http://buywithnorx.online/]tadalafil medicine online[/url]

LEAVE A REPLY

Please enter your comment!
Please enter your name here